From 345032f804752306f4d29fe0f298643c7ad23b30 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 21 Feb 2022 12:35:52 +0000 Subject: [PATCH 001/254] Get env stuff working on Windows --- core/os/os2/env.odin | 15 ++------ core/os/os2/env_windows.odin | 72 +++++++++++++++++++++-------------- core/os/os2/errors.odin | 56 ++++++++++++++++----------- core/os/os2/file.odin | 31 ++++++++++----- core/os/os2/file_windows.odin | 2 +- core/os/os2/user.odin | 22 +++++------ 6 files changed, 112 insertions(+), 86 deletions(-) diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index f25290a59..f1a3e40c7 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -1,20 +1,11 @@ package os2 -// get_env retrieves the value of the environment variable named by the key -// It returns the value, which will be empty if the variable is not present -// To distinguish between an empty value and an unset value, use lookup_env -// NOTE: the value will be allocated with the supplied allocator -get_env :: proc(key: string, allocator := context.allocator) -> string { - value, _ := lookup_env(key, allocator) - return value -} - -// lookup_env gets the value of the environment variable named by the key +// get_env gets the value of the environment variable named by the key // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - return _lookup_env(key, allocator) +get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return _get_env(key, allocator) } // set_env sets the value of the environment variable named by the key diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index af04db858..a3b97375b 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,50 +1,56 @@ //+private package os2 +import "core:runtime" import "core:mem" import win32 "core:sys/windows" -_lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { if key == "" { return } wkey := win32.utf8_to_wstring(key) - b := make([dynamic]u16, 100, context.temp_allocator) - for { - n := win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) - if n == 0 { - err := win32.GetLastError() - if err == win32.ERROR_ENVVAR_NOT_FOUND { - return "", false - } - } - if n <= u32(len(b)) { - value = win32.utf16_to_utf8(b[:n], allocator) - found = true - return - } - - resize(&b, len(b)*2) + // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew + buf_len := win32.GetEnvironmentVariableW(wkey, nil, 0) + if buf_len == 0 { + return } + buf := make([dynamic]u16, buf_len, context.temp_allocator) + n := win32.GetEnvironmentVariableW(wkey, raw_data(buf), buf_len) + if n == 0 { + if win32.GetLastError() == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false + } + value = "" + found = true + return + } + + value = win32.utf16_to_utf8(buf[:n], allocator) + found = true + return } _set_env :: proc(key, value: string) -> bool { k := win32.utf8_to_wstring(key) v := win32.utf8_to_wstring(value) + // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew return bool(win32.SetEnvironmentVariableW(k, v)) } _unset_env :: proc(key: string) -> bool { k := win32.utf8_to_wstring(key) + + // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew return bool(win32.SetEnvironmentVariableW(k, nil)) } _clear_env :: proc() { envs := environ(context.temp_allocator) for env in envs { - for j in 1.. Maybe(Path_Error) { +_mkdir :: proc(name: string, perm: File_Mode) -> Error { return nil } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) { +_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { // TODO(bill): _mkdir_all for windows return nil } -_remove_all :: proc(path: string) -> Maybe(Path_Error) { +_remove_all :: proc(path: string) -> Error { // TODO(bill): _remove_all for windows return nil } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index 5570ca282..628b4c836 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -4,8 +4,12 @@ package os2 import win32 "core:sys/windows" _pipe :: proc() -> (r, w: Handle, err: Error) { + sa: win32.SECURITY_ATTRIBUTES + sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) + sa.bInheritHandle = true + p: [2]win32.HANDLE - if !win32.CreatePipe(&p[0], &p[1], nil, 0) { + if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { return 0, 0, Platform_Error{i32(win32.GetLastError())} } return Handle(p[0]), Handle(p[1]), nil diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 19f1453ff..34d0de1c5 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -24,15 +24,15 @@ file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { delete(fi.fullpath, allocator) } -fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { return _fstat(fd, allocator) } -stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return _stat(name, allocator) } -lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return _lstat(name, allocator) } diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index f46a9435c..23ab8a44a 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -4,9 +4,9 @@ package os2 import "core:time" import win32 "core:sys/windows" -_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { if fd == 0 { - return {}, Path_Error{err = .Invalid_Argument} + return {}, .Invalid_Argument } context.allocator = allocator @@ -27,10 +27,10 @@ _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe( return _file_info_from_get_file_information_by_handle(path, h) } -_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS) } -_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT) } _same_file :: proc(fi1, fi2: File_Info) -> bool { @@ -38,13 +38,7 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { } - -_stat_errno :: proc(errno: win32.DWORD) -> Path_Error { - return Path_Error{err = Platform_Error{i32(errno)}} -} - - -full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Maybe(Path_Error)) { +full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Error) { context.allocator = allocator name := name @@ -57,7 +51,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) if n == 0 { delete(buf) - return "", _stat_errno(win32.GetLastError()) + return "", _get_platform_error() } if n <= u32(len(buf)) { return win32.utf16_to_utf8(buf[:n]), nil @@ -69,9 +63,9 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa } -internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Maybe(Path_Error)) { +internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { - return {}, Path_Error{err = .Not_Exist} + return {}, .Not_Exist } context.allocator = allocator @@ -91,7 +85,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co fd: win32.WIN32_FIND_DATAW sh := win32.FindFirstFileW(wname, &fd) if sh == win32.INVALID_HANDLE_VALUE { - e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}} + e = _get_platform_error() return } win32.FindClose(sh) @@ -101,7 +95,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) if h == win32.INVALID_HANDLE_VALUE { - e = Path_Error{err = Platform_Error{i32(win32.GetLastError())}} + e = _get_platform_error() return } defer win32.CloseHandle(h) @@ -130,9 +124,9 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { } -_cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { +_cleanpath_from_handle :: proc(fd: Handle) -> (string, Error) { if fd == 0 { - return "", Path_Error{err = .Invalid_Argument} + return "", .Invalid_Argument } h := win32.HANDLE(fd) @@ -143,7 +137,7 @@ _cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0) switch err { case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: - return "", _stat_errno(err) + return "", Platform_Error{i32(err)} case win32.ERROR_NOT_ENOUGH_MEMORY: MAX_PATH = MAX_PATH*2 + 1 continue @@ -153,9 +147,9 @@ _cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { return _cleanpath_from_buf(buf), nil } -_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) { +_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Error) { if fd == 0 { - return nil, Path_Error{err = .Invalid_Argument} + return nil, .Invalid_Argument } h := win32.HANDLE(fd) @@ -166,7 +160,7 @@ _cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) { err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0) switch err { case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: - return nil, _stat_errno(err) + return nil, Platform_Error{i32(err)} case win32.ERROR_NOT_ENOUGH_MEMORY: MAX_PATH = MAX_PATH*2 + 1 continue @@ -251,7 +245,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA } -_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { +_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -268,7 +262,7 @@ _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE } -_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { +_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -285,10 +279,10 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string } -_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Maybe(Path_Error)) { +_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { - return {}, _stat_errno(win32.GetLastError()) + return {}, _get_platform_error() } @@ -296,7 +290,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { err := win32.GetLastError() if err != win32.ERROR_INVALID_PARAMETER { - return {}, _stat_errno(err) + return {}, Platform_Error{i32(err)} } // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 8c58fbd52..a530a402a 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -53,6 +53,7 @@ foreign kernel32 { LeaveCriticalSection :: proc(CriticalSection: ^CRITICAL_SECTION) --- DeleteCriticalSection :: proc(CriticalSection: ^CRITICAL_SECTION) --- + PathFileExistsW :: proc(lpPathName: LPCWSTR) -> BOOL --- RemoveDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL --- SetFileAttributesW :: proc(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL --- SetLastError :: proc(dwErrCode: DWORD) --- From 6630d703f82bf06849727ba2a62d38c56366c433 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 21 Feb 2022 13:42:29 +0000 Subject: [PATCH 003/254] Clean up ok or error handling --- core/os/os2/file_windows.odin | 71 ++++++++++------------------------- 1 file changed, 20 insertions(+), 51 deletions(-) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index d0cf4505d..9fdbd9a5a 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -15,6 +15,10 @@ _get_platform_error :: proc() -> Error { return Platform_Error{i32(err)} } +_ok_or_error :: proc(ok: win32.BOOL) -> Error { + return nil if ok else _get_platform_error() +} + _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { get_handle :: proc(h: win32.DWORD) -> Handle { fd := win32.GetStdHandle(h) @@ -88,17 +92,13 @@ _close :: proc(fd: Handle) -> Error { hnd := win32.HANDLE(fd) file_info: win32.BY_HANDLE_FILE_INFORMATION - if ok := win32.GetFileInformationByHandle(hnd, &file_info); !ok { - return _get_platform_error() - } + _ok_or_error(win32.GetFileInformationByHandle(hnd, &file_info)) or_return + if file_info.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { return nil } - if ok := win32.CloseHandle(hnd); !ok { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.CloseHandle(hnd)) } _name :: proc(fd: Handle, allocator := context.allocator) -> string { @@ -145,10 +145,7 @@ _read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { max_read := u32(min(BUF_SIZE, len(b)/4)) single_read_length: u32 - ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) - if !ok { - err = _get_platform_error() - } + err = _ok_or_error(win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil)) buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) src := buf8[:buf8_len] @@ -245,9 +242,7 @@ _pread :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { h := win32.HANDLE(fd) done: win32.DWORD - if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - _get_platform_error() or_return - } + _ok_or_error(win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return return int(done), nil } @@ -267,9 +262,7 @@ _pwrite :: proc(fd: Handle, data: []byte, offset: i64) -> (n: int, err: Error) { h := win32.HANDLE(fd) done: win32.DWORD - if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { - _get_platform_error() or_return - } + _ok_or_error(win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o)) or_return return int(done), nil } @@ -298,9 +291,7 @@ _write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { _file_size :: proc(fd: Handle) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER - if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { - err = _get_platform_error() - } + err = _ok_or_error(win32.GetFileSizeEx(win32.HANDLE(fd), &length)) return i64(length), err } @@ -310,34 +301,24 @@ _sync :: proc(fd: Handle) -> Error { } _flush :: proc(fd: Handle) -> Error { - if !win32.FlushFileBuffers(win32.HANDLE(fd)) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.FlushFileBuffers(win32.HANDLE(fd))) } _truncate :: proc(fd: Handle, size: i64) -> Error { offset := seek(fd, size, .Start) or_return defer seek(fd, offset, .Start) - if !win32.SetEndOfFile(win32.HANDLE(fd)) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.SetEndOfFile(win32.HANDLE(fd))) } _remove :: proc(name: string) -> Error { p := win32.utf8_to_wstring(_fix_long_path(name)) - err, err1: Error - if !win32.DeleteFileW(p) { - err = _get_platform_error() - } + + err := _ok_or_error(win32.DeleteFileW(p)) if err == nil { return nil } - if !win32.RemoveDirectoryW(p) { - err1 = _get_platform_error() - } + err1 := _ok_or_error(win32.RemoveDirectoryW(p)) if err1 == nil { return nil } @@ -351,10 +332,7 @@ _remove :: proc(name: string) -> Error { err = err1 } else if a & win32.FILE_ATTRIBUTE_READONLY != 0 { if win32.SetFileAttributesW(p, a &~ win32.FILE_ATTRIBUTE_READONLY) { - err = nil - if !win32.DeleteFileW(p) { - err = _get_platform_error() - } + err = _ok_or_error(win32.DeleteFileW(p)) } } } @@ -366,20 +344,14 @@ _remove :: proc(name: string) -> Error { _rename :: proc(old_path, new_path: string) -> Error { from := win32.utf8_to_wstring(old_path, context.temp_allocator) to := win32.utf8_to_wstring(new_path, context.temp_allocator) - if !win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING)) } _link :: proc(old_name, new_name: string) -> Error { n := win32.utf8_to_wstring(_fix_long_path(new_name)) o := win32.utf8_to_wstring(_fix_long_path(old_name)) - if !win32.CreateHardLinkW(n, o, nil) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.CreateHardLinkW(n, o, nil)) } _symlink :: proc(old_name, new_name: string) -> Error { @@ -392,10 +364,7 @@ _read_link :: proc(name: string) -> (string, Error) { _unlink :: proc(path: string) -> Error { wpath := win32.utf8_to_wstring(path, context.temp_allocator) - if !win32.DeleteFileW(wpath) { - return _get_platform_error() - } - return nil + return _ok_or_error(win32.DeleteFileW(wpath)) } From e51bb4ef12ff6e5cf900f266f6da9d132b7167eb Mon Sep 17 00:00:00 2001 From: CiD- Date: Thu, 3 Mar 2022 10:16:36 -0500 Subject: [PATCH 004/254] os2 linux begin --- core/os/os2/file_linux.odin | 236 ++++++++++++++++++++++++++++++ core/os/os2/stat_linux.odin | 116 +++++++++++++++ core/sys/unix/syscalls_linux.odin | 138 ++++++++++++++++- 3 files changed, 486 insertions(+), 4 deletions(-) create mode 100644 core/os/os2/file_linux.odin create mode 100644 core/os/os2/stat_linux.odin diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin new file mode 100644 index 000000000..75a71b22b --- /dev/null +++ b/core/os/os2/file_linux.odin @@ -0,0 +1,236 @@ +//+private +package os2 + +import "core:io" +import "core:time" +import "core:sys/unix" + + +_get_platform_error :: proc(res: int) -> Error { + errno := unix.get_errno(res) + return Platform_Error{i32(errno)} +} + +_ok_or_error :: proc(res: int) -> Error { + return res >= 0 ? nil : _get_platform_error(res) +} + +_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := Handle(unix.sys_open(cstr, int(flags), int(perm))) + if handle < 0 { + return Handle(-1), _get_platform_error(int(handle)) + } + return handle, nil +} + +_close :: proc(fd: Handle) -> Error { + res := unix.sys_close(int(fd)) + return _ok_or_error(res) +} + +_name :: proc(fd: Handle, allocator := context.allocator) -> string { + //TODO + return "" +} + +_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + res := unix.sys_lseek(int(fd), offset, int(whence)) + if res < 0 { + return -1, _get_platform_error(int(res)) + } + return res, nil +} + +_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { + if len(p) == 0 { + return 0, nil + } + n = unix.sys_read(fd, &data[0], c.size_t(len(data))) + if n < 0 { + return -1, unix.get_errno(n) + } + return bytes_read, nil +} + +_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + curr_offset, err := _seek(fd, 0, .Current) + if err != nil { + return 0, err + } + defer _seek(fd, curr_offset, .Start) + _seek(fd, offset, .Start) + + b := p + for len(b) > 0 { + m := _read(fd, b) or_return + n += m + b = b[m:] + } + return +} + +_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { + //TODO + return +} + +_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { + if len(p) == 0 { + return 0, nil + } + n = unix.sys_write(fd, &p[0], uint(len(p))) + if n < 0 { + return -1, _get_platform_error(n) + } + return int(n), nil +} + +_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { + if offset < 0 { + return 0, .Invalid_Offset + } + + curr_offset, err := _seek(fd, 0, .Current) + if err != nil { + return 0, err + } + defer _seek(fd, curr_offset, .Start) + _seek(fd, offset, .Start) + + b := p + for len(b) > 0 { + m := _write(fd, b) or_return + n += m + b = b[m:] + } + return +} + +_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { + //TODO + return +} + +_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { + s, err := _fstat(fd) or_return + if err != nil { + return 0, err + } + return max(s.size, 0), nil +} + +_sync :: proc(fd: Handle) -> Error { + return _ok_or_error(unix.sys_fsync(int(fd))) +} + +_flush :: proc(fd: Handle) -> Error { + return _ok_or_error(unix.sys_fsync(int(fd))) +} + +_truncate :: proc(fd: Handle, size: i64) -> Error { + return _ok_or_error(unix.sys_ftruncate(int(fd), size)) +} + +_remove :: proc(name: string) -> Error { + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + if _is_dir(name) { + return _ok_or_error(unix.sys_rmdir(path_cstr)) + } + return _ok_or_error(unix.sys_unlink(path_cstr)) +} + +_rename :: proc(old_path, new_path: string) -> Error { + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + return _ok_or_error(unix.sys_rename(old_path_cstr, new_path_cstr)) +} + +_link :: proc(old_name, new_name: string) -> Error { + old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) + new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + return _ok_or_error(unix.sys_link(old_name_cstr, new_name_cstr)) +} + +_symlink :: proc(old_name, new_name: string) -> Error { + old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) + new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr)) +} + +_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { + path_cstr := strings.clone_to_cstring(path) + defer delete(path_cstr) + + bufsz : uint = 256 + buf := make([]byte, bufsz, allocator) + for { + rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz) + if rc < 0 { + delete(buf) + return "", unix.get_errno(rc) + } else if rc == int(bufsz) { + bufsz *= 2 + delete(buf) + buf = make([]byte, bufsz, allocator) + } else { + return strings.string_from_ptr(&buf[0], rc), nil + } + } +} + +_unlink :: proc(path: string) -> Error { + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + return _ok_or_error(unix.sys_unlink(path_cstr)) +} + +_chdir :: proc(fd: Handle) -> Error { + return _ok_or_error(unix.sys_fchdir(int(fd))) +} + +_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { + //TODO + return nil +} + +_chown :: proc(fd: Handle, uid, gid: int) -> Error { + //TODO + return nil +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + //TODO + return nil +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + //TODO + return nil +} + +_exists :: proc(path: string) -> bool { + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + return unix.sys_access(path_cstr, F_OK) == 0 +} + +_is_file :: proc(fd: Handle) -> bool { + s: OS_Stat + res := unix.sys_fstat(int(fd), rawptr(&s)) + if res < 0 { // error + return false + } + return S_ISREG(s.mode) +} + +_is_dir :: proc(fd: Handle) -> bool { + s: OS_Stat + res := unix.sys_fstat(int(fd), rawptr(&s)) + if res < 0 { // error + return false + } + return S_ISDIR(s.mode) +} diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin new file mode 100644 index 000000000..7fce8fb9c --- /dev/null +++ b/core/os/os2/stat_linux.odin @@ -0,0 +1,116 @@ +//+private +package os2 + +import "core:time" +import "core:sys/unix" + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + + // Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + + // Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + + +S_ISLNK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFLNK } +S_ISREG :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFREG } +S_ISDIR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFDIR } +S_ISCHR :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFCHR } +S_ISBLK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFBLK } +S_ISFIFO :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFIFO } +S_ISSOCK :: #force_inline proc(m: u32) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +@private +OS_Stat :: struct { + device_id: u64, // ID of device containing file + serial: u64, // File serial number + nlink: u64, // Number of hard links + mode: u32, // Mode of the file + uid: u32, // User ID of the file's owner + gid: u32, // Group ID of the file's group + _padding: i32, // 32 bits of padding + rdev: u64, // Device ID, if device + size: i64, // Size of the file, in bytes + block_size: i64, // Optimal bllocksize for I/O + blocks: i64, // Number of 512-byte blocks allocated + + last_access: Unix_File_Time, // Time of last access + modified: Unix_File_Time, // Time of last modification + status_change: Unix_File_Time, // Time of last status change + + _reserve1, + _reserve2, + _reserve3: i64, +} + +_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { +} + +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { +} + +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { + cstr := strings.clone_to_cstring(path) + defer delete(cstr) + + s: OS_Stat + result := unix.sys_lstat(cstr, &s) + if result < 0 { + return {}, unix.get_errno(result) + } + + fi := File_Info { + fullpath = "", + name = "", + size = s.size, + mode = 0, + is_dir = S_ISDIR(s.mode), + creation_time = nil, // linux does not track this + //TODO + modification_time = nil, + access_time = nil, + } + + return fi, nil +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} + +_stat_internal :: proc(name: string) -> (s: OS_Stat, res: int) { + name_cstr = strings.clone_to_cstring(name, context.temp_allocator) + res = unix.sys_stat(name_cstr, &s) + return +} diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 0082c7261..243f8accc 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -15,7 +15,7 @@ import "core:intrinsics" // 386: arch/x86/entry/syscalls/sycall_32.tbl // arm: arch/arm/tools/syscall.tbl -when ODIN_ARCH == .amd64 { +when ODIN_ARCH == "amd64" { SYS_read : uintptr : 0 SYS_write : uintptr : 1 SYS_open : uintptr : 2 @@ -374,7 +374,7 @@ when ODIN_ARCH == .amd64 { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when ODIN_ARCH == .arm64 { +} else when ODIN_ARCH == "arm64" { SYS_io_setup : uintptr : 0 SYS_io_destroy : uintptr : 1 SYS_io_submit : uintptr : 2 @@ -675,7 +675,7 @@ when ODIN_ARCH == .amd64 { SYS_landlock_create_ruleset : uintptr : 444 SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 -} else when ODIN_ARCH == .i386 { +} else when ODIN_ARCH == "386" { SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1112,7 +1112,7 @@ when ODIN_ARCH == .amd64 { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when false /*ODIN_ARCH == .arm*/ { // TODO +} else when ODIN_ARCH == "arm" { SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1516,6 +1516,10 @@ when ODIN_ARCH == .amd64 { #panic("Unsupported architecture") } +AT_FDCWD :: -100 +AT_REMOVEDIR :: uintptr(0x200) +AT_SYMLINK_NOFOLLOW :: uintptr(0x100) + sys_gettid :: proc "contextless" () -> int { return cast(int)intrinsics.syscall(SYS_gettid) } @@ -1523,3 +1527,129 @@ sys_gettid :: proc "contextless" () -> int { sys_getrandom :: proc "contextless" (buf: ^byte, buflen: int, flags: uint) -> int { return cast(int)intrinsics.syscall(SYS_getrandom, buf, cast(uintptr)(buflen), cast(uintptr)(flags)) } + +sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { + when ODIN_ARCH != "arm64" { + res := int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) + } else { // NOTE: arm64 does not have open + res := int(intrinsics.syscall(SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode)))) + } + return -1 if res < 0 else res +} + +sys_close :: proc(fd: int) -> int { + return int(intrinsics.syscall(SYS_close, uintptr(fd))) +} + +sys_read :: proc(fd: int, buf: rawptr, size: uint) -> int { + return int(intrinsics.syscall(SYS_read, uintptr(fd), uintptr(buf), uintptr(size))) +} + +sys_write :: proc(fd: int, buf: rawptr, size: uint) -> int { + return int(intrinsics.syscall(SYS_write, uintptr(fd), uintptr(buf), uintptr(size))) +} + +sys_lseek :: proc(fd: int, offset: i64, whence: int) -> i64 { + when ODIN_ARCH == "amd64" || ODIN_ARCH == "arm64" { + return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence))) + } else { + low := uintptr(offset & 0xFFFFFFFF) + high := uintptr(offset >> 32) + result: i64 + res := i64(intrinsics.syscall(SYS__llseek, uintptr(fd), high, low, &result, uintptr(whence))) + return -1 if res < 0 else result + } +} + +sys_stat :: proc(path: cstring, stat: rawptr) -> int { + when ODIN_ARCH == "amd64" { + return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat))) + } else when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat))) + } else { // NOTE: arm64 does not have stat + return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), 0)) + } +} + +sys_fstat :: proc(fd: int, stat: rawptr) -> int { + when ODIN_ARCH == "amd64" || ODIN_ARCH == "arm64" { + return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat))) + } else { + return int(intrinsics.syscall(SYS_fstat64, uintptr(fd), uintptr(stat))) + } +} + +sys_lstat :: proc(path: cstring, stat: rawptr) -> int { + when ODIN_ARCH == "amd64" { + return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat))) + } else when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat))) + } else { // NOTE: arm64 does not have any lstat + return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) + } +} + +sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) + } else { // NOTE: arm64 does not have readlink + return int(intrinsics.syscall(SYS_readlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) + } +} + +sys_access :: proc(path: cstring, mask: int) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) + } else { // NOTE: arm64 does not have access + return int(intrinsics.syscall(SYS_faccessat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mask))) + } +} + +sys_getcwd :: proc(buf: rawptr, size: uint) -> int { + return int(intrinsics.syscall(SYS_getcwd, uintptr(buf), uintptr(size))) +} + +sys_chdir :: proc(path: cstring) -> int { + return int(intrinsics.syscall(SYS_chdir, uintptr(rawptr(path)))) +} + +sys_rename :: proc(old, new: cstring) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) + } else { // NOTE: arm64 does not have rename + return int(intrinsics.syscall(SYS_renameat, uintptr(AT_FDCWD), uintptr(rawptr(old)), uintptr(rawptr(new)))) + } +} + +sys_unlink :: proc(path: cstring) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) + } else { // NOTE: arm64 does not have unlink + return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path), 0))) + } +} + +sys_rmdir :: proc(path: cstring) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) + } else { // NOTE: arm64 does not have rmdir + return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), AT_REMOVEDIR)) + } +} + +sys_mkdir :: proc(path: cstring, mode: u32 = 0o775) -> int { + when ODIN_ARCH != "arm64" { + return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) + } else { // NOTE: arm64 does not have mkdir + return int(intrinsics.syscall(SYS_mkdirat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) + } +} + +//TODO: ftruncate, symlink, readlink, fchdir, fchmod, chown, fchown, lchown + +get_errno :: proc(res: int) -> i32 { + if res < 0 && res > -4096 { + return i32(-res) + } + return 0 +} From 658a605c75c32746f248dc8e8e367c6b5dae976e Mon Sep 17 00:00:00 2001 From: jasonkercher Date: Fri, 4 Mar 2022 17:11:53 -0500 Subject: [PATCH 005/254] compiles --- core/os/os2/env_linux.odin | 28 +++++ core/os/os2/errors_linux.odin | 134 +++++++++++++++++++++++ core/os/os2/file.odin | 4 + core/os/os2/file_linux.odin | 169 ++++++++++++++++++++---------- core/os/os2/file_windows.odin | 5 + core/os/os2/heap_linux.odin | 27 +++++ core/os/os2/path_linux.odin | 85 +++++++++++++++ core/os/os2/pipe_linux.odin | 7 ++ core/os/os2/stat_linux.odin | 22 ++-- core/os/os2/temp_file_linux.odin | 18 ++++ core/sys/unix/syscalls_linux.odin | 146 +++++++++++++++++++++----- 11 files changed, 557 insertions(+), 88 deletions(-) create mode 100644 core/os/os2/env_linux.odin create mode 100644 core/os/os2/errors_linux.odin create mode 100644 core/os/os2/heap_linux.odin create mode 100644 core/os/os2/path_linux.odin create mode 100644 core/os/os2/pipe_linux.odin create mode 100644 core/os/os2/temp_file_linux.odin diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin new file mode 100644 index 000000000..1833ac4dc --- /dev/null +++ b/core/os/os2/env_linux.odin @@ -0,0 +1,28 @@ +//+private +package os2 + +_get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + //TODO + return +} + +_set_env :: proc(key, value: string) -> bool { + //TODO + return false +} + +_unset_env :: proc(key: string) -> bool { + //TODO + return false +} + +_clear_env :: proc() { + //TODO +} + +_environ :: proc(allocator := context.allocator) -> []string { + //TODO + return nil +} + + diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin new file mode 100644 index 000000000..f074c7c86 --- /dev/null +++ b/core/os/os2/errors_linux.odin @@ -0,0 +1,134 @@ +//+private +package os2 + +EPERM :: 1 +ENOENT :: 2 +ESRCH :: 3 +EINTR :: 4 +EIO :: 5 +ENXIO :: 6 +EBADF :: 9 +EAGAIN :: 11 +ENOMEM :: 12 +EACCES :: 13 +EFAULT :: 14 +EEXIST :: 17 +ENODEV :: 19 +ENOTDIR :: 20 +EISDIR :: 21 +EINVAL :: 22 +ENFILE :: 23 +EMFILE :: 24 +ETXTBSY :: 26 +EFBIG :: 27 +ENOSPC :: 28 +ESPIPE :: 29 +EROFS :: 30 +EPIPE :: 32 +ERANGE :: 34 /* Result too large */ +EDEADLK :: 35 /* Resource deadlock would occur */ +ENAMETOOLONG :: 36 /* File name too long */ +ENOLCK :: 37 /* No record locks available */ +ENOSYS :: 38 /* Invalid system call number */ +ENOTEMPTY :: 39 /* Directory not empty */ +ELOOP :: 40 /* Too many symbolic links encountered */ +EWOULDBLOCK :: EAGAIN /* Operation would block */ +ENOMSG :: 42 /* No message of desired type */ +EIDRM :: 43 /* Identifier removed */ +ECHRNG :: 44 /* Channel number out of range */ +EL2NSYNC :: 45 /* Level 2 not synchronized */ +EL3HLT :: 46 /* Level 3 halted */ +EL3RST :: 47 /* Level 3 reset */ +ELNRNG :: 48 /* Link number out of range */ +EUNATCH :: 49 /* Protocol driver not attached */ +ENOCSI :: 50 /* No CSI structure available */ +EL2HLT :: 51 /* Level 2 halted */ +EBADE :: 52 /* Invalid exchange */ +EBADR :: 53 /* Invalid request descriptor */ +EXFULL :: 54 /* Exchange full */ +ENOANO :: 55 /* No anode */ +EBADRQC :: 56 /* Invalid request code */ +EBADSLT :: 57 /* Invalid slot */ +EDEADLOCK :: EDEADLK +EBFONT :: 59 /* Bad font file format */ +ENOSTR :: 60 /* Device not a stream */ +ENODATA :: 61 /* No data available */ +ETIME :: 62 /* Timer expired */ +ENOSR :: 63 /* Out of streams resources */ +ENONET :: 64 /* Machine is not on the network */ +ENOPKG :: 65 /* Package not installed */ +EREMOTE :: 66 /* Object is remote */ +ENOLINK :: 67 /* Link has been severed */ +EADV :: 68 /* Advertise error */ +ESRMNT :: 69 /* Srmount error */ +ECOMM :: 70 /* Communication error on send */ +EPROTO :: 71 /* Protocol error */ +EMULTIHOP :: 72 /* Multihop attempted */ +EDOTDOT :: 73 /* RFS specific error */ +EBADMSG :: 74 /* Not a data message */ +EOVERFLOW :: 75 /* Value too large for defined data type */ +ENOTUNIQ :: 76 /* Name not unique on network */ +EBADFD :: 77 /* File descriptor in bad state */ +EREMCHG :: 78 /* Remote address changed */ +ELIBACC :: 79 /* Can not access a needed shared library */ +ELIBBAD :: 80 /* Accessing a corrupted shared library */ +ELIBSCN :: 81 /* .lib section in a.out corrupted */ +ELIBMAX :: 82 /* Attempting to link in too many shared libraries */ +ELIBEXEC :: 83 /* Cannot exec a shared library directly */ +EILSEQ :: 84 /* Illegal byte sequence */ +ERESTART :: 85 /* Interrupted system call should be restarted */ +ESTRPIPE :: 86 /* Streams pipe error */ +EUSERS :: 87 /* Too many users */ +ENOTSOCK :: 88 /* Socket operation on non-socket */ +EDESTADDRREQ :: 89 /* Destination address required */ +EMSGSIZE :: 90 /* Message too long */ +EPROTOTYPE :: 91 /* Protocol wrong type for socket */ +ENOPROTOOPT :: 92 /* Protocol not available */ +EPROTONOSUPPORT:: 93 /* Protocol not supported */ +ESOCKTNOSUPPORT:: 94 /* Socket type not supported */ +EOPNOTSUPP :: 95 /* Operation not supported on transport endpoint */ +EPFNOSUPPORT :: 96 /* Protocol family not supported */ +EAFNOSUPPORT :: 97 /* Address family not supported by protocol */ +EADDRINUSE :: 98 /* Address already in use */ +EADDRNOTAVAIL :: 99 /* Cannot assign requested address */ +ENETDOWN :: 100 /* Network is down */ +ENETUNREACH :: 101 /* Network is unreachable */ +ENETRESET :: 102 /* Network dropped connection because of reset */ +ECONNABORTED :: 103 /* Software caused connection abort */ +ECONNRESET :: 104 /* Connection reset by peer */ +ENOBUFS :: 105 /* No buffer space available */ +EISCONN :: 106 /* Transport endpoint is already connected */ +ENOTCONN :: 107 /* Transport endpoint is not connected */ +ESHUTDOWN :: 108 /* Cannot send after transport endpoint shutdown */ +ETOOMANYREFS :: 109 /* Too many references: cannot splice */ +ETIMEDOUT :: 110 /* Connection timed out */ +ECONNREFUSED :: 111 /* Connection refused */ +EHOSTDOWN :: 112 /* Host is down */ +EHOSTUNREACH :: 113 /* No route to host */ +EALREADY :: 114 /* Operation already in progress */ +EINPROGRESS :: 115 /* Operation now in progress */ +ESTALE :: 116 /* Stale file handle */ +EUCLEAN :: 117 /* Structure needs cleaning */ +ENOTNAM :: 118 /* Not a XENIX named type file */ +ENAVAIL :: 119 /* No XENIX semaphores available */ +EISNAM :: 120 /* Is a named type file */ +EREMOTEIO :: 121 /* Remote I/O error */ +EDQUOT :: 122 /* Quota exceeded */ +ENOMEDIUM :: 123 /* No medium found */ +EMEDIUMTYPE :: 124 /* Wrong medium type */ +ECANCELED :: 125 /* Operation Canceled */ +ENOKEY :: 126 /* Required key not available */ +EKEYEXPIRED :: 127 /* Key has expired */ +EKEYREVOKED :: 128 /* Key has been revoked */ +EKEYREJECTED :: 129 /* Key was rejected by service */ +EOWNERDEAD :: 130 /* Owner died */ +ENOTRECOVERABLE:: 131 /* State not recoverable */ +ERFKILL :: 132 /* Operation not possible due to RF-kill */ +EHWPOISON :: 133 /* Memory page has hardware error */ + +_error_string :: proc(errno: i32) -> string { + if errno == 0 { + return "" + } + return "Error" +} diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 707df37a2..09e1e8daf 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -61,6 +61,10 @@ create :: proc(name: string, perm: File_Mode = 0) -> (Handle, Error) { return open(name, {.Read, .Write, .Create}, perm) } +opendir :: proc(name: string) -> (Handle, Error) { + return _opendir(name) +} + open :: proc(name: string, flags := File_Flags{.Read}, perm: File_Mode = 0) -> (Handle, Error) { flags := flags if .Write not_in flags { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 75a71b22b..72fbdcb56 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -3,6 +3,7 @@ package os2 import "core:io" import "core:time" +import "core:strings" import "core:sys/unix" @@ -15,13 +16,64 @@ _ok_or_error :: proc(res: int) -> Error { return res >= 0 ? nil : _get_platform_error(res) } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { - cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle := Handle(unix.sys_open(cstr, int(flags), int(perm))) - if handle < 0 { - return Handle(-1), _get_platform_error(int(handle)) +_std_handle :: proc(kind: Std_Handle_Kind) -> Handle { + switch kind { + case .stdin: return Handle(0) + case .stdout: return Handle(1) + case .stderr: return Handle(2) } - return handle, nil + unreachable() +} + +_O_RDONLY :: 0o0 +_O_WRONLY :: 0o1 +_O_RDWR :: 0o2 +_O_CREAT :: 0o100 +_O_EXCL :: 0o200 +_O_TRUNC :: 0o1000 +_O_APPEND :: 0o2000 +_O_NONBLOCK :: 0o4000 +_O_LARGEFILE :: 0o100000 +_O_DIRECTORY :: 0o200000 +_O_SYNC :: 0o4010000 +_O_CLOEXEC :: 0o2000000 + +_opendir :: proc(name: string) -> (Handle, Error) { + cstr := strings.clone_to_cstring(name, context.temp_allocator) + + flags := _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC + + handle_i := unix.sys_open(cstr, flags) + if handle_i < 0 { + return INVALID_HANDLE, _get_platform_error(handle_i) + } + + return Handle(handle_i), nil +} + +_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { + cstr := strings.clone_to_cstring(name, context.temp_allocator) + + flags_i: int + switch flags & O_RDONLY|O_WRONLY|O_RDWR { + case O_RDONLY: flags_i = _O_RDONLY + case O_WRONLY: flags_i = _O_WRONLY + case O_RDWR: flags_i = _O_RDWR + } + + flags_i |= (_O_APPEND * int(.Append in flags)) + flags_i |= (_O_CREAT * int(.Create in flags)) + flags_i |= (_O_EXCL * int(.Excl in flags)) + flags_i |= (_O_SYNC * int(.Sync in flags)) + flags_i |= (_O_TRUNC * int(.Trunc in flags)) + flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) + + handle_i := unix.sys_open(cstr, flags_i, int(perm)) + if handle_i < 0 { + return INVALID_HANDLE, _get_platform_error(handle_i) + } + + return Handle(handle_i), nil } _close :: proc(fd: Handle) -> Error { @@ -46,30 +98,27 @@ _read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { if len(p) == 0 { return 0, nil } - n = unix.sys_read(fd, &data[0], c.size_t(len(data))) + n = unix.sys_read(int(fd), &p[0], len(p)) if n < 0 { - return -1, unix.get_errno(n) + return -1, _get_platform_error(int(unix.get_errno(n))) } - return bytes_read, nil + return n, nil } _read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { return 0, .Invalid_Offset } - - curr_offset, err := _seek(fd, 0, .Current) - if err != nil { - return 0, err - } - defer _seek(fd, curr_offset, .Start) - _seek(fd, offset, .Start) - b := p + b, offset := p, offset for len(b) > 0 { - m := _read(fd, b) or_return + m := unix.sys_pread(int(fd), &b[0], len(b), offset) + if m < 0 { + return -1, _get_platform_error(m) + } n += m b = b[m:] + offset += i64(m) } return } @@ -83,7 +132,7 @@ _write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { if len(p) == 0 { return 0, nil } - n = unix.sys_write(fd, &p[0], uint(len(p))) + n = unix.sys_write(int(fd), &p[0], uint(len(p))) if n < 0 { return -1, _get_platform_error(n) } @@ -94,19 +143,16 @@ _write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { if offset < 0 { return 0, .Invalid_Offset } - - curr_offset, err := _seek(fd, 0, .Current) - if err != nil { - return 0, err - } - defer _seek(fd, curr_offset, .Start) - _seek(fd, offset, .Start) - b := p + b, offset := p, offset for len(b) > 0 { - m := _write(fd, b) or_return + m := unix.sys_pwrite(int(fd), &b[0], len(b), offset) + if m < 0 { + return -1, _get_platform_error(m) + } n += m b = b[m:] + offset += i64(m) } return } @@ -117,11 +163,12 @@ _write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { } _file_size :: proc(fd: Handle) -> (n: i64, err: Error) { - s, err := _fstat(fd) or_return - if err != nil { - return 0, err + s: OS_Stat = --- + res := unix.sys_fstat(int(fd), &s) + if res < 0 { + return -1, _get_platform_error(res) } - return max(s.size, 0), nil + return s.size, nil } _sync :: proc(fd: Handle) -> Error { @@ -137,17 +184,25 @@ _truncate :: proc(fd: Handle, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - if _is_dir(name) { - return _ok_or_error(unix.sys_rmdir(path_cstr)) + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + + handle_i := unix.sys_open(name_cstr, int(File_Flags.Read)) + if handle_i < 0 { + return _get_platform_error(handle_i) } - return _ok_or_error(unix.sys_unlink(path_cstr)) + defer unix.sys_close(handle_i) + + /* TODO: THIS WILL NOT WORK */ + if _is_dir(Handle(handle_i)) { + return _ok_or_error(unix.sys_rmdir(name_cstr)) + } + return _ok_or_error(unix.sys_unlink(name_cstr)) } -_rename :: proc(old_path, new_path: string) -> Error { - old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) - new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) - return _ok_or_error(unix.sys_rename(old_path_cstr, new_path_cstr)) +_rename :: proc(old_name, new_name: string) -> Error { + old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) + new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + return _ok_or_error(unix.sys_rename(old_name_cstr, new_name_cstr)) } _link :: proc(old_name, new_name: string) -> Error { @@ -163,16 +218,16 @@ _symlink :: proc(old_name, new_name: string) -> Error { } _read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { - path_cstr := strings.clone_to_cstring(path) - defer delete(path_cstr) + name_cstr := strings.clone_to_cstring(name) + defer delete(name_cstr) bufsz : uint = 256 buf := make([]byte, bufsz, allocator) for { - rc := unix.sys_readlink(path_cstr, &(buf[0]), bufsz) + rc := unix.sys_readlink(name_cstr, &(buf[0]), bufsz) if rc < 0 { delete(buf) - return "", unix.get_errno(rc) + return "", _get_platform_error(int(unix.get_errno(rc))) } else if rc == int(bufsz) { bufsz *= 2 delete(buf) @@ -183,9 +238,9 @@ _read_link :: proc(name: string, allocator := context.allocator) -> (string, Err } } -_unlink :: proc(path: string) -> Error { - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _ok_or_error(unix.sys_unlink(path_cstr)) +_unlink :: proc(name: string) -> Error { + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + return _ok_or_error(unix.sys_unlink(name_cstr)) } _chdir :: proc(fd: Handle) -> Error { @@ -193,18 +248,16 @@ _chdir :: proc(fd: Handle) -> Error { } _chmod :: proc(fd: Handle, mode: File_Mode) -> Error { - //TODO - return nil + return _ok_or_error(unix.sys_fchmod(int(fd), int(mode))) } _chown :: proc(fd: Handle, uid, gid: int) -> Error { - //TODO - return nil + return _ok_or_error(unix.sys_fchown(int(fd), uid, gid)) } _lchown :: proc(name: string, uid, gid: int) -> Error { - //TODO - return nil + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid)) } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { @@ -212,14 +265,14 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return nil } -_exists :: proc(path: string) -> bool { - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return unix.sys_access(path_cstr, F_OK) == 0 +_exists :: proc(name: string) -> bool { + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + return unix.sys_access(name_cstr, F_OK) == 0 } _is_file :: proc(fd: Handle) -> bool { s: OS_Stat - res := unix.sys_fstat(int(fd), rawptr(&s)) + res := unix.sys_fstat(int(fd), &s) if res < 0 { // error return false } @@ -228,7 +281,7 @@ _is_file :: proc(fd: Handle) -> bool { _is_dir :: proc(fd: Handle) -> bool { s: OS_Stat - res := unix.sys_fstat(int(fd), rawptr(&s)) + res := unix.sys_fstat(int(fd), &s) if res < 0 { // error return false } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 9fdbd9a5a..dd33d8a53 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -36,6 +36,11 @@ _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { unreachable() } +_opendir :: proc(path: string) -> (handle: Handle, err: Error) { + return INVALID_HANDLE, .Invalid_Argument +} + + _open :: proc(path: string, flags: File_Flags, perm: File_Mode) -> (handle: Handle, err: Error) { handle = INVALID_HANDLE if len(path) == 0 { diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin new file mode 100644 index 000000000..f617f8cc8 --- /dev/null +++ b/core/os/os2/heap_linux.odin @@ -0,0 +1,27 @@ +//+private +package os2 + +import "core:mem" + +heap_alloc :: proc(size: int) -> rawptr { + // TODO + return nil +} + +heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { + // TODO + return nil +} +heap_free :: proc(ptr: rawptr) { + if ptr == nil { + return + } + // TODO +} + +_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { + // TODO + return nil, nil +} diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin new file mode 100644 index 000000000..b45d6e976 --- /dev/null +++ b/core/os/os2/path_linux.odin @@ -0,0 +1,85 @@ +//+private +package os2 + +import "core:fmt" +import "core:strings" +import "core:sys/unix" +import "core:path/filepath" + +_Path_Separator :: '/' +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == '/' +} + +_mkdir :: proc(path: string, perm: File_Mode) -> Error { + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + //TODO file_mode + return _ok_or_error(unix.sys_mkdir(path_cstr)) +} + +_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { + _mkdir_all_stat :: proc(path: string, s: ^OS_Stat, perm: File_Mode) -> Error { + if len(path) == 0 { + return nil + } + + path := path[len(path)-1] == '/' ? path[:len(path)-1] : path + dir, _ := filepath.split(path) + + if len(dir) == 0 { + return _mkdir(path, perm) + } + + dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) + errno := int(unix.get_errno(unix.sys_stat(dir_cstr, s))) + switch errno { + case 0: + if !S_ISDIR(s.mode) { + return .Exist + } + return _mkdir(path, perm) + case ENOENT: + _mkdir_all_stat(dir, s, perm) or_return + return _mkdir(path, perm) + case: + return _get_platform_error(errno) + } + unreachable() + } + // OS_Stat is fat. Make one and re-use it. + s: OS_Stat = --- + return _mkdir_all_stat(path, &s, perm) +} + +_remove_all :: proc(path: string) -> Error { + // TODO + return nil +} + +_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + // NOTE(jason): Avoiding libc, so just use 4096 directly + PATH_MAX :: 4096 + buf := make([dynamic]u8, PATH_MAX, allocator) + for { + #no_bounds_check res := unix.sys_getcwd(&buf[0], uint(len(buf))) + + if res >= 0 { + return strings.string_from_nul_terminated_ptr(&buf[0], len(buf)), nil + } + if errno := int(unix.get_errno(res)); errno != ERANGE { + return "", _get_platform_error(errno) + } + resize(&buf, len(buf)+PATH_MAX) + } + unreachable() +} + +_setwd :: proc(dir: string) -> (err: Error) { + dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) + return _ok_or_error(unix.sys_chdir(dir_cstr)) +} diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin new file mode 100644 index 000000000..0699c5720 --- /dev/null +++ b/core/os/os2/pipe_linux.odin @@ -0,0 +1,7 @@ +//+private +package os2 + +_pipe :: proc() -> (r, w: Handle, err: Error) { + return INVALID_HANDLE, INVALID_HANDLE, nil +} + diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 7fce8fb9c..c4cc5fe8d 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -2,7 +2,9 @@ package os2 import "core:time" +import "core:strings" import "core:sys/unix" +import "core:path/filepath" // File type S_IFMT :: 0o170000 // Type of file mask @@ -51,6 +53,12 @@ X_OK :: 1 // Test for execute permission W_OK :: 2 // Test for write permission R_OK :: 4 // Test for read permission +@private +Unix_File_Time :: struct { + seconds: i64, + nanoseconds: i64, +} + @private OS_Stat :: struct { device_id: u64, // ID of device containing file @@ -75,19 +83,21 @@ OS_Stat :: struct { } _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { + return File_Info{}, nil } _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { + return File_Info{}, nil } _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - cstr := strings.clone_to_cstring(path) + cstr := strings.clone_to_cstring(name) defer delete(cstr) s: OS_Stat result := unix.sys_lstat(cstr, &s) if result < 0 { - return {}, unix.get_errno(result) + return {}, _get_platform_error(int(unix.get_errno(result))) } fi := File_Info { @@ -96,10 +106,10 @@ _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Erro size = s.size, mode = 0, is_dir = S_ISDIR(s.mode), - creation_time = nil, // linux does not track this + creation_time = time.Time{0}, // linux does not track this //TODO - modification_time = nil, - access_time = nil, + modification_time = time.Time{0}, + access_time = time.Time{0}, } return fi, nil @@ -110,7 +120,7 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { } _stat_internal :: proc(name: string) -> (s: OS_Stat, res: int) { - name_cstr = strings.clone_to_cstring(name, context.temp_allocator) + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) res = unix.sys_stat(name_cstr, &s) return } diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin new file mode 100644 index 000000000..d56bc34d3 --- /dev/null +++ b/core/os/os2/temp_file_linux.odin @@ -0,0 +1,18 @@ +//+private +package os2 + + +_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { + //TODO + return 0, nil +} + +_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { + //TODO + return "", nil +} + +_temp_dir :: proc(allocator := context.allocator) -> string { + //TODO + return "" +} diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 243f8accc..ccd8a75e6 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -15,7 +15,7 @@ import "core:intrinsics" // 386: arch/x86/entry/syscalls/sycall_32.tbl // arm: arch/arm/tools/syscall.tbl -when ODIN_ARCH == "amd64" { +when ODIN_ARCH == .amd64 { SYS_read : uintptr : 0 SYS_write : uintptr : 1 SYS_open : uintptr : 2 @@ -33,8 +33,8 @@ when ODIN_ARCH == "amd64" { SYS_rt_sigprocmask : uintptr : 14 SYS_rt_sigreturn : uintptr : 15 SYS_ioctl : uintptr : 16 - SYS_pread : uintptr : 17 - SYS_pwrite : uintptr : 18 + SYS_pread64 : uintptr : 17 + SYS_pwrite64 : uintptr : 18 SYS_readv : uintptr : 19 SYS_writev : uintptr : 20 SYS_access : uintptr : 21 @@ -374,7 +374,7 @@ when ODIN_ARCH == "amd64" { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when ODIN_ARCH == "arm64" { +} else when ODIN_ARCH == .arm64 { SYS_io_setup : uintptr : 0 SYS_io_destroy : uintptr : 1 SYS_io_submit : uintptr : 2 @@ -675,7 +675,7 @@ when ODIN_ARCH == "amd64" { SYS_landlock_create_ruleset : uintptr : 444 SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 -} else when ODIN_ARCH == "386" { +} else when ODIN_ARCH == .i386 { SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1112,7 +1112,7 @@ when ODIN_ARCH == "amd64" { SYS_landlock_add_rule : uintptr : 445 SYS_landlock_restrict_self : uintptr : 446 SYS_memfd_secret : uintptr : 447 -} else when ODIN_ARCH == "arm" { +} else when ODIN_ARCH == .arm { SYS_restart_syscall : uintptr : 0 SYS_exit : uintptr : 1 SYS_fork : uintptr : 2 @@ -1518,6 +1518,7 @@ when ODIN_ARCH == "amd64" { AT_FDCWD :: -100 AT_REMOVEDIR :: uintptr(0x200) +AT_SYMLINK_FOLLOW :: uintptr(0x400) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) sys_gettid :: proc "contextless" () -> int { @@ -1529,12 +1530,11 @@ sys_getrandom :: proc "contextless" (buf: ^byte, buflen: int, flags: uint) -> in } sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { - when ODIN_ARCH != "arm64" { - res := int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } else { // NOTE: arm64 does not have open - res := int(intrinsics.syscall(SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode)))) + return int(intrinsics.syscall(SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode)))) } - return -1 if res < 0 else res } sys_close :: proc(fd: int) -> int { @@ -1545,26 +1545,46 @@ sys_read :: proc(fd: int, buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_read, uintptr(fd), uintptr(buf), uintptr(size))) } +sys_pread :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) + } else { + low := uintptr(offset & 0xFFFFFFFF) + high := uintptr(offset >> 32) + return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), high, low)) + } +} + sys_write :: proc(fd: int, buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_write, uintptr(fd), uintptr(buf), uintptr(size))) } +sys_pwrite :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) + } else { + low := uintptr(offset & 0xFFFFFFFF) + high := uintptr(offset >> 32) + return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), high, low)) + } +} + sys_lseek :: proc(fd: int, offset: i64, whence: int) -> i64 { - when ODIN_ARCH == "amd64" || ODIN_ARCH == "arm64" { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence))) } else { low := uintptr(offset & 0xFFFFFFFF) high := uintptr(offset >> 32) result: i64 res := i64(intrinsics.syscall(SYS__llseek, uintptr(fd), high, low, &result, uintptr(whence))) - return -1 if res < 0 else result + return res if res < 0 else result } } sys_stat :: proc(path: cstring, stat: rawptr) -> int { - when ODIN_ARCH == "amd64" { + when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat))) - } else when ODIN_ARCH != "arm64" { + } else when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have stat return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), 0)) @@ -1572,7 +1592,7 @@ sys_stat :: proc(path: cstring, stat: rawptr) -> int { } sys_fstat :: proc(fd: int, stat: rawptr) -> int { - when ODIN_ARCH == "amd64" || ODIN_ARCH == "arm64" { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat))) } else { return int(intrinsics.syscall(SYS_fstat64, uintptr(fd), uintptr(stat))) @@ -1580,9 +1600,9 @@ sys_fstat :: proc(fd: int, stat: rawptr) -> int { } sys_lstat :: proc(path: cstring, stat: rawptr) -> int { - when ODIN_ARCH == "amd64" { + when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat))) - } else when ODIN_ARCH != "arm64" { + } else when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have any lstat return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) @@ -1590,15 +1610,23 @@ sys_lstat :: proc(path: cstring, stat: rawptr) -> int { } sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } else { // NOTE: arm64 does not have readlink return int(intrinsics.syscall(SYS_readlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } } +sys_symlink :: proc(old_name: cstring, new_name: cstring) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) + } else { // NOTE: arm64 does not have symlink + return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), uintptr(AT_FDCWD), uintptr(rawptr(new_name)))) + } +} + sys_access :: proc(path: cstring, mask: int) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) } else { // NOTE: arm64 does not have access return int(intrinsics.syscall(SYS_faccessat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mask))) @@ -1613,16 +1641,60 @@ sys_chdir :: proc(path: cstring) -> int { return int(intrinsics.syscall(SYS_chdir, uintptr(rawptr(path)))) } +sys_fchdir :: proc(fd: int) -> int { + return int(intrinsics.syscall(SYS_fchdir, uintptr(fd))) +} + +sys_chmod :: proc(path: cstring, mode: int) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode))) + } else { // NOTE: arm64 does not have chmod + return int(intrinsics.syscall(SYS_fchmodat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) + } +} + +sys_fchmod :: proc(fd: int, mode: int) -> int { + return int(intrinsics.syscall(SYS_fchmod, uintptr(fd), uintptr(mode))) +} + +sys_chown :: proc(path: cstring, user: int, group: int) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) + } else { // NOTE: arm64 does not have chown + return int(intrinsics.syscall(SYS_fchownat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(user), uintptr(group), 0)) + } +} + +sys_fchown :: proc(fd: int, user: int, group: int) -> int { + return int(intrinsics.syscall(SYS_fchown, uintptr(fd), uintptr(user), uintptr(group))) +} + +sys_lchown :: proc(path: cstring, user: int, group: int) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) + } else { // NOTE: arm64 does not have lchown + return int(intrinsics.syscall(SYS_fchownat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW)) + } +} + sys_rename :: proc(old, new: cstring) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) } else { // NOTE: arm64 does not have rename return int(intrinsics.syscall(SYS_renameat, uintptr(AT_FDCWD), uintptr(rawptr(old)), uintptr(rawptr(new)))) } } +sys_link :: proc(old_name: cstring, new_name: cstring) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) + } else { // NOTE: arm64 does not have link + return int(intrinsics.syscall(SYS_linkat, uintptr(AT_FDCWD), uintptr(rawptr(old_name)), uintptr(AT_FDCWD), uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW)) + } +} + sys_unlink :: proc(path: cstring) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have unlink return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path), 0))) @@ -1630,7 +1702,7 @@ sys_unlink :: proc(path: cstring) -> int { } sys_rmdir :: proc(path: cstring) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have rmdir return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), AT_REMOVEDIR)) @@ -1638,14 +1710,40 @@ sys_rmdir :: proc(path: cstring) -> int { } sys_mkdir :: proc(path: cstring, mode: u32 = 0o775) -> int { - when ODIN_ARCH != "arm64" { + when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir return int(intrinsics.syscall(SYS_mkdirat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) } } -//TODO: ftruncate, symlink, readlink, fchdir, fchmod, chown, fchown, lchown +sys_truncate :: proc(path: cstring, length: i64) -> int { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) + } else { + low := uintptr(length & 0xFFFFFFFF) + high := uintptr(length >> 32) + return int(intrinsics.syscall(SYS_truncate64, uintptr(rawptr(path)), high, low)) + } +} + +sys_ftruncate :: proc(fd: int, length: i64) -> int { + when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { + return int(intrinsics.syscall(SYS_ftruncate, uintptr(fd), uintptr(length))) + } else { + low := uintptr(length & 0xFFFFFFFF) + high := uintptr(length >> 32) + return int(intrinsics.syscall(SYS_ftruncate64, uintptr(fd), high, low)) + } +} + +sys_fsync :: proc(fd: int) -> int { + return int(intrinsics.syscall(SYS_fsync, uintptr(fd))) +} + +sys_getdents64 :: proc(fd: int, dirent: rawptr, count: int) -> int { + return int(intrinsics.syscall(SYS_getdents64, uintptr(fd), uintptr(dirent), uintptr(count))) +} get_errno :: proc(res: int) -> i32 { if res < 0 && res > -4096 { From 1f19610fd67b00b49cc9d726af2e8d9ac5f4807b Mon Sep 17 00:00:00 2001 From: jasonkercher Date: Mon, 7 Mar 2022 17:16:03 -0500 Subject: [PATCH 006/254] added _remove_all --- core/os/os2/file_linux.odin | 44 ++++++------- core/os/os2/path_linux.odin | 101 ++++++++++++++++++++++++++++-- core/sys/unix/syscalls_linux.odin | 8 +++ 3 files changed, 126 insertions(+), 27 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 72fbdcb56..a88515b0e 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -25,23 +25,23 @@ _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { unreachable() } -_O_RDONLY :: 0o0 -_O_WRONLY :: 0o1 -_O_RDWR :: 0o2 -_O_CREAT :: 0o100 -_O_EXCL :: 0o200 -_O_TRUNC :: 0o1000 -_O_APPEND :: 0o2000 -_O_NONBLOCK :: 0o4000 -_O_LARGEFILE :: 0o100000 -_O_DIRECTORY :: 0o200000 -_O_SYNC :: 0o4010000 -_O_CLOEXEC :: 0o2000000 +__O_RDONLY :: 0o0 +__O_WRONLY :: 0o1 +__O_RDWR :: 0o2 +__O_CREAT :: 0o100 +__O_EXCL :: 0o200 +__O_TRUNC :: 0o1000 +__O_APPEND :: 0o2000 +__O_NONBLOCK :: 0o4000 +__O_LARGEFILE :: 0o100000 +__O_DIRECTORY :: 0o200000 +__O_SYNC :: 0o4010000 +__O_CLOEXEC :: 0o2000000 _opendir :: proc(name: string) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) - flags := _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC + flags := __O_RDONLY|__O_NONBLOCK|__O_DIRECTORY|__O_LARGEFILE|__O_CLOEXEC handle_i := unix.sys_open(cstr, flags) if handle_i < 0 { @@ -56,17 +56,17 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Erro flags_i: int switch flags & O_RDONLY|O_WRONLY|O_RDWR { - case O_RDONLY: flags_i = _O_RDONLY - case O_WRONLY: flags_i = _O_WRONLY - case O_RDWR: flags_i = _O_RDWR + case O_RDONLY: flags_i = __O_RDONLY + case O_WRONLY: flags_i = __O_WRONLY + case O_RDWR: flags_i = __O_RDWR } - flags_i |= (_O_APPEND * int(.Append in flags)) - flags_i |= (_O_CREAT * int(.Create in flags)) - flags_i |= (_O_EXCL * int(.Excl in flags)) - flags_i |= (_O_SYNC * int(.Sync in flags)) - flags_i |= (_O_TRUNC * int(.Trunc in flags)) - flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) + flags_i |= (__O_APPEND * int(.Append in flags)) + flags_i |= (__O_CREAT * int(.Create in flags)) + flags_i |= (__O_EXCL * int(.Excl in flags)) + flags_i |= (__O_SYNC * int(.Sync in flags)) + flags_i |= (__O_TRUNC * int(.Trunc in flags)) + flags_i |= (__O_CLOEXEC * int(.Close_On_Exec in flags)) handle_i := unix.sys_open(cstr, flags_i, int(perm)) if handle_i < 0 { diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index b45d6e976..31abf5bf8 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,7 +1,6 @@ //+private package os2 -import "core:fmt" import "core:strings" import "core:sys/unix" import "core:path/filepath" @@ -9,6 +8,8 @@ import "core:path/filepath" _Path_Separator :: '/' _Path_List_Separator :: ':' +DIRECTORY_FLAGS :: __O_RDONLY|__O_NONBLOCK|__O_DIRECTORY|__O_LARGEFILE|__O_CLOEXEC + _is_path_separator :: proc(c: byte) -> bool { return c == '/' } @@ -53,9 +54,99 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { return _mkdir_all_stat(path, &s, perm) } +dirent64 :: struct { + d_ino: u64, + d_off: u64, + d_reclen: u16, + d_type: u8, + d_name: [1]u8, +} + +DT_UNKNOWN :: 0 +DT_FIFO :: 1 +DT_CHR :: 2 +DT_DIR :: 4 +DT_BLK :: 6 +DT_REG :: 8 +DT_LNK :: 10 +DT_SOCK :: 12 +DT_WHT :: 14 + _remove_all :: proc(path: string) -> Error { - // TODO - return nil + _remove_all_dir :: proc(dfd: Handle) -> Error { + n := 64 + buf := make([]u8, n) + defer delete(buf) + + loop: for { + res := unix.sys_getdents64(int(dfd), &buf[0], n) + switch res { + case -22: //-EINVAL + n *= 2 + buf = make([]u8, n) + continue loop + case -4096..<0: + return _get_platform_error(res) + case 0: + break loop + } + + d: ^dirent64 + + for i := 0; i < res; i += int(d.d_reclen) { + description: string + d = (^dirent64)(rawptr(&buf[i])) + d_name_cstr := cstring(&d.d_name[0]) + + buf_len := uintptr(d.d_reclen) - offset_of(d.d_name) + + /* check for current directory (.) */ + #no_bounds_check if buf_len > 1 && d.d_name[0] == '.' && d.d_name[1] == 0 { + continue + } + + /* check for parent directory (..) */ + #no_bounds_check if buf_len > 2 && d.d_name[0] == '.' && d.d_name[1] == '.' && d.d_name[2] == 0 { + continue + } + + res: int + + switch d.d_type { + case DT_DIR: + handle_i := unix.sys_openat(int(dfd), d_name_cstr, DIRECTORY_FLAGS) + if handle_i < 0 { + return _get_platform_error(handle_i) + } + defer unix.sys_close(handle_i) + _remove_all_dir(Handle(handle_i)) or_return + res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR)) + case: + res = unix.sys_unlinkat(int(dfd), d_name_cstr) + } + + if res < 0 { + return _get_platform_error(res) + } + } + } + return nil + } + + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + handle_i := unix.sys_open(cstr, DIRECTORY_FLAGS) + switch handle_i { + case -ENOTDIR: + return _ok_or_error(unix.sys_unlink(cstr)) + case -4096..<0: + return _get_platform_error(handle_i) + } + + fd := Handle(handle_i) + defer close(fd) + _remove_all_dir(fd) or_return + return _ok_or_error(unix.sys_rmdir(cstr)) } _getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { @@ -71,8 +162,8 @@ _getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { if res >= 0 { return strings.string_from_nul_terminated_ptr(&buf[0], len(buf)), nil } - if errno := int(unix.get_errno(res)); errno != ERANGE { - return "", _get_platform_error(errno) + if res != -ERANGE { + return "", _get_platform_error(res) } resize(&buf, len(buf)+PATH_MAX) } diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index ccd8a75e6..889dd3b90 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1537,6 +1537,10 @@ sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { } } +sys_openat :: proc(dfd: int, path: cstring, flags: int, mode: int = 0o000) -> int { + return int(intrinsics.syscall(SYS_openat, uintptr(dfd), uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) +} + sys_close :: proc(fd: int) -> int { return int(intrinsics.syscall(SYS_close, uintptr(fd))) } @@ -1701,6 +1705,10 @@ sys_unlink :: proc(path: cstring) -> int { } } +sys_unlinkat :: proc(dfd: int, path: cstring, flag: int = 0) -> int { + return int(intrinsics.syscall(SYS_unlinkat, uintptr(dfd), uintptr(rawptr(path)), flag)) +} + sys_rmdir :: proc(path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) From 832003dd4b20b3581e22f57ffef72e67d6cb7146 Mon Sep 17 00:00:00 2001 From: CiD- Date: Tue, 8 Mar 2022 17:15:45 -0500 Subject: [PATCH 007/254] os2 tests --- core/os/os2/errors_linux.odin | 11 +++++++++++ core/os/os2/file.odin | 4 ---- core/os/os2/file_linux.odin | 29 +---------------------------- tests/core/Makefile | 7 +++++-- 4 files changed, 17 insertions(+), 34 deletions(-) diff --git a/core/os/os2/errors_linux.odin b/core/os/os2/errors_linux.odin index f074c7c86..d9056bd6b 100644 --- a/core/os/os2/errors_linux.odin +++ b/core/os/os2/errors_linux.odin @@ -1,6 +1,8 @@ //+private package os2 +import "core:sys/unix" + EPERM :: 1 ENOENT :: 2 ESRCH :: 3 @@ -126,6 +128,15 @@ ENOTRECOVERABLE:: 131 /* State not recoverable */ ERFKILL :: 132 /* Operation not possible due to RF-kill */ EHWPOISON :: 133 /* Memory page has hardware error */ +_get_platform_error :: proc(res: int) -> Error { + errno := unix.get_errno(res) + return Platform_Error{i32(errno)} +} + +_ok_or_error :: proc(res: int) -> Error { + return res >= 0 ? nil : _get_platform_error(res) +} + _error_string :: proc(errno: i32) -> string { if errno == 0 { return "" diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 09e1e8daf..707df37a2 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -61,10 +61,6 @@ create :: proc(name: string, perm: File_Mode = 0) -> (Handle, Error) { return open(name, {.Read, .Write, .Create}, perm) } -opendir :: proc(name: string) -> (Handle, Error) { - return _opendir(name) -} - open :: proc(name: string, flags := File_Flags{.Read}, perm: File_Mode = 0) -> (Handle, Error) { flags := flags if .Write not_in flags { diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index a88515b0e..db0e2efa8 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -7,22 +7,8 @@ import "core:strings" import "core:sys/unix" -_get_platform_error :: proc(res: int) -> Error { - errno := unix.get_errno(res) - return Platform_Error{i32(errno)} -} - -_ok_or_error :: proc(res: int) -> Error { - return res >= 0 ? nil : _get_platform_error(res) -} - _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { - switch kind { - case .stdin: return Handle(0) - case .stdout: return Handle(1) - case .stderr: return Handle(2) - } - unreachable() + return Handle(kind) } __O_RDONLY :: 0o0 @@ -38,19 +24,6 @@ __O_DIRECTORY :: 0o200000 __O_SYNC :: 0o4010000 __O_CLOEXEC :: 0o2000000 -_opendir :: proc(name: string) -> (Handle, Error) { - cstr := strings.clone_to_cstring(name, context.temp_allocator) - - flags := __O_RDONLY|__O_NONBLOCK|__O_DIRECTORY|__O_LARGEFILE|__O_CLOEXEC - - handle_i := unix.sys_open(cstr, flags) - if handle_i < 0 { - return INVALID_HANDLE, _get_platform_error(handle_i) - } - - return Handle(handle_i), nil -} - _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) diff --git a/tests/core/Makefile b/tests/core/Makefile index 1c2cee6bd..0c3e1e09a 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,7 +1,7 @@ ODIN=../../odin PYTHON=$(shell which python3) -all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test +all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test os2_test download_test_assets: $(PYTHON) download_assets.py @@ -22,4 +22,7 @@ crypto_test: $(ODIN) run crypto -out=crypto_hash -o:speed -no-bounds-check noise_test: - $(ODIN) run math/noise -out=test_noise \ No newline at end of file + $(ODIN) run math/noise -out=test_noise + +os2_test: + $(ODIN) run os2/test_os2.odin -out=test_os2 From bad295cf695e623591f737fb68556ea0a76a53ce Mon Sep 17 00:00:00 2001 From: CiD- Date: Thu, 10 Mar 2022 09:23:33 -0500 Subject: [PATCH 008/254] add test directory... --- tests/core/os2/test_os2.odin | 170 +++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 tests/core/os2/test_os2.odin diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin new file mode 100644 index 000000000..7fa0dd20d --- /dev/null +++ b/tests/core/os2/test_os2.odin @@ -0,0 +1,170 @@ +package test_os2 + +import "core:fmt" +import "core:os/os2" +import "core:sys/unix" +import "core:testing" +import "core:intrinsics" + +TEST_count := 0 +TEST_fail := 0 + +when ODIN_TEST { + expect :: testing.expect + log :: testing.log +} else { + expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) where intrinsics.type_is_comparable(T) { + fmt.printf("[%v] ", loc) + TEST_count += 1 + ok := value == expected + if !ok { + fmt.printf("expected %v, got %v", expected, value) + TEST_fail += 1 + return + } + fmt.println(" PASS") + } + + expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { + fmt.printf("[%v] ", loc) + TEST_count += 1 + if !condition { + TEST_fail += 1 + fmt.println(message) + return + } + fmt.println(" PASS") + } + log :: proc(t: ^testing.T, v: any, loc := #caller_location) { + fmt.printf("[%v] ", loc) + fmt.printf("log: %v\n", v) + } +} + +main :: proc() +{ + t: testing.T + file_test(&t) + path_test(&t) + fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) +} + +@private +_expect_no_error :: proc(t: ^testing.T, e: os2.Error, loc := #caller_location) { + expect(t, e == nil, "unexpected error", loc) +} + + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +@test +file_test :: proc(t: ^testing.T) { + + /* Things to test: + * std_handle,create,open,close,name,seek,read,read_at,read_from,write,write_at, + * write_to,file_size,sync,flush,truncate,remove,rename,link,symlink,read_link, + * unlink,chdir,chmod,chown,lchown,chtimes,exists,is_file,is_dir + */ + + stdin := os2.std_handle(.stdin) + expect_value(t, stdin, 0) + stdout := os2.std_handle(.stdout) + expect_value(t, stdout, 1) + stderr := os2.std_handle(.stderr) + expect_value(t, stderr, 2) + + fd, err := os2.open("filethatdoesntexist.txt") + expect(t, err != nil, "missing error") + expect_value(t, fd, os2.INVALID_HANDLE) + + fd, err = os2.open("write.txt", {.Write, .Create, .Trunc}, 0o664) + _expect_no_error(t, err) + expect(t, fd != os2.INVALID_HANDLE, "unexpected handle") + + s1 := "hello" + b1 := transmute([]u8)s1 + + n: int + n, err = os2.write_at(fd, b1, 10) + _expect_no_error(t, err) + expect_value(t, n, 5) + + s2 := "abcdefghij" + b2 := transmute([]u8)s2 + + n, err = os2.write(fd, b2) + _expect_no_error(t, err) + expect_value(t, n, 10) + + _expect_no_error(t, os2.sync(fd)) + _expect_no_error(t, os2.close(fd)) + + fd, err = os2.open("write.txt") + _expect_no_error(t, err) + + buf: [32]u8 + + n, err = os2.read(fd, buf[:]) + _expect_no_error(t, err) + expect_value(t, n, 15) + expect_value(t, string(buf[:n]), "abcdefghijhello") + + n, err = os2.read_at(fd, buf[0:2], 1) + _expect_no_error(t, err) + expect_value(t, n, 2) + expect_value(t, string(buf[0:2]), "bc") + + _expect_no_error(t, os2.close(fd)) +} + +@test +path_test :: proc(t: ^testing.T) { + err: os2.Error + if os2.exists("a") { + err = os2.remove_all("a") + _expect_no_error(t, err) + } + + err = os2.mkdir_all("a/b/c/d", 0) + _expect_no_error(t, err) + + expect(t, os2.exists("a"), "directory does not exist") + + fd: os2.Handle + fd, err = os2.create("a/b/c/file.txt", 0o644) + _expect_no_error(t, err) + + err = os2.close(fd) + _expect_no_error(t, err) + + expect(t, unix.sys_access("a/b/c/file.txt", X_OK) < 0, "unexpected exec permission") + + err = os2.rename("a/b/c/file.txt", "a/b/file.txt") + _expect_no_error(t, err) + + expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected exec permission") + + err = os2.symlink("b/c/d", "a/symlink_to_d") + _expect_no_error(t, err) + + symlink: string + symlink, err = os2.read_link("a/symlink_to_d") + _expect_no_error(t, err) + expect_value(t, symlink, "b/c/d") + + fd, err = os2.create("a/symlink_to_d/shnt.txt", 0o744) + _expect_no_error(t, err) + + err = os2.close(fd) + _expect_no_error(t, err) + + expect_value(t, unix.sys_access("a/b/c/d/shnt.txt", X_OK | R_OK | W_OK), 0) + + err = os2.remove_all("a") + _expect_no_error(t, err) + + expect(t, !os2.exists("a"), "directory a exists") +} From 0b61215f7bf056e3ae4b5619542481c2a1b3fdc0 Mon Sep 17 00:00:00 2001 From: Jason Kercher Date: Thu, 10 Mar 2022 11:12:06 -0500 Subject: [PATCH 009/254] getting tests to run --- core/os/os2/env_windows.odin | 4 ++-- core/os/os2/file_windows.odin | 5 ----- tests/core/Makefile | 32 ++++++++++++++++++++++++++++++++ tests/core/build.bat | 7 ++++++- tests/core/os2/test_os2.odin | 18 ++++++++++++++---- 5 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 tests/core/Makefile diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index a3b97375b..1e1ffba4d 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -1,8 +1,8 @@ //+private package os2 -import "core:runtime" -import "core:mem" +//import "core:runtime" +//import "core:mem" import win32 "core:sys/windows" _get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index dd33d8a53..9fdbd9a5a 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -36,11 +36,6 @@ _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { unreachable() } -_opendir :: proc(path: string) -> (handle: Handle, err: Error) { - return INVALID_HANDLE, .Invalid_Argument -} - - _open :: proc(path: string, flags: File_Flags, perm: File_Mode) -> (handle: Handle, err: Error) { handle = INVALID_HANDLE if len(path) == 0 { diff --git a/tests/core/Makefile b/tests/core/Makefile new file mode 100644 index 000000000..82bcae068 --- /dev/null +++ b/tests/core/Makefile @@ -0,0 +1,32 @@ +ODIN=../../odin +PYTHON=$(shell which python3) + +all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test os2_test + +download_test_assets: + $(PYTHON) download_assets.py + +image_test: + $(ODIN) run image/test_core_image.odin + +compress_test: + $(ODIN) run compress/test_core_compress.odin + +strings_test: + $(ODIN) run strings/test_core_strings.odin + +hash_test: + $(ODIN) run hash -out=test_hash -o:speed -no-bounds-check + +crypto_test: + $(ODIN) run crypto -out=crypto_hash -o:speed -no-bounds-check + +noise_test: + $(ODIN) run math/noise -out=test_noise + +os2_test: + $(ODIN) run os2/test_os2.odin -out=test_os2 + +encoding_test: + $(ODIN) run encoding/json -out=test_json + $(ODIN) run encoding/varint -out=test_varint diff --git a/tests/core/build.bat b/tests/core/build.bat index 0227ac6bb..8cf6486d3 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -41,4 +41,9 @@ echo --- echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% \ No newline at end of file +%PATH_TO_ODIN% run math/noise %COMMON% + +echo --- +echo Running core:os/os2 tests +echo --- +%PATH_TO_ODIN% run os2 %COMMON% diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index 7fa0dd20d..f8ef133a5 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -2,10 +2,14 @@ package test_os2 import "core:fmt" import "core:os/os2" -import "core:sys/unix" import "core:testing" import "core:intrinsics" +// really only want sys_access for more finite testing +when ODIN_OS == .Linux { + import "core:sys/unix" +} + TEST_count := 0 TEST_fail := 0 @@ -140,12 +144,16 @@ path_test :: proc(t: ^testing.T) { err = os2.close(fd) _expect_no_error(t, err) - expect(t, unix.sys_access("a/b/c/file.txt", X_OK) < 0, "unexpected exec permission") + when ODIN_OS == .Linux { + expect(t, unix.sys_access("a/b/c/file.txt", X_OK) < 0, "unexpected exec permission") + } err = os2.rename("a/b/c/file.txt", "a/b/file.txt") _expect_no_error(t, err) - expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected exec permission") + when ODIN_OS == .Linux { + expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected exec permission") + } err = os2.symlink("b/c/d", "a/symlink_to_d") _expect_no_error(t, err) @@ -161,7 +169,9 @@ path_test :: proc(t: ^testing.T) { err = os2.close(fd) _expect_no_error(t, err) - expect_value(t, unix.sys_access("a/b/c/d/shnt.txt", X_OK | R_OK | W_OK), 0) + when ODIN_OS == .Linux { + expect_value(t, unix.sys_access("a/b/c/d/shnt.txt", X_OK | R_OK | W_OK), 0) + } err = os2.remove_all("a") _expect_no_error(t, err) From e008b5a1602d669c865ac3ad42bc6f65b34d8012 Mon Sep 17 00:00:00 2001 From: "U-JSM\\jkercher" Date: Fri, 11 Mar 2022 10:47:59 -0500 Subject: [PATCH 010/254] build os2 test on windows --- tests/core/build.bat | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/core/build.bat b/tests/core/build.bat index 8cf6486d3..27b444e44 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -46,4 +46,5 @@ echo --- echo --- echo Running core:os/os2 tests echo --- -%PATH_TO_ODIN% run os2 %COMMON% +Rem Needed Shlwapi.lib for PathFileExistsW +%PATH_TO_ODIN% run os2 %COMMON% -extra-linker-flags:Shlwapi.lib From c293e88f2e31bfed896ddba701bdc2629497005a Mon Sep 17 00:00:00 2001 From: CiD- Date: Mon, 14 Mar 2022 13:34:06 -0400 Subject: [PATCH 011/254] commit to merge upstream/master --- core/os/os2/file_linux.odin | 43 +++++++------ core/os/os2/path_linux.odin | 72 ++++++++-------------- core/sys/unix/syscalls_linux.odin | 10 +++- tests/core/crypto_hash | Bin 621648 -> 0 bytes tests/core/os2/test_os2.odin | 96 +++++++++++++++++++++++------- 5 files changed, 129 insertions(+), 92 deletions(-) delete mode 100644 tests/core/crypto_hash diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index db0e2efa8..9030d265d 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -11,35 +11,35 @@ _std_handle :: proc(kind: Std_Handle_Kind) -> Handle { return Handle(kind) } -__O_RDONLY :: 0o0 -__O_WRONLY :: 0o1 -__O_RDWR :: 0o2 -__O_CREAT :: 0o100 -__O_EXCL :: 0o200 -__O_TRUNC :: 0o1000 -__O_APPEND :: 0o2000 -__O_NONBLOCK :: 0o4000 -__O_LARGEFILE :: 0o100000 -__O_DIRECTORY :: 0o200000 -__O_SYNC :: 0o4010000 -__O_CLOEXEC :: 0o2000000 +_O_RDONLY :: 0o0 +_O_WRONLY :: 0o1 +_O_RDWR :: 0o2 +_O_CREAT :: 0o100 +_O_EXCL :: 0o200 +_O_TRUNC :: 0o1000 +_O_APPEND :: 0o2000 +_O_NONBLOCK :: 0o4000 +_O_LARGEFILE :: 0o100000 +_O_DIRECTORY :: 0o200000 +_O_SYNC :: 0o4010000 +_O_CLOEXEC :: 0o2000000 _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) flags_i: int switch flags & O_RDONLY|O_WRONLY|O_RDWR { - case O_RDONLY: flags_i = __O_RDONLY - case O_WRONLY: flags_i = __O_WRONLY - case O_RDWR: flags_i = __O_RDWR + case O_RDONLY: flags_i = _O_RDONLY + case O_WRONLY: flags_i = _O_WRONLY + case O_RDWR: flags_i = _O_RDWR } - flags_i |= (__O_APPEND * int(.Append in flags)) - flags_i |= (__O_CREAT * int(.Create in flags)) - flags_i |= (__O_EXCL * int(.Excl in flags)) - flags_i |= (__O_SYNC * int(.Sync in flags)) - flags_i |= (__O_TRUNC * int(.Trunc in flags)) - flags_i |= (__O_CLOEXEC * int(.Close_On_Exec in flags)) + flags_i |= (_O_APPEND * int(.Append in flags)) + flags_i |= (_O_CREAT * int(.Create in flags)) + flags_i |= (_O_EXCL * int(.Excl in flags)) + flags_i |= (_O_SYNC * int(.Sync in flags)) + flags_i |= (_O_TRUNC * int(.Trunc in flags)) + flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) handle_i := unix.sys_open(cstr, flags_i, int(perm)) if handle_i < 0 { @@ -165,7 +165,6 @@ _remove :: proc(name: string) -> Error { } defer unix.sys_close(handle_i) - /* TODO: THIS WILL NOT WORK */ if _is_dir(Handle(handle_i)) { return _ok_or_error(unix.sys_rmdir(name_cstr)) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 31abf5bf8..b474ae207 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -8,7 +8,16 @@ import "core:path/filepath" _Path_Separator :: '/' _Path_List_Separator :: ':' -DIRECTORY_FLAGS :: __O_RDONLY|__O_NONBLOCK|__O_DIRECTORY|__O_LARGEFILE|__O_CLOEXEC +_S_IFMT :: 0o170000 // Type of file mask +_S_IFIFO :: 0o010000 // Named pipe (fifo) +_S_IFCHR :: 0o020000 // Character special +_S_IFDIR :: 0o040000 // Directory +_S_IFBLK :: 0o060000 // Block special +_S_IFREG :: 0o100000 // Regular +_S_IFLNK :: 0o120000 // Symbolic link +_S_IFSOCK :: 0o140000 // Socket + +_OPENDIR_FLAGS :: _O_RDONLY|_O_NONBLOCK|_O_DIRECTORY|_O_LARGEFILE|_O_CLOEXEC _is_path_separator :: proc(c: byte) -> bool { return c == '/' @@ -16,42 +25,17 @@ _is_path_separator :: proc(c: byte) -> bool { _mkdir :: proc(path: string, perm: File_Mode) -> Error { path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - //TODO file_mode - return _ok_or_error(unix.sys_mkdir(path_cstr)) + perm_i: int + if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { + return .Invalid_Argument + } + + return _ok_or_error(unix.sys_mkdir(path_cstr, int(perm & 0o777))) } +// TODO _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - _mkdir_all_stat :: proc(path: string, s: ^OS_Stat, perm: File_Mode) -> Error { - if len(path) == 0 { - return nil - } - - path := path[len(path)-1] == '/' ? path[:len(path)-1] : path - dir, _ := filepath.split(path) - - if len(dir) == 0 { - return _mkdir(path, perm) - } - - dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) - errno := int(unix.get_errno(unix.sys_stat(dir_cstr, s))) - switch errno { - case 0: - if !S_ISDIR(s.mode) { - return .Exist - } - return _mkdir(path, perm) - case ENOENT: - _mkdir_all_stat(dir, s, perm) or_return - return _mkdir(path, perm) - case: - return _get_platform_error(errno) - } - unreachable() - } - // OS_Stat is fat. Make one and re-use it. - s: OS_Stat = --- - return _mkdir_all_stat(path, &s, perm) + return nil } dirent64 :: struct { @@ -62,17 +46,9 @@ dirent64 :: struct { d_name: [1]u8, } -DT_UNKNOWN :: 0 -DT_FIFO :: 1 -DT_CHR :: 2 -DT_DIR :: 4 -DT_BLK :: 6 -DT_REG :: 8 -DT_LNK :: 10 -DT_SOCK :: 12 -DT_WHT :: 14 - _remove_all :: proc(path: string) -> Error { + DT_DIR :: 4 + _remove_all_dir :: proc(dfd: Handle) -> Error { n := 64 buf := make([]u8, n) @@ -114,7 +90,7 @@ _remove_all :: proc(path: string) -> Error { switch d.d_type { case DT_DIR: - handle_i := unix.sys_openat(int(dfd), d_name_cstr, DIRECTORY_FLAGS) + handle_i := unix.sys_openat(int(dfd), d_name_cstr, _OPENDIR_FLAGS) if handle_i < 0 { return _get_platform_error(handle_i) } @@ -135,7 +111,7 @@ _remove_all :: proc(path: string) -> Error { cstr := strings.clone_to_cstring(path, context.temp_allocator) - handle_i := unix.sys_open(cstr, DIRECTORY_FLAGS) + handle_i := unix.sys_open(cstr, _OPENDIR_FLAGS) switch handle_i { case -ENOTDIR: return _ok_or_error(unix.sys_unlink(cstr)) @@ -149,7 +125,7 @@ _remove_all :: proc(path: string) -> Error { return _ok_or_error(unix.sys_rmdir(cstr)) } -_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { +_getwd :: proc(allocator := context.allocator) -> (string, Error) { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -170,7 +146,7 @@ _getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { unreachable() } -_setwd :: proc(dir: string) -> (err: Error) { +_setwd :: proc(dir: string) -> Error { dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) return _ok_or_error(unix.sys_chdir(dir_cstr)) } diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 889dd3b90..926e69691 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1717,7 +1717,7 @@ sys_rmdir :: proc(path: cstring) -> int { } } -sys_mkdir :: proc(path: cstring, mode: u32 = 0o775) -> int { +sys_mkdir :: proc(path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir @@ -1725,6 +1725,14 @@ sys_mkdir :: proc(path: cstring, mode: u32 = 0o775) -> int { } } +sys_mknod :: proc(path: cstring, mode: int, dev: int) -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) + } else { // NOTE: arm64 does not have mknod + return int(intrinsics.syscall(SYS_mknodat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) + } +} + sys_truncate :: proc(path: cstring, length: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) diff --git a/tests/core/crypto_hash b/tests/core/crypto_hash deleted file mode 100644 index 18b85a1e8ed34fa9fa8659c7e2841b67c9b476c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 621648 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
ENDSC_# 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

QPAbe;-=?`$DV#1JLS! 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 diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index f8ef133a5..a629063bd 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -1,5 +1,6 @@ package test_os2 +import "core:os" import "core:fmt" import "core:os/os2" import "core:testing" @@ -22,7 +23,7 @@ when ODIN_TEST { TEST_count += 1 ok := value == expected if !ok { - fmt.printf("expected %v, got %v", expected, value) + fmt.printf("expected %v, got %v\n", expected, value) TEST_fail += 1 return } @@ -45,12 +46,13 @@ when ODIN_TEST { } } -main :: proc() +main :: proc() { t: testing.T file_test(&t) path_test(&t) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + os.exit(TEST_fail > 0 ? 1 : 0) } @private @@ -59,7 +61,7 @@ _expect_no_error :: proc(t: ^testing.T, e: os2.Error, loc := #caller_location) { } -F_OK :: 0 // Test for file existance +F_OK :: 0 // Test for file existence X_OK :: 1 // Test for execute permission W_OK :: 2 // Test for write permission R_OK :: 4 // Test for read permission @@ -84,44 +86,92 @@ file_test :: proc(t: ^testing.T) { expect(t, err != nil, "missing error") expect_value(t, fd, os2.INVALID_HANDLE) - fd, err = os2.open("write.txt", {.Write, .Create, .Trunc}, 0o664) + // NOTE: no executable permissions here + fd, err = os2.open("file.txt", {.Write, .Create, .Trunc}, 0o664) _expect_no_error(t, err) expect(t, fd != os2.INVALID_HANDLE, "unexpected handle") - s1 := "hello" - b1 := transmute([]u8)s1 - + s := "hello" n: int - n, err = os2.write_at(fd, b1, 10) + n, err = os2.write_at(fd, transmute([]u8)s, 10) _expect_no_error(t, err) expect_value(t, n, 5) - s2 := "abcdefghij" - b2 := transmute([]u8)s2 - - n, err = os2.write(fd, b2) + s = "abcdefghij" + n, err = os2.write(fd, transmute([]u8)s) _expect_no_error(t, err) expect_value(t, n, 10) + // seek to the "ll" in "hello" + n64: i64 + n64, err = os2.seek(fd, 12, .Start) + _expect_no_error(t, err) + expect_value(t, n64, 12) + + s = "11" + n, err = os2.write(fd, transmute([]u8)s) + _expect_no_error(t, err) + expect_value(t, n, 2) + + // seek to the "e" in "he11o" + n64, err = os2.seek(fd, -3, .Current) + _expect_no_error(t, err) + expect_value(t, n64, 11) + + s = "3" + n, err = os2.write(fd, transmute([]u8)s) + _expect_no_error(t, err) + expect_value(t, n, 1) + + // seek to the "o" in "h311o" + n64, err = os2.seek(fd, -1, .End) + _expect_no_error(t, err) + expect_value(t, n64, 14) + + s = "0" + n, err = os2.write(fd, transmute([]u8)s) + _expect_no_error(t, err) + expect_value(t, n, 1) + _expect_no_error(t, os2.sync(fd)) + + // Add executable permissions to current file (as well as read/write to all) + err = os2.chmod(fd, 0o766) + _expect_no_error(t, err) + + when ODIN_OS == .Linux { + expect(t, unix.sys_access("file.txt", X_OK) == 0, "expected exec permission") + } + + // NOTE: chown not possible without root user + //_expect_no_error(t, os2.chown(fd, 0, 0)) _expect_no_error(t, os2.close(fd)) - fd, err = os2.open("write.txt") + + fd, err = os2.open("file.txt") _expect_no_error(t, err) buf: [32]u8 - + n, err = os2.read(fd, buf[:]) _expect_no_error(t, err) expect_value(t, n, 15) - expect_value(t, string(buf[:n]), "abcdefghijhello") + expect_value(t, string(buf[:n]), "abcdefghijh3110") n, err = os2.read_at(fd, buf[0:2], 1) _expect_no_error(t, err) expect_value(t, n, 2) expect_value(t, string(buf[0:2]), "bc") + n64, err = os2.file_size(fd) + _expect_no_error(t, err) + expect_value(t, n64, 15) + _expect_no_error(t, os2.close(fd)) + + _expect_no_error(t, os2.remove("file.txt")) + _expect_no_error(t, os2.mkdir("empty dir", 0)) + _expect_no_error(t, os2.remove("empty dir")) } @test @@ -131,7 +181,7 @@ path_test :: proc(t: ^testing.T) { err = os2.remove_all("a") _expect_no_error(t, err) } - + err = os2.mkdir_all("a/b/c/d", 0) _expect_no_error(t, err) @@ -141,23 +191,25 @@ path_test :: proc(t: ^testing.T) { fd, err = os2.create("a/b/c/file.txt", 0o644) _expect_no_error(t, err) - err = os2.close(fd) - _expect_no_error(t, err) when ODIN_OS == .Linux { expect(t, unix.sys_access("a/b/c/file.txt", X_OK) < 0, "unexpected exec permission") + } else { + expect(t, os2.exists("a/b/c/file.txt"), "file does not exist") } err = os2.rename("a/b/c/file.txt", "a/b/file.txt") _expect_no_error(t, err) when ODIN_OS == .Linux { - expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected exec permission") + expect(t, unix.sys_access("a/b/c/file.txt", F_OK) < 0, "unexpected file existence") + } else { + expect(t, !os2.exists("a/b/c/file.txt"), "unexpected file existence") } err = os2.symlink("b/c/d", "a/symlink_to_d") _expect_no_error(t, err) - + symlink: string symlink, err = os2.read_link("a/symlink_to_d") _expect_no_error(t, err) @@ -171,8 +223,10 @@ path_test :: proc(t: ^testing.T) { when ODIN_OS == .Linux { expect_value(t, unix.sys_access("a/b/c/d/shnt.txt", X_OK | R_OK | W_OK), 0) + } else { + expect(t, os2.exists("a/b/c/d/shnt.txt"), "file does not exist") } - + err = os2.remove_all("a") _expect_no_error(t, err) From b21e7e4518a82713910d959841a4a0eef88fc4a2 Mon Sep 17 00:00:00 2001 From: CiD- Date: Mon, 14 Mar 2022 15:44:34 -0400 Subject: [PATCH 012/254] rewrite mkdir_all --- core/os/os2/path_linux.odin | 63 +++++++++++++++++++++++++++++-- core/sys/unix/syscalls_linux.odin | 48 +++++++++++++++-------- tests/core/Makefile | 16 +++----- tests/core/os2/test_os2.odin | 9 +++-- 4 files changed, 101 insertions(+), 35 deletions(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index b474ae207..e47bd36b0 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -24,18 +24,73 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(path: string, perm: File_Mode) -> Error { - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - perm_i: int if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { return .Invalid_Argument } + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) return _ok_or_error(unix.sys_mkdir(path_cstr, int(perm & 0o777))) } -// TODO _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - return nil + _mkdirat :: proc(dfd: Handle, path: []u8, perm: int, has_created: ^bool) -> Error { + if len(path) == 0 { + return nil + } + i: int + for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {} + path[i] = 0 + new_dfd := unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) + switch new_dfd { + case -ENOENT: + res := unix.sys_mkdirat(int(dfd), cstring(&path[0]), perm) + if res < 0 { + return _get_platform_error(res) + } + has_created^ = true + new_dfd = unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) + if new_dfd < 0 { + return _get_platform_error(new_dfd) + } + fallthrough + case 0: + unix.sys_close(int(dfd)) + // skip consecutive '/' + for i += 1; i < len(path) && path[i] == '/'; i += 1 {} + return _mkdirat(Handle(new_dfd), path[i:], perm, has_created) + case: + return _get_platform_error(new_dfd) + } + unreachable() + } + + if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { + return .Invalid_Argument + } + + // need something we can edit, and use to generate cstrings + path_bytes := make([]u8, len(path) + 1, context.temp_allocator) + copy(path_bytes, path) + path_bytes[len(path)] = 0 + + dfd: int + if path_bytes[0] == '/' { + dfd = unix.sys_open("/", _OPENDIR_FLAGS) + path_bytes = path_bytes[1:] + } else { + dfd = unix.sys_open(".", _OPENDIR_FLAGS) + } + if dfd < 0 { + return _get_platform_error(dfd) + } + + has_created: bool + _mkdirat(Handle(dfd), path_bytes, int(perm & 0o777), &has_created) or_return + if has_created { + return nil + } + return .Exist + //return has_created ? nil : .Exist } dirent64 :: struct { diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 8cfb97076..8a1aadb9c 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1518,7 +1518,7 @@ when ODIN_ARCH == .amd64 { #panic("Unsupported architecture") } -AT_FDCWD :: -100 +AT_FDCWD :: ~uintptr(99) AT_REMOVEDIR :: uintptr(0x200) AT_SYMLINK_FOLLOW :: uintptr(0x400) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) @@ -1535,7 +1535,7 @@ sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } else { // NOTE: arm64 does not have open - return int(intrinsics.syscall(SYS_openat, uintptr(AT_FDCWD), uintptr(rawptr(path), uintptr(flags), uintptr(mode)))) + return int(intrinsics.syscall(SYS_openat, AT_FDCWD, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } } @@ -1593,7 +1593,7 @@ sys_stat :: proc(path: cstring, stat: rawptr) -> int { } else when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_stat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have stat - return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), 0)) + return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), 0)) } } @@ -1611,7 +1611,7 @@ sys_lstat :: proc(path: cstring, stat: rawptr) -> int { } else when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_lstat64, uintptr(rawptr(path)), uintptr(stat))) } else { // NOTE: arm64 does not have any lstat - return int(intrinsics.syscall(SYS_fstatat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) + return int(intrinsics.syscall(SYS_fstatat, AT_FDCWD, uintptr(rawptr(path)), uintptr(stat), AT_SYMLINK_NOFOLLOW)) } } @@ -1619,7 +1619,7 @@ sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } else { // NOTE: arm64 does not have readlink - return int(intrinsics.syscall(SYS_readlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) + return int(intrinsics.syscall(SYS_readlinkat, AT_FDCWD, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } } @@ -1627,7 +1627,7 @@ sys_symlink :: proc(old_name: cstring, new_name: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have symlink - return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), uintptr(AT_FDCWD), uintptr(rawptr(new_name)))) + return int(intrinsics.syscall(SYS_symlinkat, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)))) } } @@ -1635,7 +1635,7 @@ sys_access :: proc(path: cstring, mask: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) } else { // NOTE: arm64 does not have access - return int(intrinsics.syscall(SYS_faccessat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mask))) + return int(intrinsics.syscall(SYS_faccessat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mask))) } } @@ -1655,7 +1655,7 @@ sys_chmod :: proc(path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have chmod - return int(intrinsics.syscall(SYS_fchmodat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) + return int(intrinsics.syscall(SYS_fchmodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode))) } } @@ -1667,7 +1667,7 @@ sys_chown :: proc(path: cstring, user: int, group: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have chown - return int(intrinsics.syscall(SYS_fchownat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(user), uintptr(group), 0)) + return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), 0)) } } @@ -1679,7 +1679,7 @@ sys_lchown :: proc(path: cstring, user: int, group: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have lchown - return int(intrinsics.syscall(SYS_fchownat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW)) + return int(intrinsics.syscall(SYS_fchownat, AT_FDCWD, uintptr(rawptr(path)), uintptr(user), uintptr(group), AT_SYMLINK_NOFOLLOW)) } } @@ -1687,7 +1687,7 @@ sys_rename :: proc(old, new: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) } else { // NOTE: arm64 does not have rename - return int(intrinsics.syscall(SYS_renameat, uintptr(AT_FDCWD), uintptr(rawptr(old)), uintptr(rawptr(new)))) + return int(intrinsics.syscall(SYS_renameat, AT_FDCWD, uintptr(rawptr(old)), uintptr(rawptr(new)))) } } @@ -1695,7 +1695,7 @@ sys_link :: proc(old_name: cstring, new_name: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have link - return int(intrinsics.syscall(SYS_linkat, uintptr(AT_FDCWD), uintptr(rawptr(old_name)), uintptr(AT_FDCWD), uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW)) + return int(intrinsics.syscall(SYS_linkat, AT_FDCWD, uintptr(rawptr(old_name)), AT_FDCWD, uintptr(rawptr(new_name)), AT_SYMLINK_FOLLOW)) } } @@ -1703,7 +1703,7 @@ sys_unlink :: proc(path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have unlink - return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path), 0))) + return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), 0)) } } @@ -1715,7 +1715,7 @@ sys_rmdir :: proc(path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have rmdir - return int(intrinsics.syscall(SYS_unlinkat, uintptr(AT_FDCWD), uintptr(rawptr(path)), AT_REMOVEDIR)) + return int(intrinsics.syscall(SYS_unlinkat, AT_FDCWD, uintptr(rawptr(path)), AT_REMOVEDIR)) } } @@ -1723,18 +1723,26 @@ sys_mkdir :: proc(path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir - return int(intrinsics.syscall(SYS_mkdirat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode))) + return int(intrinsics.syscall(SYS_mkdirat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode))) } } +sys_mkdirat :: proc(dfd: int, path: cstring, mode: int) -> int { + return int(intrinsics.syscall(SYS_mkdirat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode))) +} + sys_mknod :: proc(path: cstring, mode: int, dev: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } else { // NOTE: arm64 does not have mknod - return int(intrinsics.syscall(SYS_mknodat, uintptr(AT_FDCWD), uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) + return int(intrinsics.syscall(SYS_mknodat, AT_FDCWD, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } } +sys_mknodat :: proc(dfd: int, path: cstring, mode: int, dev: int) -> int { + return int(intrinsics.syscall(SYS_mknodat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) +} + sys_truncate :: proc(path: cstring, length: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) @@ -1763,6 +1771,14 @@ sys_getdents64 :: proc(fd: int, dirent: rawptr, count: int) -> int { return int(intrinsics.syscall(SYS_getdents64, uintptr(fd), uintptr(dirent), uintptr(count))) } +sys_fork :: proc() -> int { + when ODIN_ARCH != .arm64 { + return int(intrinsics.syscall(SYS_fork)) + } else { + return int(intrinsics.syscall(SYS_clone, SIGCHLD)) + } +} + get_errno :: proc(res: int) -> i32 { if res < 0 && res > -4096 { return i32(-res) diff --git a/tests/core/Makefile b/tests/core/Makefile index 449efbb25..90b0df449 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,12 +1,8 @@ ODIN=../../odin PYTHON=$(shell which python3) -<<<<<<< HEAD -all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test os2_test -======= all: download_test_assets image_test compress_test strings_test hash_test crypto_test noise_test encoding_test \ - math_test linalg_glsl_math_test ->>>>>>> upstream/master + math_test linalg_glsl_math_test os2_test download_test_assets: $(PYTHON) download_assets.py @@ -29,18 +25,16 @@ crypto_test: noise_test: $(ODIN) run math/noise -out=test_noise -os2_test: - $(ODIN) run os2/test_os2.odin -out=test_os2 - encoding_test: $(ODIN) run encoding/json -out=test_json $(ODIN) run encoding/varint -out=test_varint -<<<<<<< HEAD -======= math_test: $(ODIN) run math/test_core_math.odin -out=test_core_math -collection:tests=.. linalg_glsl_math_test: $(ODIN) run math/linalg/glsl/test_linalg_glsl_math.odin -out=test_linalg_glsl_math -collection:tests=.. ->>>>>>> upstream/master + +os2_test: + $(ODIN) run os2/test_os2.odin -out=test_os2 + diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index a629063bd..0e0d3dcb5 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -15,8 +15,9 @@ TEST_count := 0 TEST_fail := 0 when ODIN_TEST { - expect :: testing.expect - log :: testing.log + expect_value :: testing.expect_value + expect :: testing.expect + log :: testing.log } else { expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) where intrinsics.type_is_comparable(T) { fmt.printf("[%v] ", loc) @@ -170,7 +171,7 @@ file_test :: proc(t: ^testing.T) { _expect_no_error(t, os2.close(fd)) _expect_no_error(t, os2.remove("file.txt")) - _expect_no_error(t, os2.mkdir("empty dir", 0)) + _expect_no_error(t, os2.mkdir("empty dir", 0o755)) _expect_no_error(t, os2.remove("empty dir")) } @@ -182,7 +183,7 @@ path_test :: proc(t: ^testing.T) { _expect_no_error(t, err) } - err = os2.mkdir_all("a/b/c/d", 0) + err = os2.mkdir_all("a/b/c/d", 0o755) _expect_no_error(t, err) expect(t, os2.exists("a"), "directory does not exist") From 4b1822ade8eda7cda798dc0d0b22e1f1d7040a8f Mon Sep 17 00:00:00 2001 From: CiD- Date: Mon, 14 Mar 2022 15:48:47 -0400 Subject: [PATCH 013/254] mkdir_all: close last open file --- core/os/os2/path_linux.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index e47bd36b0..fa47f1883 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -35,7 +35,7 @@ _mkdir :: proc(path: string, perm: File_Mode) -> Error { _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { _mkdirat :: proc(dfd: Handle, path: []u8, perm: int, has_created: ^bool) -> Error { if len(path) == 0 { - return nil + return _ok_or_error(unix.sys_close(int(dfd))) } i: int for /**/; i < len(path) - 1 && path[i] != '/'; i += 1 {} @@ -43,18 +43,18 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { new_dfd := unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) switch new_dfd { case -ENOENT: - res := unix.sys_mkdirat(int(dfd), cstring(&path[0]), perm) - if res < 0 { + if res := unix.sys_mkdirat(int(dfd), cstring(&path[0]), perm); res < 0 { return _get_platform_error(res) } has_created^ = true - new_dfd = unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS) - if new_dfd < 0 { + if new_dfd = unix.sys_openat(int(dfd), cstring(&path[0]), _OPENDIR_FLAGS); new_dfd < 0 { return _get_platform_error(new_dfd) } fallthrough case 0: - unix.sys_close(int(dfd)) + if res := unix.sys_close(int(dfd)) < 0; res < 0 { + return _get_platform_error(res) + } // skip consecutive '/' for i += 1; i < len(path) && path[i] == '/'; i += 1 {} return _mkdirat(Handle(new_dfd), path[i:], perm, has_created) From 6d6e840bc26a92bee1eb257d081bbc3695d741a4 Mon Sep 17 00:00:00 2001 From: CiD- Date: Mon, 14 Mar 2022 15:56:41 -0400 Subject: [PATCH 014/254] mkdir_all: WHOOPS --- core/os/os2/path_linux.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index fa47f1883..85c39916f 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -52,7 +52,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } fallthrough case 0: - if res := unix.sys_close(int(dfd)) < 0; res < 0 { + if res := unix.sys_close(int(dfd)); res < 0 { return _get_platform_error(res) } // skip consecutive '/' From 36c22393a4e3026bc84a5ee8d2230d1f6f78b83c Mon Sep 17 00:00:00 2001 From: CiD- Date: Tue, 15 Mar 2022 11:47:35 -0400 Subject: [PATCH 015/254] fix memory leak --- core/os/os2/path_linux.odin | 3 ++- tests/core/os2/test_os2.odin | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 85c39916f..889f7e447 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -112,7 +112,8 @@ _remove_all :: proc(path: string) -> Error { loop: for { res := unix.sys_getdents64(int(dfd), &buf[0], n) switch res { - case -22: //-EINVAL + case -EINVAL: + delete(buf) n *= 2 buf = make([]u8, n) continue loop diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index 0e0d3dcb5..e52351f03 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -2,6 +2,7 @@ package test_os2 import "core:os" import "core:fmt" +import "core:mem" import "core:os/os2" import "core:testing" import "core:intrinsics" @@ -49,10 +50,22 @@ when ODIN_TEST { main :: proc() { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + t: testing.T file_test(&t) path_test(&t) fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) + + for _, leak in track.allocation_map { + fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) + } + for bad_free in track.bad_free_array { + fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) + } + os.exit(TEST_fail > 0 ? 1 : 0) } @@ -215,6 +228,7 @@ path_test :: proc(t: ^testing.T) { symlink, err = os2.read_link("a/symlink_to_d") _expect_no_error(t, err) expect_value(t, symlink, "b/c/d") + delete(symlink) fd, err = os2.create("a/symlink_to_d/shnt.txt", 0o744) _expect_no_error(t, err) From e252d3bedf28e603ee3c33a4aebcfefbb5cfb66c Mon Sep 17 00:00:00 2001 From: CiD- Date: Wed, 23 Mar 2022 11:49:19 -0400 Subject: [PATCH 016/254] add os2.name --- core/os/os2/file_linux.odin | 27 +++++++++++++++++++++------ tests/core/os2/test_os2.odin | 25 ++++++++++++++++++++----- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 9030d265d..7040d0ed3 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -4,6 +4,7 @@ package os2 import "core:io" import "core:time" import "core:strings" +import "core:strconv" import "core:sys/unix" @@ -55,8 +56,20 @@ _close :: proc(fd: Handle) -> Error { } _name :: proc(fd: Handle, allocator := context.allocator) -> string { - //TODO - return "" + // NOTE: Not sure how portable this really is + PROC_FD_PATH :: "/proc/self/fd/" + + buf: [32]u8 + copy(buf[:], PROC_FD_PATH) + + strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) + + realpath: string + err: Error + if realpath, err = _read_link_cstr(cstring(&buf[0])); err != nil || realpath[0] != '/' { + return "" + } + return realpath } _seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { @@ -189,10 +202,7 @@ _symlink :: proc(old_name, new_name: string) -> Error { return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr)) } -_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { - name_cstr := strings.clone_to_cstring(name) - defer delete(name_cstr) - +_read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> (string, Error) { bufsz : uint = 256 buf := make([]byte, bufsz, allocator) for { @@ -210,6 +220,11 @@ _read_link :: proc(name: string, allocator := context.allocator) -> (string, Err } } +_read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + return _read_link_cstr(name_cstr, allocator) +} + _unlink :: proc(name: string) -> Error { name_cstr := strings.clone_to_cstring(name, context.temp_allocator) return _ok_or_error(unix.sys_unlink(name_cstr)) diff --git a/tests/core/os2/test_os2.odin b/tests/core/os2/test_os2.odin index e52351f03..c588e532e 100644 --- a/tests/core/os2/test_os2.odin +++ b/tests/core/os2/test_os2.odin @@ -1,9 +1,11 @@ package test_os2 +import "core:os/os2" + import "core:os" import "core:fmt" import "core:mem" -import "core:os/os2" +import "core:strings" import "core:testing" import "core:intrinsics" @@ -116,7 +118,7 @@ file_test :: proc(t: ^testing.T) { _expect_no_error(t, err) expect_value(t, n, 10) - // seek to the "ll" in "hello" + // seek FROM BEGINNING to the "ll" in "hello" n64: i64 n64, err = os2.seek(fd, 12, .Start) _expect_no_error(t, err) @@ -127,7 +129,7 @@ file_test :: proc(t: ^testing.T) { _expect_no_error(t, err) expect_value(t, n, 2) - // seek to the "e" in "he11o" + // seek BACK to the "e" in "he11o" n64, err = os2.seek(fd, -3, .Current) _expect_no_error(t, err) expect_value(t, n64, 11) @@ -137,7 +139,7 @@ file_test :: proc(t: ^testing.T) { _expect_no_error(t, err) expect_value(t, n, 1) - // seek to the "o" in "h311o" + // seek FROM THE END the "o" in "h311o" n64, err = os2.seek(fd, -1, .End) _expect_no_error(t, err) expect_value(t, n64, 14) @@ -157,11 +159,24 @@ file_test :: proc(t: ^testing.T) { expect(t, unix.sys_access("file.txt", X_OK) == 0, "expected exec permission") } + /* Build expected full path via cwd and known file name */ + parts: [2]string + parts[0], err = os2.getwd() + defer delete(parts[0]) + _expect_no_error(t, err) + + parts[1] = "/file.txt" + expected_full_path := strings.concatenate(parts[:]) + defer delete(expected_full_path) + + full_path := os2.name(fd) + defer delete(full_path) + expect_value(t, full_path, expected_full_path) + // NOTE: chown not possible without root user //_expect_no_error(t, os2.chown(fd, 0, 0)) _expect_no_error(t, os2.close(fd)) - fd, err = os2.open("file.txt") _expect_no_error(t, err) From 645661889137f6f4cb96b9671976dbc006345497 Mon Sep 17 00:00:00 2001 From: CiD- Date: Wed, 30 Mar 2022 16:54:29 -0400 Subject: [PATCH 017/254] finish up stat, lstat and fstat --- core/os/os2/file_linux.odin | 4 ++- core/os/os2/stat_linux.odin | 50 +++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 7040d0ed3..3202b9e16 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -22,8 +22,10 @@ _O_APPEND :: 0o2000 _O_NONBLOCK :: 0o4000 _O_LARGEFILE :: 0o100000 _O_DIRECTORY :: 0o200000 +_O_NOFOLLOW :: 0o400000 _O_SYNC :: 0o4010000 _O_CLOEXEC :: 0o2000000 +_O_PATH :: 0o10000000 _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) @@ -66,7 +68,7 @@ _name :: proc(fd: Handle, allocator := context.allocator) -> string { realpath: string err: Error - if realpath, err = _read_link_cstr(cstring(&buf[0])); err != nil || realpath[0] != '/' { + if realpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || realpath[0] != '/' { return "" } return realpath diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index c4cc5fe8d..d22f32d65 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -82,39 +82,51 @@ OS_Stat :: struct { _reserve3: i64, } + _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) { - return File_Info{}, nil -} - -_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - return File_Info{}, nil -} - -_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - cstr := strings.clone_to_cstring(name) - defer delete(cstr) - s: OS_Stat - result := unix.sys_lstat(cstr, &s) + result := unix.sys_fstat(int(fd), &s) if result < 0 { - return {}, _get_platform_error(int(unix.get_errno(result))) + return {}, _get_platform_error(result) } + // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time fi := File_Info { - fullpath = "", + fullpath = _name(fd, allocator), name = "", size = s.size, mode = 0, is_dir = S_ISDIR(s.mode), - creation_time = time.Time{0}, // linux does not track this - //TODO - modification_time = time.Time{0}, - access_time = time.Time{0}, + modification_time = time.Time {s.modified.seconds}, + access_time = time.Time {s.last_access.seconds}, + creation_time = time.Time{0}, // regular stat does not provide this } - + + fi.name = filepath.base(fi.fullpath) return fi, nil } +// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { + cstr := strings.clone_to_cstring(name, context.temp_allocator) + fd := unix.sys_open(cstr, _O_RDONLY) + if fd < 0 { + return {}, _get_platform_error(fd) + } + defer unix.sys_close(fd) + return _fstat(Handle(fd), allocator) +} + +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { + cstr := strings.clone_to_cstring(name, context.temp_allocator) + fd := unix.sys_open(cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) + if fd < 0 { + return {}, _get_platform_error(fd) + } + defer unix.sys_close(fd) + return _fstat(Handle(fd), allocator) +} + _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } From 88de3a1c0633761e2f73f4e576a0dfd2bb9c32bf Mon Sep 17 00:00:00 2001 From: CiD- Date: Fri, 1 Apr 2022 22:41:35 -0400 Subject: [PATCH 018/254] add _chtimes --- core/os/os2/file_linux.odin | 10 ++++++++-- core/sys/unix/syscalls_linux.odin | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 3202b9e16..89075e00c 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -27,6 +27,8 @@ _O_SYNC :: 0o4010000 _O_CLOEXEC :: 0o2000000 _O_PATH :: 0o10000000 +_AT_FDCWD :: -100 + _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { cstr := strings.clone_to_cstring(name, context.temp_allocator) @@ -250,8 +252,12 @@ _lchown :: proc(name: string, uid, gid: int) -> Error { } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { - //TODO - return nil + name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + times := [2]Unix_File_Time { + { atime._nsec, 0 }, + { mtime._nsec, 0 }, + } + return _ok_or_error(unix.sys_utimensat(_AT_FDCWD, name_cstr, ×, 0)) } _exists :: proc(name: string) -> bool { diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 8a1aadb9c..6fc6dc594 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1779,6 +1779,12 @@ sys_fork :: proc() -> int { } } +// NOTE: Unsure about if this works directly on 32 bit archs. It may need 32 bit version of the time struct. +// As of Linux 5.1, there is a utimensat_time64 function. Maybe use this in the future? +sys_utimensat :: proc(dfd: int, path: cstring, times: rawptr, flags: int) -> int { + return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags))) +} + get_errno :: proc(res: int) -> i32 { if res < 0 && res > -4096 { return i32(-res) From aadb4db2113896011a9cbc661c2ccde893300cb1 Mon Sep 17 00:00:00 2001 From: CiD- Date: Wed, 6 Apr 2022 10:53:46 -0400 Subject: [PATCH 019/254] avoid temp_allocator on stupidly long paths --- core/os/os2/file_linux.odin | 86 +++++++++++++++++++++++++++++++------ core/os/os2/path_linux.odin | 37 +++++++++++++--- core/os/os2/stat_linux.odin | 20 ++++++--- 3 files changed, 117 insertions(+), 26 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 89075e00c..998fe8617 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -29,8 +29,13 @@ _O_PATH :: 0o10000000 _AT_FDCWD :: -100 +_CSTRING_NAME_HEAP_THRESHOLD :: 512 + _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Error) { - cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } flags_i: int switch flags & O_RDONLY|O_WRONLY|O_RDWR { @@ -46,7 +51,7 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (Handle, Erro flags_i |= (_O_TRUNC * int(.Trunc in flags)) flags_i |= (_O_CLOEXEC * int(.Close_On_Exec in flags)) - handle_i := unix.sys_open(cstr, flags_i, int(perm)) + handle_i := unix.sys_open(name_cstr, flags_i, int(perm)) if handle_i < 0 { return INVALID_HANDLE, _get_platform_error(handle_i) } @@ -174,7 +179,10 @@ _truncate :: proc(fd: Handle, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } handle_i := unix.sys_open(name_cstr, int(File_Flags.Read)) if handle_i < 0 { @@ -189,20 +197,41 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + old_name_cstr, old_allocated := _name_to_cstring(old_name) + new_name_cstr, new_allocated := _name_to_cstring(new_name) + defer if old_allocated { + delete(old_name_cstr) + } + defer if new_allocated { + delete(new_name_cstr) + } + return _ok_or_error(unix.sys_rename(old_name_cstr, new_name_cstr)) } _link :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + old_name_cstr, old_allocated := _name_to_cstring(old_name) + new_name_cstr, new_allocated := _name_to_cstring(new_name) + defer if old_allocated { + delete(old_name_cstr) + } + defer if new_allocated { + delete(new_name_cstr) + } + return _ok_or_error(unix.sys_link(old_name_cstr, new_name_cstr)) } _symlink :: proc(old_name, new_name: string) -> Error { - old_name_cstr := strings.clone_to_cstring(old_name, context.temp_allocator) - new_name_cstr := strings.clone_to_cstring(new_name, context.temp_allocator) + old_name_cstr, old_allocated := _name_to_cstring(old_name) + new_name_cstr, new_allocated := _name_to_cstring(new_name) + defer if old_allocated { + delete(old_name_cstr) + } + defer if new_allocated { + delete(new_name_cstr) + } + return _ok_or_error(unix.sys_symlink(old_name_cstr, new_name_cstr)) } @@ -225,12 +254,18 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator := context.allocator) -> ( } _read_link :: proc(name: string, allocator := context.allocator) -> (string, Error) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } return _read_link_cstr(name_cstr, allocator) } _unlink :: proc(name: string) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } return _ok_or_error(unix.sys_unlink(name_cstr)) } @@ -247,12 +282,18 @@ _chown :: proc(fd: Handle, uid, gid: int) -> Error { } _lchown :: proc(name: string, uid, gid: int) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } return _ok_or_error(unix.sys_lchown(name_cstr, uid, gid)) } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } times := [2]Unix_File_Time { { atime._nsec, 0 }, { mtime._nsec, 0 }, @@ -261,7 +302,10 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { } _exists :: proc(name: string) -> bool { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } return unix.sys_access(name_cstr, F_OK) == 0 } @@ -282,3 +326,17 @@ _is_dir :: proc(fd: Handle) -> bool { } return S_ISDIR(s.mode) } + +// Ideally we want to use the temp_allocator. PATH_MAX on Linux is commonly +// defined as 512, however, it is well known that paths can exceed that limit. +// So, in theory you could have a path larger than the entire temp_allocator's +// buffer. Therefor any large paths will use context.allocator. +_name_to_cstring :: proc(path: string) -> (cpath: cstring, allocated: bool) { + if len(path) > _CSTRING_NAME_HEAP_THRESHOLD { + cpath = strings.clone_to_cstring(path) + allocated = true + return + } + cpath = strings.clone_to_cstring(path, context.temp_allocator) + return +} diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 889f7e447..5dadb7608 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -24,11 +24,16 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(path: string, perm: File_Mode) -> Error { + // NOTE: These modes would require sys_mknod, however, that would require + // additional arguments to this function. if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { return .Invalid_Argument } - path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + path_cstr, allocated := _name_to_cstring(path) + defer if allocated { + delete(path_cstr) + } return _ok_or_error(unix.sys_mkdir(path_cstr, int(perm & 0o777))) } @@ -69,7 +74,19 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } // need something we can edit, and use to generate cstrings - path_bytes := make([]u8, len(path) + 1, context.temp_allocator) + allocated: bool + path_bytes: []u8 + if len(path) > _CSTRING_NAME_HEAP_THRESHOLD { + allocated = true + path_bytes = make([]u8, len(path) + 1) + } else { + path_bytes = make([]u8, len(path) + 1, context.temp_allocator) + } + defer if allocated { + delete(path_bytes) + } + + // NULL terminate the byte slice to make it a valid cstring copy(path_bytes, path) path_bytes[len(path)] = 0 @@ -165,12 +182,15 @@ _remove_all :: proc(path: string) -> Error { return nil } - cstr := strings.clone_to_cstring(path, context.temp_allocator) + path_cstr, allocated := _name_to_cstring(path) + defer if allocated { + delete(path_cstr) + } - handle_i := unix.sys_open(cstr, _OPENDIR_FLAGS) + handle_i := unix.sys_open(path_cstr, _OPENDIR_FLAGS) switch handle_i { case -ENOTDIR: - return _ok_or_error(unix.sys_unlink(cstr)) + return _ok_or_error(unix.sys_unlink(path_cstr)) case -4096..<0: return _get_platform_error(handle_i) } @@ -178,7 +198,7 @@ _remove_all :: proc(path: string) -> Error { fd := Handle(handle_i) defer close(fd) _remove_all_dir(fd) or_return - return _ok_or_error(unix.sys_rmdir(cstr)) + return _ok_or_error(unix.sys_rmdir(path_cstr)) } _getwd :: proc(allocator := context.allocator) -> (string, Error) { @@ -203,6 +223,9 @@ _getwd :: proc(allocator := context.allocator) -> (string, Error) { } _setwd :: proc(dir: string) -> Error { - dir_cstr := strings.clone_to_cstring(dir, context.temp_allocator) + dir_cstr, allocated := _name_to_cstring(dir) + defer if allocated { + delete(dir_cstr) + } return _ok_or_error(unix.sys_chdir(dir_cstr)) } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index d22f32d65..9bfd900b6 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -108,8 +108,12 @@ _fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Error) // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - cstr := strings.clone_to_cstring(name, context.temp_allocator) - fd := unix.sys_open(cstr, _O_RDONLY) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + + fd := unix.sys_open(name_cstr, _O_RDONLY) if fd < 0 { return {}, _get_platform_error(fd) } @@ -118,8 +122,11 @@ _stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error } _lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - cstr := strings.clone_to_cstring(name, context.temp_allocator) - fd := unix.sys_open(cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } + fd := unix.sys_open(name_cstr, _O_RDONLY | _O_PATH | _O_NOFOLLOW) if fd < 0 { return {}, _get_platform_error(fd) } @@ -132,7 +139,10 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { } _stat_internal :: proc(name: string) -> (s: OS_Stat, res: int) { - name_cstr := strings.clone_to_cstring(name, context.temp_allocator) + name_cstr, allocated := _name_to_cstring(name) + defer if allocated { + delete(name_cstr) + } res = unix.sys_stat(name_cstr, &s) return } From 9ae566adcc5e7e55039349eed2b4768739391ae8 Mon Sep 17 00:00:00 2001 From: CiD- Date: Fri, 8 Apr 2022 13:45:19 -0400 Subject: [PATCH 020/254] commit before fetching upstream/master --- core/os/os2/file_linux.odin | 8 +- core/sys/unix/syscalls_linux.odin | 125 +++++++++++++++++++++--------- 2 files changed, 91 insertions(+), 42 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 998fe8617..449eff1e2 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -331,12 +331,12 @@ _is_dir :: proc(fd: Handle) -> bool { // defined as 512, however, it is well known that paths can exceed that limit. // So, in theory you could have a path larger than the entire temp_allocator's // buffer. Therefor any large paths will use context.allocator. -_name_to_cstring :: proc(path: string) -> (cpath: cstring, allocated: bool) { - if len(path) > _CSTRING_NAME_HEAP_THRESHOLD { - cpath = strings.clone_to_cstring(path) +_name_to_cstring :: proc(name: string) -> (cname: cstring, allocated: bool) { + if len(name) > _CSTRING_NAME_HEAP_THRESHOLD { + cname = strings.clone_to_cstring(name) allocated = true return } - cpath = strings.clone_to_cstring(path, context.temp_allocator) + cname = strings.clone_to_cstring(name, context.temp_allocator) return } diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 6fc6dc594..e72bfcedf 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1518,11 +1518,43 @@ when ODIN_ARCH == .amd64 { #panic("Unsupported architecture") } +// syscall related constants AT_FDCWD :: ~uintptr(99) AT_REMOVEDIR :: uintptr(0x200) AT_SYMLINK_FOLLOW :: uintptr(0x400) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) +PROT_NONE :: 0x0 +PROT_READ :: 0x1 +PROT_WRITE :: 0x2 +PROT_EXEC :: 0x4 +PROT_GROWSDOWN :: 0x01000000 +PROT_GROWSUP :: 0x02000000 + +MAP_FIXED :: 0x1 +MAP_PRIVATE :: 0x2 +MAP_SHARED :: 0x4 +MAP_ANONYMOUS :: 0x20 + +MADV_NORMAL :: 0 +MADV_RANDOM :: 1 +MADV_SEQUENTIAL :: 2 +MADV_WILLNEED :: 3 +MADV_DONTNEED :: 4 +MADV_FREE :: 8 +MADV_REMOVE :: 9 +MADV_DONTFORK :: 10 +MADV_DOFORK :: 11 +MADV_MERGEABLE :: 12 +MADV_UNMERGEABLE :: 13 +MADV_HUGEPAGE :: 14 +MADV_NOHUGEPAGE :: 15 +MADV_DONTDUMP :: 16 +MADV_DODUMP :: 17 +MADV_WIPEONFORK :: 18 +MADV_KEEPONFORK :: 19 +MADV_HWPOISON :: 100 + sys_gettid :: proc "contextless" () -> int { return cast(int)intrinsics.syscall(SYS_gettid) } @@ -1531,7 +1563,7 @@ sys_getrandom :: proc "contextless" (buf: ^byte, buflen: int, flags: uint) -> in return cast(int)intrinsics.syscall(SYS_getrandom, buf, cast(uintptr)(buflen), cast(uintptr)(flags)) } -sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { +sys_open :: proc "contextless" (path: cstring, flags: int, mode: int = 0o000) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_open, uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } else { // NOTE: arm64 does not have open @@ -1539,19 +1571,19 @@ sys_open :: proc(path: cstring, flags: int, mode: int = 0o000) -> int { } } -sys_openat :: proc(dfd: int, path: cstring, flags: int, mode: int = 0o000) -> int { +sys_openat :: proc "contextless" (dfd: int, path: cstring, flags: int, mode: int = 0o000) -> int { return int(intrinsics.syscall(SYS_openat, uintptr(dfd), uintptr(rawptr(path)), uintptr(flags), uintptr(mode))) } -sys_close :: proc(fd: int) -> int { +sys_close :: proc "contextless" (fd: int) -> int { return int(intrinsics.syscall(SYS_close, uintptr(fd))) } -sys_read :: proc(fd: int, buf: rawptr, size: uint) -> int { +sys_read :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_read, uintptr(fd), uintptr(buf), uintptr(size))) } -sys_pread :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { +sys_pread :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_pread64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) } else { @@ -1561,11 +1593,11 @@ sys_pread :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { } } -sys_write :: proc(fd: int, buf: rawptr, size: uint) -> int { +sys_write :: proc "contextless" (fd: int, buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_write, uintptr(fd), uintptr(buf), uintptr(size))) } -sys_pwrite :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { +sys_pwrite :: proc "contextless" (fd: int, buf: rawptr, size: uint, offset: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_pwrite64, uintptr(fd), uintptr(buf), uintptr(size), uintptr(offset))) } else { @@ -1575,7 +1607,7 @@ sys_pwrite :: proc(fd: int, buf: rawptr, size: uint, offset: i64) -> int { } } -sys_lseek :: proc(fd: int, offset: i64, whence: int) -> i64 { +sys_lseek :: proc "contextless" (fd: int, offset: i64, whence: int) -> i64 { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return i64(intrinsics.syscall(SYS_lseek, uintptr(fd), uintptr(offset), uintptr(whence))) } else { @@ -1587,7 +1619,7 @@ sys_lseek :: proc(fd: int, offset: i64, whence: int) -> i64 { } } -sys_stat :: proc(path: cstring, stat: rawptr) -> int { +sys_stat :: proc "contextless" (path: cstring, stat: rawptr) -> int { when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_stat, uintptr(rawptr(path)), uintptr(stat))) } else when ODIN_ARCH != .arm64 { @@ -1597,7 +1629,7 @@ sys_stat :: proc(path: cstring, stat: rawptr) -> int { } } -sys_fstat :: proc(fd: int, stat: rawptr) -> int { +sys_fstat :: proc "contextless" (fd: int, stat: rawptr) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_fstat, uintptr(fd), uintptr(stat))) } else { @@ -1605,7 +1637,7 @@ sys_fstat :: proc(fd: int, stat: rawptr) -> int { } } -sys_lstat :: proc(path: cstring, stat: rawptr) -> int { +sys_lstat :: proc "contextless" (path: cstring, stat: rawptr) -> int { when ODIN_ARCH == .amd64 { return int(intrinsics.syscall(SYS_lstat, uintptr(rawptr(path)), uintptr(stat))) } else when ODIN_ARCH != .arm64 { @@ -1615,7 +1647,7 @@ sys_lstat :: proc(path: cstring, stat: rawptr) -> int { } } -sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { +sys_readlink :: proc "contextless" (path: cstring, buf: rawptr, bufsiz: uint) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_readlink, uintptr(rawptr(path)), uintptr(buf), uintptr(bufsiz))) } else { // NOTE: arm64 does not have readlink @@ -1623,7 +1655,7 @@ sys_readlink :: proc(path: cstring, buf: rawptr, bufsiz: uint) -> int { } } -sys_symlink :: proc(old_name: cstring, new_name: cstring) -> int { +sys_symlink :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_symlink, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have symlink @@ -1631,7 +1663,7 @@ sys_symlink :: proc(old_name: cstring, new_name: cstring) -> int { } } -sys_access :: proc(path: cstring, mask: int) -> int { +sys_access :: proc "contextless" (path: cstring, mask: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_access, uintptr(rawptr(path)), uintptr(mask))) } else { // NOTE: arm64 does not have access @@ -1639,19 +1671,19 @@ sys_access :: proc(path: cstring, mask: int) -> int { } } -sys_getcwd :: proc(buf: rawptr, size: uint) -> int { +sys_getcwd :: proc "contextless" (buf: rawptr, size: uint) -> int { return int(intrinsics.syscall(SYS_getcwd, uintptr(buf), uintptr(size))) } -sys_chdir :: proc(path: cstring) -> int { +sys_chdir :: proc "contextless" (path: cstring) -> int { return int(intrinsics.syscall(SYS_chdir, uintptr(rawptr(path)))) } -sys_fchdir :: proc(fd: int) -> int { +sys_fchdir :: proc "contextless" (fd: int) -> int { return int(intrinsics.syscall(SYS_fchdir, uintptr(fd))) } -sys_chmod :: proc(path: cstring, mode: int) -> int { +sys_chmod :: proc "contextless" (path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_chmod, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have chmod @@ -1659,11 +1691,11 @@ sys_chmod :: proc(path: cstring, mode: int) -> int { } } -sys_fchmod :: proc(fd: int, mode: int) -> int { +sys_fchmod :: proc "contextless" (fd: int, mode: int) -> int { return int(intrinsics.syscall(SYS_fchmod, uintptr(fd), uintptr(mode))) } -sys_chown :: proc(path: cstring, user: int, group: int) -> int { +sys_chown :: proc "contextless" (path: cstring, user: int, group: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_chown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have chown @@ -1671,11 +1703,11 @@ sys_chown :: proc(path: cstring, user: int, group: int) -> int { } } -sys_fchown :: proc(fd: int, user: int, group: int) -> int { +sys_fchown :: proc "contextless" (fd: int, user: int, group: int) -> int { return int(intrinsics.syscall(SYS_fchown, uintptr(fd), uintptr(user), uintptr(group))) } -sys_lchown :: proc(path: cstring, user: int, group: int) -> int { +sys_lchown :: proc "contextless" (path: cstring, user: int, group: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_lchown, uintptr(rawptr(path)), uintptr(user), uintptr(group))) } else { // NOTE: arm64 does not have lchown @@ -1683,7 +1715,7 @@ sys_lchown :: proc(path: cstring, user: int, group: int) -> int { } } -sys_rename :: proc(old, new: cstring) -> int { +sys_rename :: proc "contextless" (old, new: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rename, uintptr(rawptr(old)), uintptr(rawptr(new)))) } else { // NOTE: arm64 does not have rename @@ -1691,7 +1723,7 @@ sys_rename :: proc(old, new: cstring) -> int { } } -sys_link :: proc(old_name: cstring, new_name: cstring) -> int { +sys_link :: proc "contextless" (old_name: cstring, new_name: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_link, uintptr(rawptr(old_name)), uintptr(rawptr(new_name)))) } else { // NOTE: arm64 does not have link @@ -1699,7 +1731,7 @@ sys_link :: proc(old_name: cstring, new_name: cstring) -> int { } } -sys_unlink :: proc(path: cstring) -> int { +sys_unlink :: proc "contextless" (path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_unlink, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have unlink @@ -1707,11 +1739,11 @@ sys_unlink :: proc(path: cstring) -> int { } } -sys_unlinkat :: proc(dfd: int, path: cstring, flag: int = 0) -> int { +sys_unlinkat :: proc "contextless" (dfd: int, path: cstring, flag: int = 0) -> int { return int(intrinsics.syscall(SYS_unlinkat, uintptr(dfd), uintptr(rawptr(path)), flag)) } -sys_rmdir :: proc(path: cstring) -> int { +sys_rmdir :: proc "contextless" (path: cstring) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_rmdir, uintptr(rawptr(path)))) } else { // NOTE: arm64 does not have rmdir @@ -1719,7 +1751,7 @@ sys_rmdir :: proc(path: cstring) -> int { } } -sys_mkdir :: proc(path: cstring, mode: int) -> int { +sys_mkdir :: proc "contextless" (path: cstring, mode: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mkdir, uintptr(rawptr(path)), uintptr(mode))) } else { // NOTE: arm64 does not have mkdir @@ -1727,11 +1759,11 @@ sys_mkdir :: proc(path: cstring, mode: int) -> int { } } -sys_mkdirat :: proc(dfd: int, path: cstring, mode: int) -> int { +sys_mkdirat :: proc "contextless" (dfd: int, path: cstring, mode: int) -> int { return int(intrinsics.syscall(SYS_mkdirat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode))) } -sys_mknod :: proc(path: cstring, mode: int, dev: int) -> int { +sys_mknod :: proc "contextless" (path: cstring, mode: int, dev: int) -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_mknod, uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } else { // NOTE: arm64 does not have mknod @@ -1739,11 +1771,11 @@ sys_mknod :: proc(path: cstring, mode: int, dev: int) -> int { } } -sys_mknodat :: proc(dfd: int, path: cstring, mode: int, dev: int) -> int { +sys_mknodat :: proc "contextless" (dfd: int, path: cstring, mode: int, dev: int) -> int { return int(intrinsics.syscall(SYS_mknodat, uintptr(dfd), uintptr(rawptr(path)), uintptr(mode), uintptr(dev))) } -sys_truncate :: proc(path: cstring, length: i64) -> int { +sys_truncate :: proc "contextless" (path: cstring, length: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_truncate, uintptr(rawptr(path)), uintptr(length))) } else { @@ -1753,7 +1785,7 @@ sys_truncate :: proc(path: cstring, length: i64) -> int { } } -sys_ftruncate :: proc(fd: int, length: i64) -> int { +sys_ftruncate :: proc "contextless" (fd: int, length: i64) -> int { when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 { return int(intrinsics.syscall(SYS_ftruncate, uintptr(fd), uintptr(length))) } else { @@ -1763,15 +1795,15 @@ sys_ftruncate :: proc(fd: int, length: i64) -> int { } } -sys_fsync :: proc(fd: int) -> int { +sys_fsync :: proc "contextless" (fd: int) -> int { return int(intrinsics.syscall(SYS_fsync, uintptr(fd))) } -sys_getdents64 :: proc(fd: int, dirent: rawptr, count: int) -> int { +sys_getdents64 :: proc "contextless" (fd: int, dirent: rawptr, count: int) -> int { return int(intrinsics.syscall(SYS_getdents64, uintptr(fd), uintptr(dirent), uintptr(count))) } -sys_fork :: proc() -> int { +sys_fork :: proc "contextless" () -> int { when ODIN_ARCH != .arm64 { return int(intrinsics.syscall(SYS_fork)) } else { @@ -1779,13 +1811,30 @@ sys_fork :: proc() -> int { } } +sys_mmap :: proc "contextless" (addr: rawptr, length: uint, prot, flags, fd: int, offset: uintptr) -> int { + return int(intrinsics.syscall(unix.SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset)) +} + +sys_munmap :: proc "contextless" (addr: rawptr, length: uint) -> int { + return int(intrinsics.syscall(unix.SYS_munmap, uintptr(addr), uintptr(length))) +} + +sys_mprotect :: proc "contextless" (addr: rawptr, length: uint, prot: int) -> int { + return int(intrinsics.syscall(unix.SYS_mprotect, uintptr(addr), uintptr(length), uintptr(prot))) +} + +sys_madvise :: proc "contextless" (addr: rawptr, length: uint, advice: int) -> int { + return int(intrinsics.syscall(unix.SYS_madvise, uintptr(addr), uintptr(length), uintptr(advice))) +} + + // NOTE: Unsure about if this works directly on 32 bit archs. It may need 32 bit version of the time struct. // As of Linux 5.1, there is a utimensat_time64 function. Maybe use this in the future? -sys_utimensat :: proc(dfd: int, path: cstring, times: rawptr, flags: int) -> int { +sys_utimensat :: proc "contextless" (dfd: int, path: cstring, times: rawptr, flags: int) -> int { return int(intrinsics.syscall(SYS_utimensat, uintptr(dfd), uintptr(rawptr(path)), uintptr(times), uintptr(flags))) } -get_errno :: proc(res: int) -> i32 { +get_errno :: proc "contextless" (res: int) -> i32 { if res < 0 && res > -4096 { return i32(-res) } From 1a2c36e482315f87af92f29d36ad204789a129a5 Mon Sep 17 00:00:00 2001 From: CiD- Date: Fri, 8 Apr 2022 13:52:36 -0400 Subject: [PATCH 021/254] whoops --- core/sys/unix/syscalls_linux.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index e72bfcedf..8e0f7d89d 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1812,19 +1812,19 @@ sys_fork :: proc "contextless" () -> int { } sys_mmap :: proc "contextless" (addr: rawptr, length: uint, prot, flags, fd: int, offset: uintptr) -> int { - return int(intrinsics.syscall(unix.SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset)) + return int(intrinsics.syscall(SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset)) } sys_munmap :: proc "contextless" (addr: rawptr, length: uint) -> int { - return int(intrinsics.syscall(unix.SYS_munmap, uintptr(addr), uintptr(length))) + return int(intrinsics.syscall(SYS_munmap, uintptr(addr), uintptr(length))) } sys_mprotect :: proc "contextless" (addr: rawptr, length: uint, prot: int) -> int { - return int(intrinsics.syscall(unix.SYS_mprotect, uintptr(addr), uintptr(length), uintptr(prot))) + return int(intrinsics.syscall(SYS_mprotect, uintptr(addr), uintptr(length), uintptr(prot))) } sys_madvise :: proc "contextless" (addr: rawptr, length: uint, advice: int) -> int { - return int(intrinsics.syscall(unix.SYS_madvise, uintptr(addr), uintptr(length), uintptr(advice))) + return int(intrinsics.syscall(SYS_madvise, uintptr(addr), uintptr(length), uintptr(advice))) } From 5bc81642748af7c9f1eb6847a97639620e2d41c6 Mon Sep 17 00:00:00 2001 From: CiD- Date: Tue, 26 Apr 2022 17:11:30 -0400 Subject: [PATCH 022/254] add mremap + flags --- core/sys/unix/syscalls_linux.odin | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index 8e0f7d89d..630b4ef24 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1524,18 +1524,26 @@ AT_REMOVEDIR :: uintptr(0x200) AT_SYMLINK_FOLLOW :: uintptr(0x400) AT_SYMLINK_NOFOLLOW :: uintptr(0x100) -PROT_NONE :: 0x0 -PROT_READ :: 0x1 -PROT_WRITE :: 0x2 -PROT_EXEC :: 0x4 +// mmap flags +PROT_NONE :: 0x0 +PROT_READ :: 0x1 +PROT_WRITE :: 0x2 +PROT_EXEC :: 0x4 PROT_GROWSDOWN :: 0x01000000 -PROT_GROWSUP :: 0x02000000 +PROT_GROWSUP :: 0x02000000 -MAP_FIXED :: 0x1 -MAP_PRIVATE :: 0x2 -MAP_SHARED :: 0x4 -MAP_ANONYMOUS :: 0x20 +MAP_FIXED :: 0x10 +MAP_SHARED :: 0x1 +MAP_PRIVATE :: 0x2 +MAP_SHARED_VALIDATE :: 0x3 +MAP_ANONYMOUS :: 0x20 +// mremap flags +MREMAP_MAYMOVE :: 1 +MREMAP_FIXED :: 2 +MREMAP_DONTUNMAP :: 4 + +// madvise flags MADV_NORMAL :: 0 MADV_RANDOM :: 1 MADV_SEQUENTIAL :: 2 @@ -1815,6 +1823,10 @@ sys_mmap :: proc "contextless" (addr: rawptr, length: uint, prot, flags, fd: int return int(intrinsics.syscall(SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset)) } +sys_mremap :: proc "contextless" (addr: rawptr, old_length, new_length: uint, flags: int, new_addr: rawptr = nil) -> int { + return int(intrinsics.syscall(SYS_mremap, uintptr(addr), uintptr(old_length), uintptr(new_length), uintptr(flags), uintptr(new_addr))) +} + sys_munmap :: proc "contextless" (addr: rawptr, length: uint) -> int { return int(intrinsics.syscall(SYS_munmap, uintptr(addr), uintptr(length))) } From a223340c4498f49633d4108bb1ea5024071b1d53 Mon Sep 17 00:00:00 2001 From: bkrypt <4868093+bkrypt@users.noreply.github.com> Date: Fri, 15 Apr 2022 13:38:58 +0200 Subject: [PATCH 023/254] Update `vendor/miniaudio` to v0.11.9 --- vendor/miniaudio/common.odin | 108 +- vendor/miniaudio/data_conversion.odin | 291 +- vendor/miniaudio/decoding.odin | 102 +- vendor/miniaudio/device_io_procs.odin | 786 +- vendor/miniaudio/device_io_types.odin | 365 +- vendor/miniaudio/doc.odin | 2955 +- vendor/miniaudio/effects.odin | 300 + vendor/miniaudio/encoding.odin | 23 +- vendor/miniaudio/engine.odin | 341 + vendor/miniaudio/filtering.odin | 129 +- vendor/miniaudio/generation.odin | 28 +- vendor/miniaudio/job_queue.odin | 239 + vendor/miniaudio/lib/miniaudio.lib | Bin 1873426 -> 2767136 bytes vendor/miniaudio/logging.odin | 30 + vendor/miniaudio/node_graph.odin | 469 + vendor/miniaudio/resource_manager.odin | 288 + vendor/miniaudio/src/miniaudio.h | 34632 ++++++++++++++++++----- vendor/miniaudio/synchronization.odin | 152 + vendor/miniaudio/utilities.odin | 175 +- vendor/miniaudio/vfs.odin | 10 +- 20 files changed, 32998 insertions(+), 8425 deletions(-) create mode 100644 vendor/miniaudio/effects.odin create mode 100644 vendor/miniaudio/engine.odin create mode 100644 vendor/miniaudio/job_queue.odin create mode 100644 vendor/miniaudio/node_graph.odin create mode 100644 vendor/miniaudio/resource_manager.odin create mode 100644 vendor/miniaudio/synchronization.odin diff --git a/vendor/miniaudio/common.odin b/vendor/miniaudio/common.odin index 89e3d6bd2..d7b901714 100644 --- a/vendor/miniaudio/common.odin +++ b/vendor/miniaudio/common.odin @@ -13,13 +13,15 @@ when ODIN_OS == .Windows { handle :: distinct rawptr -/* SIMD alignment in bytes. Currently set to 64 bytes in preparation for future AVX-512 optimizations. */ -SIMD_ALIGNMENT :: 64 +/* SIMD alignment in bytes. Currently set to 32 bytes in preparation for future AVX optimizations. */ +SIMD_ALIGNMENT :: 32 -LOG_LEVEL_DEBUG :: 4 -LOG_LEVEL_INFO :: 3 -LOG_LEVEL_WARNING :: 2 -LOG_LEVEL_ERROR :: 1 +log_level :: enum c.int { + LOG_LEVEL_DEBUG = 4, + LOG_LEVEL_INFO = 3, + LOG_LEVEL_WARNING = 2, + LOG_LEVEL_ERROR = 1, +} channel :: enum u8 { @@ -158,13 +160,13 @@ result :: enum c.int { FAILED_TO_STOP_BACKEND_DEVICE = -303, } + MIN_CHANNELS :: 1 -MAX_CHANNELS :: 32 +MAX_CHANNELS :: 254 MAX_FILTER_ORDER :: 8 - stream_format :: enum c.int { pcm = 0, } @@ -175,9 +177,9 @@ stream_layout :: enum c.int { } dither_mode :: enum c.int { - none = 0, - rectangle, - triangle, + none = 0, + rectangle, + triangle, } format :: enum c.int { @@ -191,6 +193,7 @@ format :: enum c.int { s24 = 3, /* Tightly packed. 3 bytes per sample. */ s32 = 4, f32 = 5, + count, } standard_sample_rate :: enum u32 { @@ -224,7 +227,6 @@ channel_mix_mode :: enum c.int { rectangular = 0, /* Simple averaging based on the plane(s) the channel is sitting on. */ simple, /* Drop excess channels; zeroed out extra channels. */ custom_weights, /* Use custom weights specified in ma_channel_router_config. */ - planar_blend = rectangular, default = rectangular, } @@ -257,6 +259,10 @@ lcg :: struct { state: i32, } + +/* Spinlocks are 32-bit for compatibility reasons. */ +spinlock :: distinct u32 + NO_THREADING :: false when !NO_THREADING { @@ -272,8 +278,6 @@ thread_priority :: enum c.int { default = 0, } -/* Spinlocks are 32-bit for compatibility reasons. */ -spinlock :: distinct u32 when ODIN_OS == .Windows { thread :: distinct rawptr @@ -297,69 +301,6 @@ when ODIN_OS == .Windows { } } - -@(default_calling_convention="c", link_prefix="ma_") -foreign lib { - /* - Locks a spinlock. - */ - spinlock_lock :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- - - /* - Locks a spinlock, but does not yield() when looping. - */ - spinlock_lock_noyield :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- - - /* - Unlocks a spinlock. - */ - spinlock_unlock :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- - - - /* - Creates a mutex. - - A mutex must be created from a valid context. A mutex is initially unlocked. - */ - mutex_init :: proc(pMutex: ^mutex) -> result --- - - /* - Deletes a mutex. - */ - mutex_uninit :: proc(pMutex: ^mutex) --- - - /* - Locks a mutex with an infinite timeout. - */ - mutex_lock :: proc(pMutex: ^mutex) --- - - /* - Unlocks a mutex. - */ - mutex_unlock :: proc(pMutex: ^mutex) --- - - - /* - Initializes an auto-reset event. - */ - event_init :: proc(pEvent: ^event) -> result --- - - /* - Uninitializes an auto-reset event. - */ - event_uninit :: proc(pEvent: ^event) --- - - /* - Waits for the specified auto-reset event to become signalled. - */ - event_wait :: proc(pEvent: ^event) -> result --- - - /* - Signals the specified auto-reset event. - */ - event_signal :: proc(pEvent: ^event) -> result --- -} - } /* NO_THREADING */ @@ -385,17 +326,22 @@ foreign lib { result_description :: proc(result: result) -> cstring --- /* - malloc(). Calls MA_MALLOC(). + malloc() */ malloc :: proc(sz: c.size_t, pAllocationCallbacks: ^allocation_callbacks) -> rawptr --- /* - realloc(). Calls MA_REALLOC(). + calloc() + */ + calloc :: proc(sz: c.size_t, pAllocationCallbacks: ^allocation_callbacks) -> rawptr --- + + /* + realloc() */ realloc :: proc(p: rawptr, sz: c.size_t, pAllocationCallbacks: ^allocation_callbacks) -> rawptr --- /* - free(). Calls MA_FREE(). + free() */ free :: proc(p: rawptr, pAllocationCallbacks: ^allocation_callbacks) --- @@ -417,7 +363,7 @@ foreign lib { /* Blends two frames in floating point format. */ - blend_f32 :: proc(pOut, pInA, pInB: ^f32, factor: f32, channels: u32) --- + blend_f32 :: proc(pOut, pInA, pInB: [^]f32, factor: f32, channels: u32) --- /* Retrieves the size of a sample in bytes for the given format. diff --git a/vendor/miniaudio/data_conversion.odin b/vendor/miniaudio/data_conversion.odin index 7167270a1..ffcf2fcb3 100644 --- a/vendor/miniaudio/data_conversion.odin +++ b/vendor/miniaudio/data_conversion.odin @@ -36,77 +36,106 @@ linear_resampler_config :: struct { } linear_resampler :: struct { - config: linear_resampler_config, + config: linear_resampler_config, inAdvanceInt: u32, inAdvanceFrac: u32, inTimeInt: u32, inTimeFrac: u32, x0: struct #raw_union { - f32: [MAX_CHANNELS]f32, - s16: [MAX_CHANNELS]i16, + f32: [^]f32, + s16: [^]i16, }, /* The previous input frame. */ x1: struct #raw_union { - f32: [MAX_CHANNELS]f32, - s16: [MAX_CHANNELS]i16, + f32: [^]f32, + s16: [^]i16, }, /* The next input frame. */ lpf: lpf, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, +} + +resampling_backend :: struct {} +resampling_backend_vtable :: struct { + onGetHeapSize: proc "c" (pUserData: rawptr, pConfig: ^resampler_config, pHeapSizeInBytes: ^c.size_t) -> result, + onInit: proc "c" (pUserData: rawptr, pConfig: ^resampler_config, pHeap: rawptr, ppBackend: ^^resampling_backend) -> result, + onUninit: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, pAllocationCallbacks: ^allocation_callbacks), + onProcess: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, pFramesIn: rawptr, pFrameCountIn: ^u64, pFramesOut: rawptr, pFrameCountOut: ^u64) -> result, + onSetRate: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, sampleRateIn: u32, sampleRateOut: u32) -> result, /* Optional. Rate changes will be disabled. */ + onGetInputLatency: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend) -> u64, /* Optional. Latency will be reported as 0. */ + onGetOutputLatency: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend) -> u64, /* Optional. Latency will be reported as 0. */ + onGetRequiredInputFrameCount: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, outputFrameCount: u64, pInputFrameCount: ^u64) -> result, /* Optional. Latency mitigation will be disabled. */ + onGetExpectedOutputFrameCount: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend, inputFrameCount: u64, pOutputFrameCount: ^u64) -> result, /* Optional. Latency mitigation will be disabled. */ + onReset: proc "c" (pUserData: rawptr, pBackend: ^resampling_backend) -> result, } resample_algorithm :: enum { linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ - speex, + custom, } resampler_config :: struct { - format: format, /* Must be either ma_format_f32 or ma_format_s16. */ - channels: u32, - sampleRateIn: u32, - sampleRateOut: u32, - algorithm: resample_algorithm, + format: format, /* Must be either ma_format_f32 or ma_format_s16. */ + channels: u32, + sampleRateIn: u32, + sampleRateOut: u32, + algorithm: resample_algorithm, /* When set to ma_resample_algorithm_custom, pBackendVTable will be used. */ + pBackendVTable: ^resampling_backend_vtable, + pBackendUserData: rawptr, linear: struct { lpfOrder: u32, - lpfNyquistFactor: f64, - }, - speex: struct { - quality: c.int, /* 0 to 10. Defaults to 3. */ }, } resampler :: struct { - config: resampler_config, + pBackend: ^resampling_backend, + pBackendVTable: ^resampling_backend_vtable, + pBackendUserData: rawptr, + format: format, + channels: u32, + sampleRateIn: u32, + sampleRateOut: u32, state: struct #raw_union { linear: linear_resampler, - speex: struct { - pSpeexResamplerState: rawptr, /* SpeexResamplerState* */ - }, - }, + }, /* State for stock resamplers so we can avoid a malloc. For stock resamplers, pBackend will point here. */ + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { linear_resampler_config_init :: proc(format: format, channels: u32, sampleRateIn, sampleRateOut: u32) -> linear_resampler_config --- - linear_resampler_init :: proc(pConfig: ^linear_resampler_config, pResampler: ^linear_resampler) -> result --- - linear_resampler_uninit :: proc(pResampler: ^linear_resampler) --- + linear_resampler_get_heap_size :: proc(pConfig: ^linear_resampler_config, pHeapSizeInBytes: ^c.size_t) -> result --- + linear_resampler_init_preallocated :: proc(pConfig: ^linear_resampler_config, pHeap: rawptr, pResampler: ^linear_resampler) -> result --- + linear_resampler_init :: proc(pConfig: ^linear_resampler_config, pAllocationCallbacks: ^allocation_callbacks, pResampler: ^linear_resampler) -> result --- + linear_resampler_uninit :: proc(pResampler: ^linear_resampler, pAllocationCallbacks: ^allocation_callbacks) --- linear_resampler_process_pcm_frames :: proc(pResampler: ^linear_resampler, pFramesIn: rawptr, pFrameCountIn: ^u64, pFramesOut: rawptr, pFrameCountOut: ^u64) -> result --- linear_resampler_set_rate :: proc(pResampler: ^linear_resampler, sampleRateIn, sampleRateOut: u32) -> result --- linear_resampler_set_rate_ratio :: proc(pResampler: ^linear_resampler, ratioInOut: f32) -> result --- - linear_resampler_get_required_input_frame_count :: proc(pResampler: ^linear_resampler, outputFrameCount: u64) -> u64 --- - linear_resampler_get_expected_output_frame_count :: proc(pResampler: ^linear_resampler, inputFrameCount: u64) -> u64 --- linear_resampler_get_input_latency :: proc(pResampler: ^linear_resampler) -> u64 --- linear_resampler_get_output_latency :: proc(pResampler: ^linear_resampler) -> u64 --- + linear_resampler_get_required_input_frame_count :: proc(pResampler: ^linear_resampler, outputFrameCount: u64, pInputFrameCount: ^u64) -> result --- + linear_resampler_get_expected_output_frame_count :: proc(pResampler: ^linear_resampler, inputFrameCount: u64, pOutputFrameCount: ^u64) -> result --- + linear_resampler_reset :: proc(pResampler: ^linear_resampler) -> result --- resampler_config_init :: proc(format: format, channels: u32, sampleRateIn, sampleRateOut: u32, algorithm: resample_algorithm) -> resampler_config --- + resampler_get_heap_size :: proc(pConfig: ^resampler_config, pHeapSizeInBytes: ^c.size_t) -> result --- + resampler_init_preallocated :: proc(pConfig: ^resampler_config, pHeap: rawptr, pResampler: ^resampler) -> result --- + /* Initializes a new resampler object from a config. */ - resampler_init :: proc(pConfig: ^resampler_config, pResampler: ^resampler) -> result --- + resampler_init :: proc(pConfig: ^resampler_config, pAllocationCallbacks: ^allocation_callbacks, pResampler: ^resampler) -> result --- /* Uninitializes a resampler. */ - resampler_uninit :: proc(pResampler: ^resampler) --- + resampler_uninit :: proc(pResampler: ^resampler, pAllocationCallbacks: ^allocation_callbacks) --- /* Converts the given input data. @@ -145,23 +174,6 @@ foreign lib { */ resampler_set_rate_ratio :: proc(pResampler: ^resampler, ratio: f32) -> result --- - - /* - Calculates the number of whole input frames that would need to be read from the client in order to output the specified - number of output frames. - - The returned value does not include cached input frames. It only returns the number of extra frames that would need to be - read from the input buffer in order to output the specified number of output frames. - */ - resampler_get_required_input_frame_count :: proc(pResampler: ^resampler, outputFrameCount: u64) -> u64 --- - - /* - Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of - input frames. - */ - resampler_get_expected_output_frame_count :: proc(pResampler: ^resampler, inputFrameCount: u64) -> u64 --- - - /* Retrieves the latency introduced by the resampler in input frames. */ @@ -171,6 +183,26 @@ foreign lib { Retrieves the latency introduced by the resampler in output frames. */ resampler_get_output_latency :: proc(pResampler: ^resampler) -> u64 --- + + /* + Calculates the number of whole input frames that would need to be read from the client in order to output the specified + number of output frames. + + The returned value does not include cached input frames. It only returns the number of extra frames that would need to be + read from the input buffer in order to output the specified number of output frames. + */ + resampler_get_required_input_frame_count :: proc(pResampler: ^resampler, outputFrameCount: u64, pInputFrameCount: ^u64) -> result --- + + /* + Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of + input frames. + */ + resampler_get_expected_output_frame_count :: proc(pResampler: ^resampler, inputFrameCount: u64, pOutputFrameCount: ^u64) -> result --- + + /* + Resets the resampler's timer and clears it's internal cache. + */ + resampler_reset :: proc(pResampler: ^resampler) -> result --- } @@ -179,42 +211,63 @@ foreign lib { Channel Conversion **************************************************************************************************************************************************************/ +channel_conversion_path :: enum c.int { + unknown, + passthrough, + mono_out, /* Converting to mono. */ + mono_in, /* Converting from mono. */ + shuffle, /* Simple shuffle. Will use this when all channels are present in both input and output channel maps, but just in a different order. */ + weights, /* Blended based on weights. */ +} + +mono_expansion_mode :: enum c.int { + duplicate = 0, /* The default. */ + average, /* Average the mono channel across all channels. */ + stereo_only, /* Duplicate to the left and right channels only and ignore the others. */ + default = duplicate, +} + channel_converter_config :: struct { - format: format, - channelsIn: u32, - channelsOut: u32, - channelMapIn: [MAX_CHANNELS]channel, - channelMapOut: [MAX_CHANNELS]channel, - mixingMode: channel_mix_mode, - weights: [MAX_CHANNELS][MAX_CHANNELS]f32, /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + format: format, + channelsIn: u32, + channelsOut: u32, + pChannelMapIn: [^]channel, + pChannelMapOut: [^]channel, + mixingMode: channel_mix_mode, + ppWeights: ^[^]f32, /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ } channel_converter :: struct { - format: format, - channelsIn: u32, - channelsOut: u32, - channelMapIn: [MAX_CHANNELS]channel, - channelMapOut: [MAX_CHANNELS]channel, - mixingMode: channel_mix_mode, - weights: struct #raw_union { - f32: [MAX_CHANNELS][MAX_CHANNELS]f32, - s16: [MAX_CHANNELS][MAX_CHANNELS]i32, + format: format, + channelsIn: u32, + channelsOut: u32, + mixingMode: channel_mix_mode, + conversionPath: channel_conversion_path, + pChannelMapIn: [^]channel, + pChannelMapOut: [^]channel, + pShuffleTable: [^]u8, + weights: struct #raw_union { /* [in][out] */ + f32: ^[^]f32, + s16: ^[^]i32, }, - isPassthrough: b8, - isSimpleShuffle: b8, - isSimpleMonoExpansion: b8, - isStereoToMono: b8, - shuffleTable: [MAX_CHANNELS]u8, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { - channel_converter_config_init :: proc(format: format, channelsIn: u32, pChannelMapIn: ^channel, channelsOut: u32, pChannelMapOut: ^channel, mixingMode: channel_mix_mode) -> channel_converter_config --- + channel_converter_config_init :: proc(format: format, channelsIn: u32, pChannelMapIn: [^]channel, channelsOut: u32, pChannelMapOut: [^]channel, mixingMode: channel_mix_mode) -> channel_converter_config --- - channel_converter_init :: proc(pConfig: ^channel_converter_config, pConverter: ^channel_converter) -> result --- - channel_converter_uninit :: proc(pConverter: ^channel_converter) --- - channel_converter_process_pcm_frames :: proc(pConverter: ^channel_converter, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- + channel_converter_get_heap_size :: proc(pConfig: ^channel_converter_config, pHeapSizeInBytes: ^c.size_t) -> result --- + channel_converter_init_preallocated :: proc(pConfig: ^channel_converter_config, pHeap: rawptr, pConverter: ^channel_converter) -> result --- + channel_converter_init :: proc(pConfig: ^channel_converter_config, pAllocationCallbacks: ^allocation_callbacks, pConverter: ^channel_converter) -> result --- + channel_converter_uninit :: proc(pConverter: ^channel_converter, pAllocationCallbacks: ^allocation_callbacks) --- + channel_converter_process_pcm_frames :: proc(pConverter: ^channel_converter, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- + channel_converter_get_input_channel_map :: proc(pConverter: ^channel_converter, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + channel_converter_get_output_channel_map :: proc(pConverter: ^channel_converter, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- } @@ -224,32 +277,39 @@ Data Conversion **************************************************************************************************************************************************************/ data_converter_config :: struct { - formatIn: format, - formatOut: format, - channelsIn: u32, - channelsOut: u32, - sampleRateIn: u32, - sampleRateOut: u32, - channelMapIn: [MAX_CHANNELS]channel, - channelMapOut: [MAX_CHANNELS]channel, - ditherMode: dither_mode, - channelMixMode: channel_mix_mode, - channelWeights: [MAX_CHANNELS][MAX_CHANNELS]f32, /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */ - resampling: struct { - algorithm: resample_algorithm, - allowDynamicSampleRate: b32, - linear: struct { - lpfOrderL: u32, - lpfNyquistFactor: f64, - }, - speex: struct { - quality: c.int, - }, - }, + formatIn: format, + formatOut: format, + channelsIn: u32, + channelsOut: u32, + sampleRateIn: u32, + sampleRateOut: u32, + pChannelMapIn: [^]channel, + pChannelMapOut: [^]channel, + ditherMode: dither_mode, + channelMixMode: channel_mix_mode, + ppChannelWeights: ^[^]f32, /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */ + allowDynamicSampleRate: b32, + resampling: resampler_config, +} + +data_converter_execution_path :: enum c.int { + passthrough, /* No conversion. */ + format_only, /* Only format conversion. */ + channels_only, /* Only channel conversion. */ + resample_only, /* Only resampling. */ + resample_first, /* All conversions, but resample as the first step. */ + channels_first, /* All conversions, but channels as the first step. */ } data_converter :: struct { - config: data_converter_config, + formatIn: format, + formatOut: format, + channelsIn: u32, + channelsOut: u32, + sampleRateIn: u32, + sampleRateOut: u32, + ditherMode: dither_mode, + executionPath: data_converter_execution_path, /* The execution path the data converter will follow when processing. */ channelConverter: channel_converter, resampler: resampler, hasPreFormatConversion: b8, @@ -257,6 +317,10 @@ data_converter :: struct { hasChannelConverter: b8, hasResampler: b8, isPassthrough: b8, + + /* Memory management. */ + _ownsHeap: b8, + _pHeap: rawptr, } @@ -265,15 +329,20 @@ foreign lib { data_converter_config_init_default :: proc() -> data_converter_config --- data_converter_config_init :: proc(formatIn, formatOut: format, channelsIn, channelsOut: u32, sampleRateIn, sampleRateOut: u32) -> data_converter_config --- - data_converter_init :: proc(pConfig: ^data_converter_config, pConverter: ^data_converter) -> result --- - data_converter_uninit :: proc(pConverter: ^data_converter) --- + data_converter_get_heap_size :: proc(pConfig: ^data_converter_config, pHeapSizeInBytes: ^c.size_t) -> result --- + data_converter_init_preallocated :: proc(pConfig: ^data_converter_config, pHeap: rawptr, pConverter: ^data_converter) -> result --- + data_converter_init :: proc(pConfig: ^data_converter_config, pAllocationCallbacks: ^allocation_callbacks, pConverter: ^data_converter) -> result --- + data_converter_uninit :: proc(pConverter: ^data_converter, pAllocationCallbacks: ^allocation_callbacks) --- data_converter_process_pcm_frames :: proc(pConverter: ^data_converter, pFramesIn: rawptr, pFrameCountIn: ^u64, pFramesOut: rawptr, pFrameCountOut: ^u64) -> result --- data_converter_set_rate :: proc(pConverter: ^data_converter, sampleRateIn, sampleRateOut: u32) -> result --- data_converter_set_rate_ratio :: proc(pConverter: ^data_converter, ratioInOut: f32) -> result --- - data_converter_get_required_input_frame_count :: proc(pConverter: ^data_converter, outputFrameCount: u64) -> u64 --- - data_converter_get_expected_output_frame_count :: proc(pConverter: ^data_converter, inputFrameCount: u64) -> u64 --- data_converter_get_input_latency :: proc(pConverter: ^data_converter) -> u64 --- data_converter_get_output_latency :: proc(pConverter: ^data_converter) -> u64 --- + data_converter_get_required_input_frame_count :: proc(pConverter: ^data_converter, outputFrameCount: u64, pInputFrameCount: ^u64) -> result --- + data_converter_get_expected_output_frame_count :: proc(pConverter: ^data_converter, inputFrameCount: u64, pOutputFrameCount: ^u64) -> result --- + data_converter_get_input_channel_map :: proc(pConverter: ^data_converter, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + data_converter_get_output_channel_map :: proc(pConverter: ^data_converter, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + data_converter_reset :: proc(pConverter: ^data_converter) -> result --- } /************************************************************************************************************************************************************ @@ -332,43 +401,40 @@ CHANNEL_INDEX_NULL :: 255 @(default_calling_convention="c", link_prefix="ma_") foreign lib { - /* Retrieves the channel position of the specified channel based on miniaudio's default channel map. */ - channel_map_get_default_channel :: proc(channelCount: u32, channelIndex: u32) -> channel --- - /* Retrieves the channel position of the specified channel in the given channel map. The pChannelMap parameter can be null, in which case miniaudio's default channel map will be assumed. */ - channel_map_get_channel :: proc(pChannelMap: ^channel, channelCount: u32, channelIndex: u32) -> channel --- + channel_map_get_channel :: proc(pChannelMap: [^]channel, channelCount: u32, channelIndex: u32) -> channel --- /* Initializes a blank channel map. When a blank channel map is specified anywhere it indicates that the native channel map should be used. */ - channel_map_init_blank :: proc(channels: u32, pChannelMap: ^channel) --- + channel_map_init_blank :: proc(pChannelMap: [^]channel, channels: u32) --- /* Helper for retrieving a standard channel map. - The output channel map buffer must have a capacity of at least `channels`. + The output channel map buffer must have a capacity of at least `channelMapCap`. */ - get_standard_channel_map :: proc(standardChannelMap: standard_channel_map, channels: u32, pChannelMap: ^channel) --- + channel_map_init_standard :: proc(standardChannelMap: standard_channel_map, pChannelMap: [^]channel, channelMapCap: c.size_t, channels: u32) --- /* Copies a channel map. Both input and output channel map buffers must have a capacity of at at least `channels`. */ - channel_map_copy :: proc(pOut: ^channel, pIn: ^channel, channels: u32) --- + channel_map_copy :: proc(pOut: [^]channel, pIn: [^]channel, channels: u32) --- /* Copies a channel map if one is specified, otherwise copies the default channel map. The output buffer must have a capacity of at least `channels`. If not NULL, the input channel map must also have a capacity of at least `channels`. */ - channel_map_copy_or_default :: proc(pOut: ^channel, pIn: ^channel, channels: u32) --- + channel_map_copy_or_default :: proc(pOut: [^]channel, channelMapCapOut: c.size_t, pIn: [^]channel, channels: u32) --- /* @@ -378,12 +444,12 @@ foreign lib { is usually treated as a passthrough. Invalid channel maps: - - A channel map with no channels - - A channel map with more than one channel and a mono channel + - A channel map with no channels + - A channel map with more than one channel and a mono channel The channel map buffer must have a capacity of at least `channels`. */ - channel_map_valid :: proc(channels: u32, pChannelMap: ^channel) -> b32 --- + channel_map_is_valid :: proc(pChannelMap: [^]channel, channels: u32) -> b32 --- /* Helper for comparing two channel maps for equality. @@ -392,23 +458,24 @@ foreign lib { Both channels map buffers must have a capacity of at least `channels`. */ - channel_map_equal :: proc(channels: u32, pChannelMapA, pChannelMapB: ^channel) -> b32 --- + channel_map_is_equal :: proc(pChannelMapA, pChannelMapB: [^]channel, channels: u32) -> b32 --- /* Helper for determining if a channel map is blank (all channels set to MA_CHANNEL_NONE). The channel map buffer must have a capacity of at least `channels`. */ - channel_map_blank :: proc(channels: u32, pChannelMap: ^channel) -> b32 --- + channel_map_is_blank :: proc(pChannelMap: [^]channel, channels: u32) -> b32 --- /* Helper for determining whether or not a channel is present in the given channel map. The channel map buffer must have a capacity of at least `channels`. */ - channel_map_contains_channel_position :: proc(channels: u32, pChannelMap: ^channel, channelPosition: channel) -> b32 --- + channel_map_contains_channel_position :: proc(channels: u32, pChannelMap: [^]channel, channelPosition: channel) -> b32 --- } + /************************************************************************************************************************************************************ Conversion Helpers @@ -461,9 +528,9 @@ foreign lib { rb_uninit :: proc(pRB: ^rb) --- rb_reset :: proc(pRB: ^rb) --- rb_acquire_read :: proc(pRB: ^rb, pSizeInBytes: ^c.size_t, ppBufferOut: ^rawptr) -> result --- - rb_commit_read :: proc(pRB: ^rb, sizeInBytes: c.size_t, pBufferOut: rawptr) -> result --- + rb_commit_read :: proc(pRB: ^rb, sizeInBytes: c.size_t) -> result --- rb_acquire_write :: proc(pRB: ^rb, pSizeInBytes: ^c.size_t, ppBufferOut: ^rawptr) -> result --- - rb_commit_write :: proc(pRB: ^rb, sizeInBytes: c.size_t, pBufferOut: rawptr) -> result --- + rb_commit_write :: proc(pRB: ^rb, sizeInBytes: c.size_t) -> result --- rb_seek_read :: proc(pRB: ^rb, offsetInBytes: c.size_t) -> result --- rb_seek_write :: proc(pRB: ^rb, offsetInBytes: c.size_t) -> result --- rb_pointer_distance :: proc(pRB: ^rb) -> i32 --- /* Returns the distance between the write pointer and the read pointer. Should never be negative for a correct program. Will return the number of bytes that can be read before the read pointer hits the write pointer. */ diff --git a/vendor/miniaudio/decoding.odin b/vendor/miniaudio/decoding.odin index dcf3b7a1a..003f6f950 100644 --- a/vendor/miniaudio/decoding.odin +++ b/vendor/miniaudio/decoding.odin @@ -22,68 +22,63 @@ you do your own synchronization. decoding_backend_config :: struct { preferredFormat: format, + seekPointCount: u32, /* Set to > 0 to generate a seektable if the decoding backend supports it. */ } @(default_calling_convention="c", link_prefix="ma_") foreign lib { - decoding_backend_config_init :: proc(preferredFormat: format) -> decoding_backend_config --- + decoding_backend_config_init :: proc(preferredFormat: format, seekPointCount: u32) -> decoding_backend_config --- } decoding_backend_vtable :: struct { onInit: proc "c" (pUserData: rawptr, onRead: decoder_read_proc, onSeek: decoder_seek_proc, onTell: decoder_tell_proc, pReadSeekTellUserData: rawptr, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, - onInitFile: proc "c" (pUserData: rawptr, pFilePath: cstring, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, /* Optional. */ + onInitFile: proc "c" (pUserData: rawptr, pFilePath: cstring, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, /* Optional. */ onInitFileW: proc "c" (pUserData: rawptr, pFilePath: [^]c.wchar_t, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, /* Optional. */ onInitMemory: proc "c" (pUserData: rawptr, pData: rawptr, dataSize: c.size_t, pConfig: ^decoding_backend_config, pAllocationCallbacks: ^allocation_callbacks, ppBackend: ^^data_source) -> result, /* Optional. */ onUninit: proc "c" (pUserData: rawptr, pBackend: ^data_source, pAllocationCallbacks: ^allocation_callbacks), - onGetChannelMap: proc "c" (pUserData: rawptr, pBackend: ^data_source, pChannelMap: ^channel, channelMapCap: c.size_t) -> result, } -/* TODO: Convert read and seek to be consistent with the VFS API (ma_result return value, bytes read moved to an output parameter). */ -decoder_read_proc :: proc "c" (pDecoder: ^decoder, pBufferOut: rawptr, bytesToRead: c.size_t) -> c.size_t /* Returns the number of bytes read. */ -decoder_seek_proc :: proc "c" (pDecoder: ^decoder, byteOffset: i64, origin: seek_origin) -> b32 +decoder_read_proc :: proc "c" (pDecoder: ^decoder, pBufferOut: rawptr, bytesToRead: c.size_t, pBytesRead: ^c.size_t) -> result /* Returns the number of bytes read. */ +decoder_seek_proc :: proc "c" (pDecoder: ^decoder, byteOffset: i64, origin: seek_origin) -> result decoder_tell_proc :: proc "c" (pDecoder: ^decoder, pCursor: ^i64) -> result decoder_config :: struct { - format: format, /* Set to 0 or ma_format_unknown to use the stream's internal format. */ - channels: u32, /* Set to 0 to use the stream's internal channels. */ - sampleRate: u32, /* Set to 0 to use the stream's internal sample rate. */ - channelMap: [MAX_CHANNELS]channel, - channelMixMode: channel_mix_mode, - ditherMode: dither_mode, - resampling: struct { - algorithm: resample_algorithm, - linear: struct { - lpfOrder: u32, - }, - speex: struct { - quality: c.int, - }, - }, + format: format, /* Set to 0 or ma_format_unknown to use the stream's internal format. */ + channels: u32, /* Set to 0 to use the stream's internal channels. */ + sampleRate: u32, /* Set to 0 to use the stream's internal sample rate. */ + channelMap: [^]channel, + channelMixMode: channel_mix_mode, + ditherMode: dither_mode, + resampling: resampler_config, allocationCallbacks: allocation_callbacks, encodingFormat: encoding_format, - ppCustomBackendVTables: ^^decoding_backend_vtable, + seekPointCount: u32, /* When set to > 0, specifies the number of seek points to use for the generation of a seek table. Not all decoding backends support this. */ + ppCustomBackendVTables: ^[^]decoding_backend_vtable, customBackendCount: u32, pCustomBackendUserData: rawptr, } decoder :: struct { - ds: data_source_base, - pBackend: ^data_source, /* The decoding backend we'll be pulling data from. */ - pBackendVTable: ^^decoding_backend_vtable, /* The vtable for the decoding backend. This needs to be stored so we can access the onUninit() callback. */ - pBackendUserData: rawptr, - onRead: decoder_read_proc, - onSeek: decoder_seek_proc, - onTell: decoder_tell_proc, - pUserData: rawptr, + ds: data_source_base, + pBackend: ^data_source, /* The decoding backend we'll be pulling data from. */ + pBackendVTable: ^decoding_backend_vtable, /* The vtable for the decoding backend. This needs to be stored so we can access the onUninit() callback. */ + pBackendUserData: rawptr, + onRead: decoder_read_proc, + onSeek: decoder_seek_proc, + onTell: decoder_tell_proc, + pUserData: rawptr, readPointerInPCMFrames: u64, /* In output sample rate. Used for keeping track of how many frames are available for decoding. */ - outputFormat: format, - outputChannels: u32, - outputSampleRate: u32, - outputChannelMap: [MAX_CHANNELS]channel, - converter: data_converter, /* <-- Data conversion is achieved by running frames through this. */ - allocationCallbacks: allocation_callbacks, + outputFormat: format, + outputChannels: u32, + outputSampleRate: u32, + converter: data_converter, /* <-- Data conversion is achieved by running frames through this. */ + pInputCache: rawptr, /* In input format. Can be null if it's not needed. */ + inputCacheCap: u64, /* The capacity of the input cache. */ + inputCacheConsumed: u64, /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ + inputCacheRemaining: u64, /* The number of valid frames remaining in the cahce. */ + allocationCallbacks: allocation_callbacks, data: struct #raw_union { vfs: struct { pVFS: ^vfs, @@ -114,6 +109,25 @@ foreign lib { */ decoder_uninit :: proc(pDecoder: ^decoder) -> result --- + /* + Reads PCM frames from the given decoder. + + This is not thread safe without your own synchronization. + */ + decoder_read_pcm_frames :: proc(pDecoder: ^decoder, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + + /* + Seeks to a PCM frame based on it's absolute index. + + This is not thread safe without your own synchronization. + */ + decoder_seek_to_pcm_frame :: proc(pDecoder: ^decoder, frameIndex: u64) -> result --- + + /* + Retrieves the decoder's output data format. + */ + decoder_get_data_format :: proc(pDecoder: ^decoder, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: ^channel, channelMapCap: c.size_t) -> result --- + /* Retrieves the current position of the read cursor in PCM frames. */ @@ -133,21 +147,7 @@ foreign lib { This function is not thread safe without your own synchronization. */ - decoder_get_length_in_pcm_frames :: proc(pDecoder: ^decoder) -> u64 --- - - /* - Reads PCM frames from the given decoder. - - This is not thread safe without your own synchronization. - */ - decoder_read_pcm_frames :: proc(pDecoder: ^decoder, pFramesOut: rawptr, frameCount: u64) -> u64 --- - - /* - Seeks to a PCM frame based on it's absolute index. - - This is not thread safe without your own synchronization. - */ - decoder_seek_to_pcm_frame :: proc(pDecoder: ^decoder, frameIndex: u64) -> result --- + decoder_get_length_in_pcm_frames :: proc(pDecoder: ^decoder, pLength: ^u64) -> result --- /* Retrieves the number of frames that can be read before reaching the end. diff --git a/vendor/miniaudio/device_io_procs.odin b/vendor/miniaudio/device_io_procs.odin index 7cff3f621..de60645e4 100644 --- a/vendor/miniaudio/device_io_procs.odin +++ b/vendor/miniaudio/device_io_procs.odin @@ -12,6 +12,12 @@ import "core:c" @(default_calling_convention="c", link_prefix="ma_") foreign lib { + device_job_thread_config_init :: proc() -> device_job_thread_config --- + + device_job_thread_init :: proc(pConfig: ^device_job_thread_config, pAllocationCallbacks: ^allocation_callbacks, pJobThread: ^device_job_thread) -> result --- + device_job_thread_uninit :: proc(pJobThread: ^device_job_thread, pAllocationCallbacks: ^allocation_callbacks) --- + device_job_thread_post :: proc(pJobThread: ^device_job_thread, pJob: ^job) -> result --- + device_job_thread_next :: proc(pJobThread: ^device_job_thread, pJob: ^job) -> result --- /* Initializes a `ma_context_config` object. @@ -46,16 +52,16 @@ foreign lib { Parameters ---------- backends (in, optional) - A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. + A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. backendCount (in, optional) - The number of items in `backend`. Ignored if `backend` is NULL. + The number of items in `backend`. Ignored if `backend` is NULL. pConfig (in, optional) - The context configuration. + The context configuration. pContext (in) - A pointer to the context object being initialized. + A pointer to the context object being initialized. Return Value @@ -72,107 +78,116 @@ foreign lib { ------- When `backends` is NULL, the default priority order will be used. Below is a list of backends in priority order: - |-------------|-----------------------|--------------------------------------------------------| - | Name | Enum Name | Supported Operating Systems | - |-------------|-----------------------|--------------------------------------------------------| - | WASAPI | ma_backend_wasapi | Windows Vista+ | - | DirectSound | ma_backend_dsound | Windows XP+ | - | WinMM | ma_backend_winmm | Windows XP+ (may work on older versions, but untested) | - | Core Audio | ma_backend_coreaudio | macOS, iOS | - | ALSA | ma_backend_alsa | Linux | - | PulseAudio | ma_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | - | JACK | ma_backend_jack | Cross Platform (disabled on BSD and Android) | - | sndio | ma_backend_sndio | OpenBSD | - | audio(4) | ma_backend_audio4 | NetBSD, OpenBSD | - | OSS | ma_backend_oss | FreeBSD | - | AAudio | ma_backend_aaudio | Android 8+ | - | OpenSL|ES | ma_backend_opensl | Android (API level 16+) | - | Web Audio | ma_backend_webaudio | Web (via Emscripten) | - | Null | ma_backend_null | Cross Platform (not used on Web) | - |-------------|-----------------------|--------------------------------------------------------| + |-------------|-----------------------|--------------------------------------------------------| + | Name | Enum Name | Supported Operating Systems | + |-------------|-----------------------|--------------------------------------------------------| + | WASAPI | ma_backend_wasapi | Windows Vista+ | + | DirectSound | ma_backend_dsound | Windows XP+ | + | WinMM | ma_backend_winmm | Windows XP+ (may work on older versions, but untested) | + | Core Audio | ma_backend_coreaudio | macOS, iOS | + | ALSA | ma_backend_alsa | Linux | + | PulseAudio | ma_backend_pulseaudio | Cross Platform (disabled on Windows, BSD and Android) | + | JACK | ma_backend_jack | Cross Platform (disabled on BSD and Android) | + | sndio | ma_backend_sndio | OpenBSD | + | audio(4) | ma_backend_audio4 | NetBSD, OpenBSD | + | OSS | ma_backend_oss | FreeBSD | + | AAudio | ma_backend_aaudio | Android 8+ | + | OpenSL|ES | ma_backend_opensl | Android (API level 16+) | + | Web Audio | ma_backend_webaudio | Web (via Emscripten) | + | Null | ma_backend_null | Cross Platform (not used on Web) | + |-------------|-----------------------|--------------------------------------------------------| The context can be configured via the `pConfig` argument. The config object is initialized with `ma_context_config_init()`. Individual configuration settings can then be set directly on the structure. Below are the members of the `ma_context_config` object. - pLog - A pointer to the `ma_log` to post log messages to. Can be NULL if the application does not - require logging. See the `ma_log` API for details on how to use the logging system. + pLog + A pointer to the `ma_log` to post log messages to. Can be NULL if the application does not + require logging. See the `ma_log` API for details on how to use the logging system. - threadPriority - The desired priority to use for the audio thread. Allowable values include the following: + threadPriority + The desired priority to use for the audio thread. Allowable values include the following: - |--------------------------------------| - | Thread Priority | - |--------------------------------------| - | ma_thread_priority_idle | - | ma_thread_priority_lowest | - | ma_thread_priority_low | - | ma_thread_priority_normal | - | ma_thread_priority_high | - | ma_thread_priority_highest (default) | - | ma_thread_priority_realtime | - | ma_thread_priority_default | - |--------------------------------------| + |--------------------------------------| + | Thread Priority | + |--------------------------------------| + | ma_thread_priority_idle | + | ma_thread_priority_lowest | + | ma_thread_priority_low | + | ma_thread_priority_normal | + | ma_thread_priority_high | + | ma_thread_priority_highest (default) | + | ma_thread_priority_realtime | + | ma_thread_priority_default | + |--------------------------------------| - pUserData - A pointer to application-defined data. This can be accessed from the context object directly such as `context.pUserData`. + threadStackSize + The desired size of the stack for the audio thread. Defaults to the operating system's default. - allocationCallbacks - Structure containing custom allocation callbacks. Leaving this at defaults will cause it to use MA_MALLOC, MA_REALLOC and MA_FREE. These allocation - callbacks will be used for anything tied to the context, including devices. + pUserData + A pointer to application-defined data. This can be accessed from the context object directly such as `context.pUserData`. - alsa.useVerboseDeviceEnumeration - ALSA will typically enumerate many different devices which can be intrusive and not user-friendly. To combat this, miniaudio will enumerate only unique - card/device pairs by default. The problem with this is that you lose a bit of flexibility and control. Setting alsa.useVerboseDeviceEnumeration makes - it so the ALSA backend includes all devices. Defaults to false. + allocationCallbacks + Structure containing custom allocation callbacks. Leaving this at defaults will cause it to use MA_MALLOC, MA_REALLOC and MA_FREE. These allocation + callbacks will be used for anything tied to the context, including devices. - pulse.pApplicationName - PulseAudio only. The application name to use when initializing the PulseAudio context with `pa_context_new()`. + alsa.useVerboseDeviceEnumeration + ALSA will typically enumerate many different devices which can be intrusive and not user-friendly. To combat this, miniaudio will enumerate only unique + card/device pairs by default. The problem with this is that you lose a bit of flexibility and control. Setting alsa.useVerboseDeviceEnumeration makes + it so the ALSA backend includes all devices. Defaults to false. - pulse.pServerName - PulseAudio only. The name of the server to connect to with `pa_context_connect()`. + pulse.pApplicationName + PulseAudio only. The application name to use when initializing the PulseAudio context with `pa_context_new()`. - pulse.tryAutoSpawn - PulseAudio only. Whether or not to try automatically starting the PulseAudio daemon. Defaults to false. If you set this to true, keep in mind that - miniaudio uses a trial and error method to find the most appropriate backend, and this will result in the PulseAudio daemon starting which may be - intrusive for the end user. + pulse.pServerName + PulseAudio only. The name of the server to connect to with `pa_context_connect()`. - coreaudio.sessionCategory - iOS only. The session category to use for the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. + pulse.tryAutoSpawn + PulseAudio only. Whether or not to try automatically starting the PulseAudio daemon. Defaults to false. If you set this to true, keep in mind that + miniaudio uses a trial and error method to find the most appropriate backend, and this will result in the PulseAudio daemon starting which may be + intrusive for the end user. - |-----------------------------------------|-------------------------------------| - | miniaudio Token | Core Audio Token | - |-----------------------------------------|-------------------------------------| - | ma_ios_session_category_ambient | AVAudioSessionCategoryAmbient | - | ma_ios_session_category_solo_ambient | AVAudioSessionCategorySoloAmbient | - | ma_ios_session_category_playback | AVAudioSessionCategoryPlayback | - | ma_ios_session_category_record | AVAudioSessionCategoryRecord | - | ma_ios_session_category_play_and_record | AVAudioSessionCategoryPlayAndRecord | - | ma_ios_session_category_multi_route | AVAudioSessionCategoryMultiRoute | - | ma_ios_session_category_none | AVAudioSessionCategoryAmbient | - | ma_ios_session_category_default | AVAudioSessionCategoryAmbient | - |-----------------------------------------|-------------------------------------| + coreaudio.sessionCategory + iOS only. The session category to use for the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. - coreaudio.sessionCategoryOptions - iOS only. Session category options to use with the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. + |-----------------------------------------|-------------------------------------| + | miniaudio Token | Core Audio Token | + |-----------------------------------------|-------------------------------------| + | ma_ios_session_category_ambient | AVAudioSessionCategoryAmbient | + | ma_ios_session_category_solo_ambient | AVAudioSessionCategorySoloAmbient | + | ma_ios_session_category_playback | AVAudioSessionCategoryPlayback | + | ma_ios_session_category_record | AVAudioSessionCategoryRecord | + | ma_ios_session_category_play_and_record | AVAudioSessionCategoryPlayAndRecord | + | ma_ios_session_category_multi_route | AVAudioSessionCategoryMultiRoute | + | ma_ios_session_category_none | AVAudioSessionCategoryAmbient | + | ma_ios_session_category_default | AVAudioSessionCategoryAmbient | + |-----------------------------------------|-------------------------------------| - |---------------------------------------------------------------------------|------------------------------------------------------------------| - | miniaudio Token | Core Audio Token | - |---------------------------------------------------------------------------|------------------------------------------------------------------| - | ma_ios_session_category_option_mix_with_others | AVAudioSessionCategoryOptionMixWithOthers | - | ma_ios_session_category_option_duck_others | AVAudioSessionCategoryOptionDuckOthers | - | ma_ios_session_category_option_allow_bluetooth | AVAudioSessionCategoryOptionAllowBluetooth | - | ma_ios_session_category_option_default_to_speaker | AVAudioSessionCategoryOptionDefaultToSpeaker | - | ma_ios_session_category_option_interrupt_spoken_audio_and_mix_with_others | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers | - | ma_ios_session_category_option_allow_bluetooth_a2dp | AVAudioSessionCategoryOptionAllowBluetoothA2DP | - | ma_ios_session_category_option_allow_air_play | AVAudioSessionCategoryOptionAllowAirPlay | - |---------------------------------------------------------------------------|------------------------------------------------------------------| + coreaudio.sessionCategoryOptions + iOS only. Session category options to use with the shared AudioSession instance. Below is a list of allowable values and their Core Audio equivalents. - jack.pClientName - The name of the client to pass to `jack_client_open()`. + |---------------------------------------------------------------------------|------------------------------------------------------------------| + | miniaudio Token | Core Audio Token | + |---------------------------------------------------------------------------|------------------------------------------------------------------| + | ma_ios_session_category_option_mix_with_others | AVAudioSessionCategoryOptionMixWithOthers | + | ma_ios_session_category_option_duck_others | AVAudioSessionCategoryOptionDuckOthers | + | ma_ios_session_category_option_allow_bluetooth | AVAudioSessionCategoryOptionAllowBluetooth | + | ma_ios_session_category_option_default_to_speaker | AVAudioSessionCategoryOptionDefaultToSpeaker | + | ma_ios_session_category_option_interrupt_spoken_audio_and_mix_with_others | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers | + | ma_ios_session_category_option_allow_bluetooth_a2dp | AVAudioSessionCategoryOptionAllowBluetoothA2DP | + | ma_ios_session_category_option_allow_air_play | AVAudioSessionCategoryOptionAllowAirPlay | + |---------------------------------------------------------------------------|------------------------------------------------------------------| - jack.tryStartServer - Whether or not to try auto-starting the JACK server. Defaults to false. + coreaudio.noAudioSessionActivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:true] on initialization. + + coreaudio.noAudioSessionDeactivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:false] on uninitialization. + + jack.pClientName + The name of the client to pass to `jack_client_open()`. + + jack.tryStartServer + Whether or not to try auto-starting the JACK server. Defaults to false. It is recommended that only a single context is active at any given time because it's a bulky data structure which performs run-time linking for the @@ -190,7 +205,7 @@ foreign lib { ma_context context; ma_result result = ma_context_init(NULL, 0, NULL, &context); if (result != MA_SUCCESS) { - // Error. + // Error. } ``` @@ -205,24 +220,30 @@ foreign lib { ```c ma_backend backends[] = { - ma_backend_alsa, - ma_backend_pulseaudio, - ma_backend_wasapi, - ma_backend_dsound + ma_backend_alsa, + ma_backend_pulseaudio, + ma_backend_wasapi, + ma_backend_dsound }; + ma_log log; + ma_log_init(&log); + ma_log_register_callback(&log, ma_log_callback_init(my_log_callbac, pMyLogUserData)); + ma_context_config config = ma_context_config_init(); - config.logCallback = my_log_callback; - config.pUserData = pMyUserData; + config.pLog = &log; // Specify a custom log object in the config so any logs that are posted from ma_context_init() are captured. ma_context context; ma_result result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), &config, &context); if (result != MA_SUCCESS) { - // Error. - if (result == MA_NO_BACKEND) { - // Couldn't find an appropriate backend. - } + // Error. + if (result == MA_NO_BACKEND) { + // Couldn't find an appropriate backend. + } } + + // You could also attach a log callback post-initialization: + ma_log_register_callback(ma_context_get_log(&context), ma_log_callback_init(my_log_callback, pMyLogUserData)); ``` @@ -298,13 +319,13 @@ foreign lib { Parameters ---------- pContext (in) - A pointer to the context performing the enumeration. + A pointer to the context performing the enumeration. callback (in) - The callback to fire for each enumerated device. + The callback to fire for each enumerated device. pUserData (in) - A pointer to application-defined data passed to the callback. + A pointer to application-defined data passed to the callback. Return Value @@ -331,15 +352,15 @@ foreign lib { Example 1 - Simple Enumeration ------------------------------ - ma_bool32 ma_device_enum_callback(pContext: ^context_type, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) + ma_bool32 ma_device_enum_callback(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData) { - printf("Device Name: %s\n", pInfo->name); - return MA_TRUE; + printf("Device Name: %s\n", pInfo->name); + return MA_TRUE; } ma_result result = ma_context_enumerate_devices(&context, my_device_enum_callback, pMyUserData); if (result != MA_SUCCESS) { - // Error. + // Error. } @@ -359,19 +380,19 @@ foreign lib { Parameters ---------- pContext (in) - A pointer to the context performing the enumeration. + A pointer to the context performing the enumeration. ppPlaybackDeviceInfos (out) - A pointer to a pointer that will receive the address of a buffer containing the list of `ma_device_info` structures for playback devices. + A pointer to a pointer that will receive the address of a buffer containing the list of `ma_device_info` structures for playback devices. pPlaybackDeviceCount (out) - A pointer to an unsigned integer that will receive the number of playback devices. + A pointer to an unsigned integer that will receive the number of playback devices. ppCaptureDeviceInfos (out) - A pointer to a pointer that will receive the address of a buffer containing the list of `ma_device_info` structures for capture devices. + A pointer to a pointer that will receive the address of a buffer containing the list of `ma_device_info` structures for capture devices. pCaptureDeviceCount (out) - A pointer to an unsigned integer that will receive the number of capture devices. + A pointer to an unsigned integer that will receive the number of capture devices. Return Value @@ -407,20 +428,16 @@ foreign lib { Parameters ---------- pContext (in) - A pointer to the context performing the query. + A pointer to the context performing the query. deviceType (in) - The type of the device being queried. Must be either `ma_device_type_playback` or `ma_device_type_capture`. + The type of the device being queried. Must be either `ma_device_type_playback` or `ma_device_type_capture`. pDeviceID (in) - The ID of the device being queried. - - shareMode (in) - The share mode to query for device capabilities. This should be set to whatever you're intending on using when initializing the device. If you're unsure, - set this to `ma_share_mode_shared`. + The ID of the device being queried. pDeviceInfo (out) - A pointer to the `ma_device_info` structure that will receive the device information. + A pointer to the `ma_device_info` structure that will receive the device information. Return Value @@ -444,7 +461,7 @@ foreign lib { This leaves pDeviceInfo unmodified in the result of an error. */ - context_get_device_info :: proc(pContext: ^context_type, deviceType: device_type, pDeviceID: ^device_id, shareMode: share_mode, pDeviceInfo: ^device_info) -> result --- + context_get_device_info :: proc(pContext: ^context_type, deviceType: device_type, pDeviceID: ^device_id, pDeviceInfo: ^device_info) -> result --- /* Determines if the given context supports loopback mode. @@ -471,16 +488,16 @@ foreign lib { Parameters ---------- deviceType (in) - The type of the device this config is being initialized for. This must set to one of the following: + The type of the device this config is being initialized for. This must set to one of the following: - |-------------------------| - | Device Type | - |-------------------------| - | ma_device_type_playback | - | ma_device_type_capture | - | ma_device_type_duplex | - | ma_device_type_loopback | - |-------------------------| + |-------------------------| + | Device Type | + |-------------------------| + | ma_device_type_playback | + | ma_device_type_capture | + | ma_device_type_duplex | + | ma_device_type_loopback | + |-------------------------| Return Value @@ -554,13 +571,13 @@ foreign lib { Parameters ---------- pContext (in, optional) - A pointer to the context that owns the device. This can be null, in which case it creates a default context internally. + A pointer to the context that owns the device. This can be null, in which case it creates a default context internally. pConfig (in) - A pointer to the device configuration. Cannot be null. See remarks for details. + A pointer to the device configuration. Cannot be null. See remarks for details. pDevice (out) - A pointer to the device object being initialized. + A pointer to the device object being initialized. Return Value @@ -583,9 +600,9 @@ foreign lib { ------- Setting `pContext` to NULL will result in miniaudio creating a default context internally and is equivalent to passing in a context initialized like so: - ```c - ma_context_init(NULL, 0, NULL, &context); - ``` + ```c + ma_context_init(NULL, 0, NULL, &context); + ``` Do not set `pContext` to NULL if you are needing to open multiple devices. You can, however, use NULL when initializing the first device, and then use device.pContext for the initialization of other devices. @@ -593,136 +610,173 @@ foreign lib { The device can be configured via the `pConfig` argument. The config object is initialized with `ma_device_config_init()`. Individual configuration settings can then be set directly on the structure. Below are the members of the `ma_device_config` object. - deviceType - Must be `ma_device_type_playback`, `ma_device_type_capture`, `ma_device_type_duplex` of `ma_device_type_loopback`. + deviceType + Must be `ma_device_type_playback`, `ma_device_type_capture`, `ma_device_type_duplex` of `ma_device_type_loopback`. - sampleRate - The sample rate, in hertz. The most common sample rates are 48000 and 44100. Setting this to 0 will use the device's native sample rate. + sampleRate + The sample rate, in hertz. The most common sample rates are 48000 and 44100. Setting this to 0 will use the device's native sample rate. - periodSizeInFrames - The desired size of a period in PCM frames. If this is 0, `periodSizeInMilliseconds` will be used instead. If both are 0 the default buffer size will - be used depending on the selected performance profile. This value affects latency. See below for details. + periodSizeInFrames + The desired size of a period in PCM frames. If this is 0, `periodSizeInMilliseconds` will be used instead. If both are 0 the default buffer size will + be used depending on the selected performance profile. This value affects latency. See below for details. - periodSizeInMilliseconds - The desired size of a period in milliseconds. If this is 0, `periodSizeInFrames` will be used instead. If both are 0 the default buffer size will be - used depending on the selected performance profile. The value affects latency. See below for details. + periodSizeInMilliseconds + The desired size of a period in milliseconds. If this is 0, `periodSizeInFrames` will be used instead. If both are 0 the default buffer size will be + used depending on the selected performance profile. The value affects latency. See below for details. - periods - The number of periods making up the device's entire buffer. The total buffer size is `periodSizeInFrames` or `periodSizeInMilliseconds` multiplied by - this value. This is just a hint as backends will be the ones who ultimately decide how your periods will be configured. + periods + The number of periods making up the device's entire buffer. The total buffer size is `periodSizeInFrames` or `periodSizeInMilliseconds` multiplied by + this value. This is just a hint as backends will be the ones who ultimately decide how your periods will be configured. - performanceProfile - A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or - `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. + performanceProfile + A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or + `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. - noPreZeroedOutputBuffer - When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of - the output buffer will be cleared the zero. You can use this to avoid the overhead of zeroing out the buffer if you can guarantee that your data - callback will write to every sample in the output buffer, or if you are doing your own clearing. + noPreSilencedOutputBuffer + When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of + the output buffer will be cleared the zero. You can use this to avoid the overhead of zeroing out the buffer if you can guarantee that your data + callback will write to every sample in the output buffer, or if you are doing your own clearing. - noClip - When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. When set to false (default), the - contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only - applies when the playback sample format is f32. + noClip + When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. When set to false (default), the + contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only + applies when the playback sample format is f32. - dataCallback - The callback to fire whenever data is ready to be delivered to or from the device. + noDisableDenormals + By default, miniaudio will disable denormals when the data callback is called. Setting this to true will prevent the disabling of denormals. - stopCallback - The callback to fire whenever the device has stopped, either explicitly via `ma_device_stop()`, or implicitly due to things like the device being - disconnected. + noFixedSizedCallback + Allows miniaudio to fire the data callback with any frame count. When this is set to true, the data callback will be fired with a consistent frame + count as specified by `periodSizeInFrames` or `periodSizeInMilliseconds`. When set to false, miniaudio will fire the callback with whatever the + backend requests, which could be anything. - pUserData - The user data pointer to use with the device. You can access this directly from the device object like `device.pUserData`. + dataCallback + The callback to fire whenever data is ready to be delivered to or from the device. - resampling.algorithm - The resampling algorithm to use when miniaudio needs to perform resampling between the rate specified by `sampleRate` and the device's native rate. The - default value is `ma_resample_algorithm_linear`, and the quality can be configured with `resampling.linear.lpfOrder`. + notificationCallback + The callback to fire when something has changed with the device, such as whether or not it has been started or stopped. - resampling.linear.lpfOrder - The linear resampler applies a low-pass filter as part of it's procesing for anti-aliasing. This setting controls the order of the filter. The higher - the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is - `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. + pUserData + The user data pointer to use with the device. You can access this directly from the device object like `device.pUserData`. - playback.pDeviceID - A pointer to a `ma_device_id` structure containing the ID of the playback device to initialize. Setting this NULL (default) will use the system's - default playback device. Retrieve the device ID from the `ma_device_info` structure, which can be retrieved using device enumeration. + resampling.algorithm + The resampling algorithm to use when miniaudio needs to perform resampling between the rate specified by `sampleRate` and the device's native rate. The + default value is `ma_resample_algorithm_linear`, and the quality can be configured with `resampling.linear.lpfOrder`. - playback.format - The sample format to use for playback. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after - initialization from the device object directly with `device.playback.format`. + resampling.pBackendVTable + A pointer to an optional vtable that can be used for plugging in a custom resampler. - playback.channels - The number of channels to use for playback. When set to 0 the device's native channel count will be used. This can be retrieved after initialization - from the device object directly with `device.playback.channels`. + resampling.pBackendUserData + A pointer that will passed to callbacks in pBackendVTable. - playback.channelMap - The channel map to use for playback. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.playback.channelMap`. + resampling.linear.lpfOrder + The linear resampler applies a low-pass filter as part of it's procesing for anti-aliasing. This setting controls the order of the filter. The higher + the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is + `MA_MAX_FILTER_ORDER`. The default value is `min(4, MA_MAX_FILTER_ORDER)`. - playback.shareMode - The preferred share mode to use for playback. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify - exclusive mode, but it's not supported by the backend, initialization will fail. You can then fall back to shared mode if desired by changing this to - ma_share_mode_shared and reinitializing. + playback.pDeviceID + A pointer to a `ma_device_id` structure containing the ID of the playback device to initialize. Setting this NULL (default) will use the system's + default playback device. Retrieve the device ID from the `ma_device_info` structure, which can be retrieved using device enumeration. - capture.pDeviceID - A pointer to a `ma_device_id` structure containing the ID of the capture device to initialize. Setting this NULL (default) will use the system's - default capture device. Retrieve the device ID from the `ma_device_info` structure, which can be retrieved using device enumeration. + playback.format + The sample format to use for playback. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after + initialization from the device object directly with `device.playback.format`. - capture.format - The sample format to use for capture. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after - initialization from the device object directly with `device.capture.format`. + playback.channels + The number of channels to use for playback. When set to 0 the device's native channel count will be used. This can be retrieved after initialization + from the device object directly with `device.playback.channels`. - capture.channels - The number of channels to use for capture. When set to 0 the device's native channel count will be used. This can be retrieved after initialization - from the device object directly with `device.capture.channels`. + playback.pChannelMap + The channel map to use for playback. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the + device object direct with `device.playback.pChannelMap`. When set, the buffer should contain `channels` items. - capture.channelMap - The channel map to use for capture. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.capture.channelMap`. + playback.shareMode + The preferred share mode to use for playback. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify + exclusive mode, but it's not supported by the backend, initialization will fail. You can then fall back to shared mode if desired by changing this to + ma_share_mode_shared and reinitializing. - capture.shareMode - The preferred share mode to use for capture. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify - exclusive mode, but it's not supported by the backend, initialization will fail. You can then fall back to shared mode if desired by changing this to - ma_share_mode_shared and reinitializing. + capture.pDeviceID + A pointer to a `ma_device_id` structure containing the ID of the capture device to initialize. Setting this NULL (default) will use the system's + default capture device. Retrieve the device ID from the `ma_device_info` structure, which can be retrieved using device enumeration. - wasapi.noAutoConvertSRC - WASAPI only. When set to true, disables WASAPI's automatic resampling and forces the use of miniaudio's resampler. Defaults to false. + capture.format + The sample format to use for capture. When set to `ma_format_unknown` the device's native format will be used. This can be retrieved after + initialization from the device object directly with `device.capture.format`. - wasapi.noDefaultQualitySRC - WASAPI only. Only used when `wasapi.noAutoConvertSRC` is set to false. When set to true, disables the use of `AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY`. - You should usually leave this set to false, which is the default. + capture.channels + The number of channels to use for capture. When set to 0 the device's native channel count will be used. This can be retrieved after initialization + from the device object directly with `device.capture.channels`. - wasapi.noAutoStreamRouting - WASAPI only. When set to true, disables automatic stream routing on the WASAPI backend. Defaults to false. + capture.pChannelMap + The channel map to use for capture. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the + device object direct with `device.capture.pChannelMap`. When set, the buffer should contain `channels` items. - wasapi.noHardwareOffloading - WASAPI only. When set to true, disables the use of WASAPI's hardware offloading feature. Defaults to false. + capture.shareMode + The preferred share mode to use for capture. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify + exclusive mode, but it's not supported by the backend, initialization will fail. You can then fall back to shared mode if desired by changing this to + ma_share_mode_shared and reinitializing. - alsa.noMMap - ALSA only. When set to true, disables MMap mode. Defaults to false. + wasapi.noAutoConvertSRC + WASAPI only. When set to true, disables WASAPI's automatic resampling and forces the use of miniaudio's resampler. Defaults to false. - alsa.noAutoFormat - ALSA only. When set to true, disables ALSA's automatic format conversion by including the SND_PCM_NO_AUTO_FORMAT flag. Defaults to false. + wasapi.noDefaultQualitySRC + WASAPI only. Only used when `wasapi.noAutoConvertSRC` is set to false. When set to true, disables the use of `AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY`. + You should usually leave this set to false, which is the default. - alsa.noAutoChannels - ALSA only. When set to true, disables ALSA's automatic channel conversion by including the SND_PCM_NO_AUTO_CHANNELS flag. Defaults to false. + wasapi.noAutoStreamRouting + WASAPI only. When set to true, disables automatic stream routing on the WASAPI backend. Defaults to false. - alsa.noAutoResample - ALSA only. When set to true, disables ALSA's automatic resampling by including the SND_PCM_NO_AUTO_RESAMPLE flag. Defaults to false. + wasapi.noHardwareOffloading + WASAPI only. When set to true, disables the use of WASAPI's hardware offloading feature. Defaults to false. - pulse.pStreamNamePlayback - PulseAudio only. Sets the stream name for playback. + alsa.noMMap + ALSA only. When set to true, disables MMap mode. Defaults to false. - pulse.pStreamNameCapture - PulseAudio only. Sets the stream name for capture. + alsa.noAutoFormat + ALSA only. When set to true, disables ALSA's automatic format conversion by including the SND_PCM_NO_AUTO_FORMAT flag. Defaults to false. - coreaudio.allowNominalSampleRateChange - Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This - is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate - that is known to be natively supported by the hardware thereby avoiding the cost of resampling. When set to true, miniaudio will - find the closest match between the sample rate requested in the device config and the sample rates natively supported by the - hardware. When set to false, the sample rate currently set by the operating system will always be used. + alsa.noAutoChannels + ALSA only. When set to true, disables ALSA's automatic channel conversion by including the SND_PCM_NO_AUTO_CHANNELS flag. Defaults to false. + + alsa.noAutoResample + ALSA only. When set to true, disables ALSA's automatic resampling by including the SND_PCM_NO_AUTO_RESAMPLE flag. Defaults to false. + + pulse.pStreamNamePlayback + PulseAudio only. Sets the stream name for playback. + + pulse.pStreamNameCapture + PulseAudio only. Sets the stream name for capture. + + coreaudio.allowNominalSampleRateChange + Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This + is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate + that is known to be natively supported by the hardware thereby avoiding the cost of resampling. When set to true, miniaudio will + find the closest match between the sample rate requested in the device config and the sample rates natively supported by the + hardware. When set to false, the sample rate currently set by the operating system will always be used. + + opensl.streamType + OpenSL only. Explicitly sets the stream type. If left unset (`ma_opensl_stream_type_default`), the + stream type will be left unset. Think of this as the type of audio you're playing. + + opensl.recordingPreset + OpenSL only. Explicitly sets the type of recording your program will be doing. When left + unset, the recording preset will be left unchanged. + + aaudio.usage + AAudio only. Explicitly sets the nature of the audio the program will be consuming. When + left unset, the usage will be left unchanged. + + aaudio.contentType + AAudio only. Sets the content type. When left unset, the content type will be left unchanged. + + aaudio.inputPreset + AAudio only. Explicitly sets the type of recording your program will be doing. When left + unset, the input preset will be left unchanged. + + aaudio.noAutoStartAfterReroute + AAudio only. Controls whether or not the device should be automatically restarted after a + stream reroute. When set to false (default) the device will be restarted automatically; + otherwise the device will be stopped. Once initialized, the device's config is immutable. If you need to change the config you will need to initialize a new device. @@ -767,7 +821,7 @@ foreign lib { ma_device device; ma_result result = ma_device_init(NULL, &config, &device); if (result != MA_SUCCESS) { - // Error + // Error } ``` @@ -782,14 +836,14 @@ foreign lib { ma_context context; ma_result result = ma_context_init(NULL, 0, NULL, &context); if (result != MA_SUCCESS) { - // Error + // Error } ma_device_info* pPlaybackDeviceInfos; ma_uint32 playbackDeviceCount; result = ma_context_get_devices(&context, &pPlaybackDeviceInfos, &playbackDeviceCount, NULL, NULL); if (result != MA_SUCCESS) { - // Error + // Error } // ... choose a device from pPlaybackDeviceInfos ... @@ -807,7 +861,7 @@ foreign lib { ma_device device; result = ma_device_init(&context, &config, &device); if (result != MA_SUCCESS) { - // Error + // Error } ``` @@ -833,19 +887,19 @@ foreign lib { Parameters ---------- backends (in, optional) - A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. + A list of backends to try initializing, in priority order. Can be NULL, in which case it uses default priority order. backendCount (in, optional) - The number of items in `backend`. Ignored if `backend` is NULL. + The number of items in `backend`. Ignored if `backend` is NULL. pContextConfig (in, optional) - The context configuration. + The context configuration. pConfig (in) - A pointer to the device configuration. Cannot be null. See remarks for details. + A pointer to the device configuration. Cannot be null. See remarks for details. pDevice (out) - A pointer to the device object being initialized. + A pointer to the device object being initialized. Return Value @@ -890,7 +944,7 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device to stop. + A pointer to the device to stop. Return Value @@ -927,6 +981,95 @@ foreign lib { device_get_log :: proc(pDevice: ^device) -> ^log --- + /* + Retrieves information about the device. + + + Parameters + ---------- + pDevice (in) + A pointer to the device whose information is being retrieved. + + type (in) + The device type. This parameter is required for duplex devices. When retrieving device + information, you are doing so for an individual playback or capture device. + + pDeviceInfo (out) + A pointer to the `ma_device_info` that will receive the device information. + + + Return Value + ------------ + MA_SUCCESS if successful; any other error code otherwise. + + + Thread Safety + ------------- + Unsafe. This should be considered unsafe because it may be calling into the backend which may or + may not be safe. + + + Callback Safety + --------------- + Unsafe. You should avoid calling this in the data callback because it may call into the backend + which may or may not be safe. + */ + device_get_info :: proc(pDevice: ^device, type: device_type, pDeviceInfo: ^device_info) -> result --- + + + /* + Retrieves the name of the device. + + + Parameters + ---------- + pDevice (in) + A pointer to the device whose information is being retrieved. + + type (in) + The device type. This parameter is required for duplex devices. When retrieving device + information, you are doing so for an individual playback or capture device. + + pName (out) + A pointer to the buffer that will receive the name. + + nameCap (in) + The capacity of the output buffer, including space for the null terminator. + + pLengthNotIncludingNullTerminator (out, optional) + A pointer to the variable that will receive the length of the name, not including the null + terminator. + + + Return Value + ------------ + MA_SUCCESS if successful; any other error code otherwise. + + + Thread Safety + ------------- + Unsafe. This should be considered unsafe because it may be calling into the backend which may or + may not be safe. + + + Callback Safety + --------------- + Unsafe. You should avoid calling this in the data callback because it may call into the backend + which may or may not be safe. + + + Remarks + ------- + If the name does not fully fit into the output buffer, it'll be truncated. You can pass in NULL to + `pName` if you want to first get the length of the name for the purpose of memory allocation of the + output buffer. Allocating a buffer of size `MA_MAX_DEVICE_NAME_LENGTH + 1` should be enough for + most cases and will avoid the need for the inefficiency of calling this function twice. + + This is implemented in terms of `ma_device_get_info()`. + */ + device_get_name :: proc(pDevice: ^device, type: device_type, pName: [^]c.char, nameCap: c.size_t, pLengthNotIncludingNullTerminator: ^c.size_t) -> result --- + + /* Starts the device. For playback devices this begins playback. For capture devices it begins recording. @@ -936,7 +1079,7 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device to start. + A pointer to the device to start. Return Value @@ -979,7 +1122,7 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device to stop. + A pointer to the device to stop. Return Value @@ -1025,7 +1168,7 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose start state is being retrieved. + A pointer to the device whose start state is being retrieved. Return Value @@ -1059,24 +1202,24 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose state is being retrieved. + A pointer to the device whose state is being retrieved. Return Value ------------ The current state of the device. The return value will be one of the following: - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_UNINITIALIZED | Will only be returned if the device is in the middle of initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPED | The device is stopped. The initial state of the device after initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTED | The device started and requesting and/or delivering audio data. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTING | The device is in the process of starting. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPING | The device is in the process of stopping. | - +------------------------+------------------------------------------------------------------------------+ + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_uninitialized | Will only be returned if the device is in the middle of initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopped | The device is stopped. The initial state of the device after initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_started | The device started and requesting and/or delivering audio data. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_starting | The device is in the process of starting. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopping | The device is in the process of stopping. | + +-------------------------------+------------------------------------------------------------------------------+ Thread Safety @@ -1094,40 +1237,89 @@ foreign lib { ------- The general flow of a devices state goes like this: - ``` - ma_device_init() -> MA_STATE_UNINITIALIZED -> MA_STATE_STOPPED - ma_device_start() -> MA_STATE_STARTING -> MA_STATE_STARTED - ma_device_stop() -> MA_STATE_STOPPING -> MA_STATE_STOPPED - ``` + ``` + ma_device_init() -> ma_device_state_uninitialized -> ma_device_state_stopped + ma_device_start() -> ma_device_state_starting -> ma_device_state_started + ma_device_stop() -> ma_device_state_stopping -> ma_device_state_stopped + ``` When the state of the device is changed with `ma_device_start()` or `ma_device_stop()` at this same time as this function is called, the value returned by this function could potentially be out of sync. If this is significant to your program you need to implement your own synchronization. */ - device_get_state :: proc(pDevice: ^device) -> u32 --- + device_get_state :: proc(pDevice: ^device) -> device_state --- + + + /* + Performs post backend initialization routines for setting up internal data conversion. + + This should be called whenever the backend is initialized. The only time this should be called from + outside of miniaudio is if you're implementing a custom backend, and you would only do it if you + are reinitializing the backend due to rerouting or reinitializing for some reason. + + + Parameters + ---------- + pDevice [in] + A pointer to the device. + + deviceType [in] + The type of the device that was just reinitialized. + + pPlaybackDescriptor [in] + The descriptor of the playback device containing the internal data format and buffer sizes. + + pPlaybackDescriptor [in] + The descriptor of the capture device containing the internal data format and buffer sizes. + + + Return Value + ------------ + MA_SUCCESS if successful; any other error otherwise. + + + Thread Safety + ------------- + Unsafe. This will be reinitializing internal data converters which may be in use by another thread. + + + Callback Safety + --------------- + Unsafe. This will be reinitializing internal data converters which may be in use by the callback. + + + Remarks + ------- + For a duplex device, you can call this for only one side of the system. This is why the deviceType + is specified as a parameter rather than deriving it from the device. + + You do not need to call this manually unless you are doing a custom backend, in which case you need + only do it if you're manually performing rerouting or reinitialization. + */ + device_post_init :: proc(pDevice: ^device, deviceType: device_type, pPlaybackDescriptor, pCaptureDescriptor: ^device_descriptor) -> result --- /* Sets the master volume factor for the device. - The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_gain_db()` to use decibel notation, where 0 is full volume and + The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_volume_db()` to use decibel notation, where 0 is full volume and values less than 0 decreases the volume. Parameters ---------- pDevice (in) - A pointer to the device whose volume is being set. + A pointer to the device whose volume is being set. volume (in) - The new volume factor. Must be within the range of [0, 1]. + The new volume factor. Must be >= 0. Return Value ------------ MA_SUCCESS if the volume was set successfully. MA_INVALID_ARGS if pDevice is NULL. - MA_INVALID_ARGS if the volume factor is not within the range of [0, 1]. + MA_INVALID_ARGS if volume is negative. Thread Safety @@ -1150,8 +1342,8 @@ foreign lib { See Also -------- ma_device_get_master_volume() - ma_device_set_master_volume_gain_db() - ma_device_get_master_volume_gain_db() + ma_device_set_master_volume_db() + ma_device_get_master_volume_db() */ device_set_master_volume :: proc(pDevice: ^device, volume: f32) -> result --- @@ -1162,10 +1354,10 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose volume factor is being retrieved. + A pointer to the device whose volume factor is being retrieved. pVolume (in) - A pointer to the variable that will receive the volume factor. The returned value will be in the range of [0, 1]. + A pointer to the variable that will receive the volume factor. The returned value will be in the range of [0, 1]. Return Value @@ -1207,10 +1399,10 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose gain is being set. + A pointer to the device whose gain is being set. gainDB (in) - The new volume as gain in decibels. Must be less than or equal to 0, where 0 is full volume and anything less than 0 decreases the volume. + The new volume as gain in decibels. Must be less than or equal to 0, where 0 is full volume and anything less than 0 decreases the volume. Return Value @@ -1243,7 +1435,7 @@ foreign lib { ma_device_set_master_volume() ma_device_get_master_volume() */ - device_set_master_gain_db :: proc(pDevice: ^device, gainDB: f32) -> result --- + device_set_master_volume_db :: proc(pDevice: ^device, gainDB: f32) -> result --- /* Retrieves the master gain in decibels. @@ -1252,10 +1444,10 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to the device whose gain is being retrieved. + A pointer to the device whose gain is being retrieved. pGainDB (in) - A pointer to the variable that will receive the gain in decibels. The returned value will be <= 0. + A pointer to the variable that will receive the gain in decibels. The returned value will be <= 0. Return Value @@ -1282,11 +1474,11 @@ foreign lib { See Also -------- - ma_device_set_master_volume_gain_db() + ma_device_set_master_volume_db() ma_device_set_master_volume() ma_device_get_master_volume() */ - device_get_master_gain_db :: proc(pDevice: ^device, pGainDB: ^f32) -> result --- + device_get_master_volume_db :: proc(pDevice: ^device, pGainDB: ^f32) -> result --- /* @@ -1296,18 +1488,18 @@ foreign lib { Parameters ---------- pDevice (in) - A pointer to device whose processing the data callback. + A pointer to device whose processing the data callback. pOutput (out) - A pointer to the buffer that will receive the output PCM frame data. On a playback device this must not be NULL. On a duplex device - this can be NULL, in which case pInput must not be NULL. + A pointer to the buffer that will receive the output PCM frame data. On a playback device this must not be NULL. On a duplex device + this can be NULL, in which case pInput must not be NULL. pInput (in) - A pointer to the buffer containing input PCM frame data. On a capture device this must not be NULL. On a duplex device this can be - NULL, in which case `pOutput` must not be NULL. + A pointer to the buffer containing input PCM frame data. On a capture device this must not be NULL. On a duplex device this can be + NULL, in which case `pOutput` must not be NULL. frameCount (in) - The number of frames being processed. + The number of frames being processed. Return Value @@ -1334,7 +1526,7 @@ foreign lib { If you are implementing a custom backend, and that backend uses a callback for data delivery, you'll need to call this from inside that callback. */ - device_handle_backend_data_callback :: proc(pDevice: ^device, pOutput: rawptr, pInput: rawptr, frameCount: u32) -> result --- + device_handle_backend_data_callback :: proc(pDevice: ^device, pOutput, pInput: rawptr, frameCount: u32) -> result --- /* @@ -1351,18 +1543,18 @@ foreign lib { Parameters ---------- pDescriptor (in) - A pointer to device descriptor whose `periodSizeInFrames` and `periodSizeInMilliseconds` members - will be used for the calculation of the buffer size. + A pointer to device descriptor whose `periodSizeInFrames` and `periodSizeInMilliseconds` members + will be used for the calculation of the buffer size. nativeSampleRate (in) - The device's native sample rate. This is only ever used when the `periodSizeInFrames` member of - `pDescriptor` is zero. In this case, `periodSizeInMilliseconds` will be used instead, in which - case a sample rate is required to convert to a size in frames. + The device's native sample rate. This is only ever used when the `periodSizeInFrames` member of + `pDescriptor` is zero. In this case, `periodSizeInMilliseconds` will be used instead, in which + case a sample rate is required to convert to a size in frames. performanceProfile (in) - When both the `periodSizeInFrames` and `periodSizeInMilliseconds` members of `pDescriptor` are - zero, miniaudio will fall back to a buffer size based on the performance profile. The profile - to use for this calculation is determine by this parameter. + When both the `periodSizeInFrames` and `periodSizeInMilliseconds` members of `pDescriptor` are + zero, miniaudio will fall back to a buffer size based on the performance profile. The profile + to use for this calculation is determine by this parameter. Return Value @@ -1408,14 +1600,14 @@ foreign lib { Parameters ---------- pBackends (out, optional) - A pointer to the buffer that will receive the enabled backends. Set to NULL to retrieve the backend count. Setting - the capacity of the buffer to `MA_BUFFER_COUNT` will guarantee it's large enough for all backends. + A pointer to the buffer that will receive the enabled backends. Set to NULL to retrieve the backend count. Setting + the capacity of the buffer to `MA_BUFFER_COUNT` will guarantee it's large enough for all backends. backendCap (in) - The capacity of the `pBackends` buffer. + The capacity of the `pBackends` buffer. pBackendCount (out) - A pointer to the variable that will receive the enabled backend count. + A pointer to the variable that will receive the enabled backend count. Return Value @@ -1463,7 +1655,7 @@ foreign lib { result = ma_get_enabled_backends(enabledBackends, MA_BACKEND_COUNT, &enabledBackendCount); if (result != MA_SUCCESS) { - // Failed to retrieve enabled backends. Should never happen in this example since all inputs are valid. + // Failed to retrieve enabled backends. Should never happen in this example since all inputs are valid. } ``` diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index e05f94665..b3ecb2301 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -18,12 +18,13 @@ SUPPORT_WEBAUDIO :: false // ODIN_OS == .Emscripten SUPPORT_CUSTOM :: true SUPPORT_NULL :: true // ODIN_OS != .Emscripten -STATE_UNINITIALIZED :: 0 -STATE_STOPPED :: 1 /* The device's default state after initialization. */ -STATE_STARTED :: 2 /* The device is started and is requesting and/or delivering audio data. */ -STATE_STARTING :: 3 /* Transitioning from a stopped state to started. */ -STATE_STOPPING :: 4 /* Transitioning from a started state to stopped. */ - +device_state :: enum c.int { + uninitialized = 0, + stopped = 1, /* The device's default state after initialization. */ + started = 2, /* The device is started and is requesting and/or delivering audio data. */ + starting = 3, /* Transitioning from a stopped state to started. */ + stopping = 4, /* Transitioning from a started state to stopped. */ +} when SUPPORT_WASAPI { @@ -56,6 +57,96 @@ backend :: enum c.int { BACKEND_COUNT :: len(backend) +/* +Device job thread. This is used by backends that require asynchronous processing of certain +operations. It is not used by all backends. + +The device job thread is made up of a thread and a job queue. You can post a job to the thread with +ma_device_job_thread_post(). The thread will do the processing of the job. +*/ +device_job_thread_config :: struct { + noThread: b32, /* Set this to true if you want to process jobs yourself. */ + jobQueueCapacity: u32, + jobQueueFlags: u32, +} + +device_job_thread :: struct { + thread: thread, + jobQueue: job_queue, + _hasThread: b32, +} + + +/* Device notification types. */ +device_notification_type :: enum c.int { + started, + stopped, + rerouted, + interruption_began, + interruption_ended, +} + +device_notification :: struct { + pDevice: ^device, + type: device_notification_type, + data: struct #raw_union { + started: struct { + _unused: c.int, + }, + stopped: struct { + _unused: c.int, + }, + rerouted: struct { + _unused: c.int, + }, + interruption: struct { + _unused: c.int, + }, + }, +} + +/* +The notification callback for when the application should be notified of a change to the device. + +This callback is used for notifying the application of changes such as when the device has started, +stopped, rerouted or an interruption has occurred. Note that not all backends will post all +notification types. For example, some backends will perform automatic stream routing without any +kind of notification to the host program which means miniaudio will never know about it and will +never be able to fire the rerouted notification. You should keep this in mind when designing your +program. + +The stopped notification will *not* get fired when a device is rerouted. + + +Parameters +---------- +pNotification (in) + A pointer to a structure containing information about the event. Use the `pDevice` member of + this object to retrieve the relevant device. The `type` member can be used to discriminate + against each of the notification types. + + +Remarks +------- +Do not restart or uninitialize the device from the callback. + +Not all notifications will be triggered by all backends, however the started and stopped events +should be reliable for all backends. Some backends do not have a good way to detect device +stoppages due to unplugging the device which may result in the stopped callback not getting +fired. This has been observed with at least one BSD variant. + +The rerouted notification is fired *after* the reroute has occurred. The stopped notification will +*not* get fired when a device is rerouted. The following backends are known to do automatic stream +rerouting, but do not have a way to be notified of the change: + + * DirectSound + +The interruption notifications are used on mobile platforms for detecting when audio is interrupted +due to things like an incoming phone call. Currently this is only implemented on iOS. None of the +Android backends will report this notification. +*/ +device_notification_proc :: proc "c" (pNotification: ^device_notification) + /* The callback for processing audio data from the device. @@ -96,9 +187,11 @@ callback. The following APIs cannot be called from inside the callback: The proper way to stop the device is to call `ma_device_stop()` from a different thread, normally the main application thread. */ -device_callback_proc :: proc "c" (pDevice: ^device, pOutput: rawptr, pInput: rawptr, frameCount: u32) +device_data_proc :: proc "c" (pDevice: ^device, pOutput, pInput: rawptr, frameCount: u32) /* +DEPRECATED. Use ma_device_notification_proc instead. + The callback for when the device has been stopped. This will be called when the device is stopped explicitly with `ma_device_stop()` and also called implicitly when the device is stopped through external forces @@ -108,48 +201,15 @@ such as being unplugged or an internal error occuring. Parameters ---------- pDevice (in) - A pointer to the device that has just stopped. + A pointer to the device that has just stopped. Remarks ------- Do not restart or uninitialize the device from the callback. */ -stop_proc :: proc "c" (pDevice: ^device) +stop_proc :: proc "c" (pDevice: ^device) /* DEPRECATED. Use ma_device_notification_proc instead. */ -/* -The callback for handling log messages. - - -Parameters ----------- -pContext (in) - A pointer to the context the log message originated from. - -pDevice (in) - A pointer to the device the log message originate from, if any. This can be null, in which case the message came from the context. - -logLevel (in) - The log level. This can be one of the following: - - +----------------------+ - | Log Level | - +----------------------+ - | MA_LOG_LEVEL_DEBUG | - | MA_LOG_LEVEL_INFO | - | MA_LOG_LEVEL_WARNING | - | MA_LOG_LEVEL_ERROR | - +----------------------+ - -message (in) - The log message. - - -Remarks -------- -Do not modify the state of the device from inside the callback. -*/ -log_proc :: proc "c" (pContext: context_type, pDevice: ^device, logLevel: u32, message: cstring) device_type :: enum c.int { playback = 1, @@ -279,29 +339,14 @@ device_id :: struct #raw_union { DATA_FORMAT_FLAG_EXCLUSIVE_MODE :: 1 << 1 /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ +MAX_DEVICE_NAME_LENGTH :: 255 + device_info :: struct { /* Basic info. This is the only information guaranteed to be filled in during device enumeration. */ id: device_id, - name: [256]byte, + name: [MAX_DEVICE_NAME_LENGTH + 1]c.char, /* +1 for null terminator. */ isDefault: b32, - /* - Detailed info. As much of this is filled as possible with ma_context_get_device_info(). Note that you are allowed to initialize - a device with settings outside of this range, but it just means the data will be converted using miniaudio's data conversion - pipeline before sending the data to/from the device. Most programs will need to not worry about these values, but it's provided - here mainly for informational purposes or in the rare case that someone might find it useful. - - These will be set to 0 when returned by ma_context_enumerate_devices() or ma_context_get_devices(). - */ - formatCount: u32, - formats: [format]format, - minChannels: u32, - maxChannels: u32, - minSampleRate: u32, - maxSampleRate: u32, - - - /* Experimental. Don't use these right now. */ nativeDataFormatCount: u32, nativeDataFormats: [/*len(format_count) * standard_sample_rate.rate_count * MAX_CHANNELS*/ 64]struct { /* Not sure how big to make this. There can be *many* permutations for virtual devices which can support anything. */ format: format, /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ @@ -312,31 +357,26 @@ device_info :: struct { } device_config :: struct { - deviceType: device_type, - sampleRate: u32, - periodSizeInFrames: u32, - periodSizeInMilliseconds: u32, - periods: u32, - performanceProfile: performance_profile, - noPreZeroedOutputBuffer: b8, /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ - noClip: b8, /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ - dataCallback: device_callback_proc, - stopCallback: stop_proc, - pUserData: rawptr, - resampling: struct { - algorithm: resample_algorithm, - linear: struct { - lpfOrder: u32, - }, - speex: struct { - quality: c.int, - }, - }, + deviceType: device_type, + sampleRate: u32, + periodSizeInFrames: u32, + periodSizeInMilliseconds: u32, + periods: u32, + performanceProfile: performance_profile, + noPreSilencedOutputBuffer: b8, /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ + noClip: b8, /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ + noDisableDenormals: b8, /* Do not disable denormals when firing the data callback. */ + noFixedSizedCallback: b8, /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */ + dataCallback: device_data_proc, + notificationCallback: device_notification_proc, + stopCallback: stop_proc, + pUserData: rawptr, + resampling: resampler_config, playback: struct { pDeviceID: ^device_id, format: format, channels: u32, - channelMap: [MAX_CHANNELS]channel, + channelMap: [^]channel, channelMixMode: channel_mix_mode, shareMode: share_mode, }, @@ -344,7 +384,7 @@ device_config :: struct { pDeviceID: ^device_id, format: format, channels: u32, - channelMap: [MAX_CHANNELS]channel, + channelMap: [^]channel, channelMixMode: channel_mix_mode, shareMode: share_mode, }, @@ -373,9 +413,10 @@ device_config :: struct { recordingPreset: opensl_recording_preset, }, aaudio: struct { - usage: aaudio_usage, - contentType: aaudio_content_type, - inputPreset: aaudio_input_preset, + usage: aaudio_usage, + contentType: aaudio_content_type, + inputPreset: aaudio_input_preset, + noAutoStartAfterReroute: b32, }, } @@ -425,14 +466,14 @@ to many devices. A device is created from a context. The general flow goes like this: 1) A context is created with `onContextInit()` - 1a) Available devices can be enumerated with `onContextEnumerateDevices()` if required. - 1b) Detailed information about a device can be queried with `onContextGetDeviceInfo()` if required. + 1a) Available devices can be enumerated with `onContextEnumerateDevices()` if required. + 1b) Detailed information about a device can be queried with `onContextGetDeviceInfo()` if required. 2) A device is created from the context that was created in the first step using `onDeviceInit()`, and optionally a device ID that was - selected from device enumeration via `onContextEnumerateDevices()`. + selected from device enumeration via `onContextEnumerateDevices()`. 3) A device is started or stopped with `onDeviceStart()` / `onDeviceStop()` 4) Data is delivered to and from the device by the backend. This is always done based on the native format returned by the prior call - to `onDeviceInit()`. Conversion between the device's native format and the format requested by the application will be handled by - miniaudio internally. + to `onDeviceInit()`. Conversion between the device's native format and the format requested by the application will be handled by + miniaudio internally. Initialization of the context is quite simple. You need to do any necessary initialization of internal objects and then output the callbacks defined in this structure. @@ -440,7 +481,7 @@ callbacks defined in this structure. Once the context has been initialized you can initialize a device. Before doing so, however, the application may want to know which physical devices are available. This is where `onContextEnumerateDevices()` comes in. This is fairly simple. For each device, fire the given callback with, at a minimum, the basic information filled out in `ma_device_info`. When the callback returns `MA_FALSE`, enumeration -needs to stop and the `onContextEnumerateDevices()` function return with a success code. +needs to stop and the `onContextEnumerateDevices()` function returns with a success code. Detailed device information can be retrieved from a device ID using `onContextGetDeviceInfo()`. This takes as input the device type and ID, and on output returns detailed information about the device in `ma_device_info`. The `onContextGetDeviceInfo()` callback must handle the @@ -455,7 +496,7 @@ internally by miniaudio. On input, if the sample format is set to `ma_format_unknown`, the backend is free to use whatever sample format it desires, so long as it's supported by miniaudio. When the channel count is set to 0, the backend should use the device's native channel count. The same applies for -sample rate. For the channel map, the default should be used when `ma_channel_map_blank()` returns true (all channels set to +sample rate. For the channel map, the default should be used when `ma_channel_map_is_blank()` returns true (all channels set to `MA_CHANNEL_NONE`). On input, the `periodSizeInFrames` or `periodSizeInMilliseconds` option should always be set. The backend should inspect both of these variables. If `periodSizeInFrames` is set, it should take priority, otherwise it needs to be derived from the period size in milliseconds (`periodSizeInMilliseconds`) and the sample rate, keeping in mind that the sample rate may be 0, in which case the @@ -474,14 +515,17 @@ This allows miniaudio to then process any necessary data conversion and then pas If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. -The audio thread should run data delivery logic in a loop while `ma_device_get_state() == MA_STATE_STARTED` and no errors have been +The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been encounted. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback. The invocation of the `onDeviceDataLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this -callback. When the device is stopped, the `ma_device_get_state() == MA_STATE_STARTED` condition will fail and the loop will be terminated +callback. When the device is stopped, the `ma_device_get_state() == ma_device_state_started` condition will fail and the loop will be terminated which will then fall through to the part that stops the device. For an example on how to implement the `onDeviceDataLoop()` callback, look at `ma_device_audio_thread__default_read_write()`. Implement the `onDeviceDataLoopWakeup()` callback if you need a mechanism to wake up the audio thread. + +If the backend supports an optimized retrieval of device information from an initialized `ma_device` object, it should implement the +`onDeviceGetInfo()` callback. This is optional, in which case it will fall back to `onContextGetDeviceInfo()` which is less efficient. */ backend_callbacks :: struct { onContextInit: proc "c" (pContext: ^context_type, pConfig: ^context_config, pCallbacks: ^backend_callbacks) -> result, @@ -496,10 +540,10 @@ backend_callbacks :: struct { onDeviceWrite: proc "c" (pDevice: ^device, pFrames: rawptr, frameCount: u32, pFramesWritten: ^u32) -> result, onDeviceDataLoop: proc "c" (pDevice: ^device) -> result, onDeviceDataLoopWakeup: proc "c" (pDevice: ^device) -> result, + onDeviceGetInfo: proc "c" (pDevice: ^device, type: device_type, pDeviceInfo: ^device_info) -> result, } context_config :: struct { - logCallback: log_proc, /* Legacy logging callback. Will be removed in version 0.11. */ pLog: ^log, threadPriority: thread_priority, threadStackSize: c.size_t, @@ -538,7 +582,7 @@ context_command__wasapi :: struct { deviceType: device_type, pAudioClient: rawptr, ppAudioClientService: ^rawptr, - pResult: ^rawptr, /* The result from creating the audio client service. */ + pResult: ^result, /* The result from creating the audio client service. */ }, releaseAudioClient: struct { pDevice: ^device, @@ -548,21 +592,20 @@ context_command__wasapi :: struct { } context_type :: struct { - callbacks: backend_callbacks, - backend: backend, /* DirectSound, ALSA, etc. */ - pLog: ^log, - log: log, /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ - logCallback: log_proc, /* Legacy callback. Will be removed in version 0.11. */ - threadPriority: thread_priority, - threadStackSize: c.size_t, - pUserData: rawptr, - allocationCallbacks: allocation_callbacks, - deviceEnumLock: mutex, /* Used to make ma_context_get_devices() thread safe. */ - deviceInfoLock: mutex, /* Used to make ma_context_get_device_info() thread safe. */ - deviceInfoCapacity: u32, /* Total capacity of pDeviceInfos. */ + callbacks: backend_callbacks, + backend: backend, /* DirectSound, ALSA, etc. */ + pLog: ^log, + log: log, /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ + threadPriority: thread_priority, + threadStackSize: c.size_t, + pUserData: rawptr, + allocationCallbacks: allocation_callbacks, + deviceEnumLock: mutex, /* Used to make ma_context_get_devices() thread safe. */ + deviceInfoLock: mutex, /* Used to make ma_context_get_device_info() thread safe. */ + deviceInfoCapacity: u32, /* Total capacity of pDeviceInfos. */ playbackDeviceInfoCount: u32, - captureDeviceInfoCount: u32, - pDeviceInfos: [^]device_info, /* Playback devices first, then capture. */ + captureDeviceInfoCount: u32, + pDeviceInfos: [^]device_info, /* Playback devices first, then capture. */ using _: struct #raw_union { wasapi: (struct { @@ -575,7 +618,7 @@ context_type :: struct { } when SUPPORT_WASAPI else struct {}), dsound: (struct { - DSoundDLL: handle, + hDSoundDLL: handle, DirectSoundCreate: proc "system" (), DirectSoundEnumerateA: proc "system" (), DirectSoundCaptureCreate: proc "system" (), @@ -741,6 +784,8 @@ context_type :: struct { /*pa_mainloop**/ pMainLoop: rawptr, /*pa_context**/ pPulseContext: rawptr, + pApplicationName: cstring, /* Set when the context is initialized. Used by devices for their local pa_context objects. */ + pServerName: cstring, /* Set when the context is initialized. Used by devices for their local pa_context objects. */ } when SUPPORT_PULSEAUDIO else struct {}), jack: (struct { @@ -762,7 +807,7 @@ context_type :: struct { jack_port_get_buffer: proc "system" (), jack_free: proc "system" (), - pClientName: [^]c.char, + pClientName: cstring, tryStartServer: b32, } when SUPPORT_JACK else struct {}), @@ -817,7 +862,7 @@ context_type :: struct { } when SUPPORT_SNDIO else struct {}), audio4: (struct { - _unused: cint, + _unused: c.int, } when SUPPORT_AUDIO4 else struct {}), oss: (struct { @@ -855,6 +900,7 @@ context_type :: struct { AAudioStream_getFramesPerBurst: proc "system" (), AAudioStream_requestStart: proc "system" (), AAudioStream_requestStop: proc "system" (), + jobThread: device_job_thread, /* For processing operations outside of the error callback, specifically device disconnections and rerouting. */ } when SUPPORT_AAUDIO else struct {}), opensl: (struct { @@ -921,37 +967,40 @@ context_type :: struct { } device :: struct { - pContext: ^context_type, - type: device_type, - sampleRate: u32, - state: u32, /*atomic*/ /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ - onData: device_callback_proc, /* Set once at initialization time and should not be changed after. */ - onStop: stop_proc, /* Set once at initialization time and should not be changed after. */ - pUserData: rawptr, /* Application defined data. */ - startStopLock: mutex, - wakeupEvent: event, - startEvent: event, - stopEvent: event, - device_thread: thread, - workResult: result, /* This is set by the worker thread after it's finished doing a job. */ - isOwnerOfContext: b8, /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ - noPreZeroedOutputBuffer: b8, - noClip: b8, - masterVolumeFactor: f32, /*atomic*/ /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ - duplexRB: duplex_rb, /* Intermediary buffer for duplex device on asynchronous backends. */ + pContext: ^context_type, + type: device_type, + sampleRate: u32, + state: u32, /*atomic*/ /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ + onData: device_data_proc, /* Set once at initialization time and should not be changed after. */ + onNotification: device_notification_proc, /* Set once at initialization time and should not be changed after. */ + onStop: stop_proc, /* DEPRECATED. Use the notification callback instead. Set once at initialization time and should not be changed after. */ + pUserData: rawptr, /* Application defined data. */ + startStopLock: mutex, + wakeupEvent: event, + startEvent: event, + stopEvent: event, + device_thread: thread, + workResult: result, /* This is set by the worker thread after it's finished doing a job. */ + isOwnerOfContext: b8, /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ + noPreSilencedOutputBuffer: b8, + noClip: b8, + noDisableDenormals: b8, + noFixedSizedCallback: b8, + masterVolumeFactor: f32, /*atomic*/ /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ + duplexRB: duplex_rb, /* Intermediary buffer for duplex device on asynchronous backends. */ resampling: struct { - algorithm: resample_algorithm, + algorithm: resample_algorithm, + pBackendVTable: ^resampling_backend_vtable, + pBackendUserData: rawptr, linear: struct { lpfOrder: u32, }, - speex: struct { - quality: c.int, - }, }, playback: struct { - id: device_id, /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - name: [256]byte, /* Maybe temporary. Likely to be replaced with a query API. */ - shareMode: share_mode, /* Set to whatever was passed in when the device was initialized. */ + pID: ^device_id, /* Set to NULL if using default ID, otherwise set to the address of "id". */ + id: device_id, /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ + name: [MAX_DEVICE_NAME_LENGTH + 1]c.char, /* Maybe temporary. Likely to be replaced with a query API. */ + shareMode: share_mode, /* Set to whatever was passed in when the device was initialized. */ playback_format: format, channels: u32, channelMap: [MAX_CHANNELS]channel, @@ -963,11 +1012,19 @@ device :: struct { internalPeriods: u32, channelMixMode: channel_mix_mode, converter: data_converter, + pIntermediaryBuffer: rawptr, /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */ + intermediaryBufferCap: u32, + intermediaryBufferLen: u32, /* How many valid frames are sitting in the intermediary buffer. */ + pInputCache: rawptr, /* In external format. Can be null. */ + inputCacheCap: u64, + inputCacheConsumed: u64, + inputCacheRemaining: u64, }, capture: struct { - id: device_id, /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - name: [256]byte, /* Maybe temporary. Likely to be replaced with a query API. */ - shareMode: share_mode, /* Set to whatever was passed in when the device was initialized. */ + pID: ^device_id, /* Set to NULL if using default ID, otherwise set to the address of "id". */ + id: device_id, /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ + name: [MAX_DEVICE_NAME_LENGTH + 1]c.char, /* Maybe temporary. Likely to be replaced with a query API. */ + shareMode: share_mode, /* Set to whatever was passed in when the device was initialized. */ capture_format: format, channels: u32, channelMap: [MAX_CHANNELS]channel, @@ -979,6 +1036,9 @@ device :: struct { internalPeriods: u32, channelMixMode: channel_mix_mode, converter: data_converter, + pIntermediaryBuffer: rawptr, /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */ + intermediaryBufferCap: u32, + intermediaryBufferLen: u32, /* How many valid frames are sitting in the intermediary buffer. */ }, using _: struct #raw_union { @@ -991,7 +1051,7 @@ device :: struct { notificationClient: IMMNotificationClient, /*HANDLE*/ hEventPlayback: handle, /* Auto reset. Initialized to signaled. */ /*HANDLE*/ hEventCapture: handle, /* Auto reset. Initialized to unsignaled. */ - actualPeriodSizeInFramesPlayback: u32, /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ + actualPeriodSizeInFramesPlayback: u32, /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ actualPeriodSizeInFramesCapture: u32, originalPeriodSizeInFrames: u32, originalPeriodSizeInMilliseconds: u32, @@ -999,8 +1059,14 @@ device :: struct { originalPerformanceProfile: performance_profile, periodSizeInFramesPlayback: u32, periodSizeInFramesCapture: u32, - isStartedCapture: b32, /*atomic*/ /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ - isStartedPlayback: b32, /*atomic*/ /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + pMappedBufferCapture: rawptr, + mappedBufferCaptureCap: u32, + mappedBufferCaptureLen: u32, + pMappedBufferPlayback: rawptr, + mappedBufferPlaybackCap: u32, + mappedBufferPlaybackLen: u32, + isStartedCapture: b32, /*atomic*/ /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + isStartedPlayback: b32, /*atomic*/ /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ noAutoConvertSRC: b8, /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ noDefaultQualitySRC: b8, /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ noHardwareOffloading: b8, @@ -1049,14 +1115,16 @@ device :: struct { } when SUPPORT_ALSA else struct {}), pulse: (struct { + /*pa_mainloop**/ pMainLoop: rawptr, + /*pa_context**/ pPulseContext: rawptr, /*pa_stream**/ pStreamPlayback: rawptr, /*pa_stream**/ pStreamCapture: rawptr, } when SUPPORT_PULSEAUDIO else struct {}), jack: (struct { /*jack_client_t**/ pClient: rawptr, - /*jack_port_t**/ pPortsPlayback: [MAX_CHANNELS]rawptr, - /*jack_port_t**/ pPortsCapture: [MAX_CHANNELS]rawptr, + /*jack_port_t**/ pPortsPlayback: [^]rawptr, + /*jack_port_t**/ pPortsCapture: [^]rawptr, pIntermediaryBufferPlayback: [^]f32, /* Typed as a float because JACK is always floating point. */ pIntermediaryBufferCapture: [^]f32, } when SUPPORT_JACK else struct {}), @@ -1079,6 +1147,7 @@ device :: struct { isSwitchingCaptureDevice: b32, /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ pRouteChangeHandler: rawptr, /* Only used on mobile platforms. Obj-C object for handling route changes. */ } when SUPPORT_COREAUDIO else struct {}), + sndio: (struct { handlePlayback: rawptr, handleCapture: rawptr, @@ -1099,6 +1168,10 @@ device :: struct { aaudio: (struct { /*AAudioStream**/ pStreamPlayback: rawptr, /*AAudioStream**/ pStreamCapture: rawptr, + usage: aaudio_usage, + contentType: aaudio_content_type, + inputPreset: aaudio_input_preset, + noAutoStartAfterReroute: b32, } when SUPPORT_AAUDIO else struct {}), opensl: (struct { diff --git a/vendor/miniaudio/doc.odin b/vendor/miniaudio/doc.odin index 887e5d149..c6de0ec61 100644 --- a/vendor/miniaudio/doc.odin +++ b/vendor/miniaudio/doc.odin @@ -2,7 +2,7 @@ package miniaudio /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.10.42 - 2021-08-22 +miniaudio - v0.11.9 - 2022-04-20 David Reid - mackron@gmail.com @@ -14,7 +14,8 @@ GitHub: https://github.com/mackron/miniaudio /* 1. Introduction =============== -miniaudio is a single file library for audio playback and capture. To use it, do the following in one .c file: +miniaudio is a single file library for audio playback and capture. To use it, do the following in +one .c file: ```c #define MINIAUDIO_IMPLEMENTATION @@ -23,16 +24,44 @@ miniaudio is a single file library for audio playback and capture. To use it, do You can do `#include "miniaudio.h"` in other parts of the program just like any other header. -miniaudio uses the concept of a "device" as the abstraction for physical devices. The idea is that you choose a physical device to emit or capture audio from, -and then move data to/from the device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a callback which you specify when -initializing the device. +miniaudio includes both low level and high level APIs. The low level API is good for those who want +to do all of their mixing themselves and only require a light weight interface to the underlying +audio device. The high level API is good for those who have complex mixing and effect requirements. -When initializing the device you first need to configure it. The device configuration allows you to specify things like the format of the data delivered via -the callback, the size of the internal buffer and the ID of the device you want to emit or capture audio from. +In miniaudio, objects are transparent structures. Unlike many other libraries, there are no handles +to opaque objects which means you need to allocate memory for objects yourself. In the examples +presented in this documentation you will often see objects declared on the stack. You need to be +careful when translating these examples to your own code so that you don't accidentally declare +your objects on the stack and then cause them to become invalid once the function returns. In +addition, you must ensure the memory address of your objects remain the same throughout their +lifetime. You therefore cannot be making copies of your objects. -Once you have the device configuration set up you can initialize the device. When initializing a device you need to allocate memory for the device object -beforehand. This gives the application complete control over how the memory is allocated. In the example below we initialize a playback device on the stack, -but you could allocate it on the heap if that suits your situation better. +A config/init pattern is used throughout the entire library. The idea is that you set up a config +object and pass that into the initialization routine. The advantage to this system is that the +config object can be initialized with logical defaults and new properties added to it without +breaking the API. The config object can be allocated on the stack and does not need to be +maintained after initialization of the corresponding object. + + +1.1. Low Level API +------------------ +The low level API gives you access to the raw audio data of an audio device. It supports playback, +capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which +physical device(s) you want to connect to. + +The low level API uses the concept of a "device" as the abstraction for physical devices. The idea +is that you choose a physical device to emit or capture audio from, and then move data to/from the +device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a +callback which you specify when initializing the device. + +When initializing the device you first need to configure it. The device configuration allows you to +specify things like the format of the data delivered via the callback, the size of the internal +buffer and the ID of the device you want to emit or capture audio from. + +Once you have the device configuration set up you can initialize the device. When initializing a +device you need to allocate memory for the device object beforehand. This gives the application +complete control over how the memory is allocated. In the example below we initialize a playback +device on the stack, but you could allocate it on the heap if that suits your situation better. ```c void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) @@ -65,20 +94,27 @@ but you could allocate it on the heap if that suits your situation better. } ``` -In the example above, `data_callback()` is where audio data is written and read from the device. The idea is in playback mode you cause sound to be emitted -from the speakers by writing audio data to the output buffer (`pOutput` in the example). In capture mode you read data from the input buffer (`pInput`) to -extract sound captured by the microphone. The `frameCount` parameter tells you how many frames can be written to the output buffer and read from the input -buffer. A "frame" is one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2 samples: one for the left, one for the right. -The channel count is defined by the device config. The size in bytes of an individual sample is defined by the sample format which is also specified in the -device config. Multi-channel audio data is always interleaved, which means the samples for each frame are stored next to each other in memory. For example, in -a stereo stream the first pair of samples will be the left and right samples for the first frame, the second pair of samples will be the left and right samples -for the second frame, etc. +In the example above, `data_callback()` is where audio data is written and read from the device. +The idea is in playback mode you cause sound to be emitted from the speakers by writing audio data +to the output buffer (`pOutput` in the example). In capture mode you read data from the input +buffer (`pInput`) to extract sound captured by the microphone. The `frameCount` parameter tells you +how many frames can be written to the output buffer and read from the input buffer. A "frame" is +one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2 +samples: one for the left, one for the right. The channel count is defined by the device config. +The size in bytes of an individual sample is defined by the sample format which is also specified +in the device config. Multi-channel audio data is always interleaved, which means the samples for +each frame are stored next to each other in memory. For example, in a stereo stream the first pair +of samples will be the left and right samples for the first frame, the second pair of samples will +be the left and right samples for the second frame, etc. -The configuration of the device is defined by the `ma_device_config` structure. The config object is always initialized with `ma_device_config_init()`. It's -important to always initialize the config with this function as it initializes it with logical defaults and ensures your program doesn't break when new members -are added to the `ma_device_config` structure. The example above uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` -takes a single parameter, which is whether or not the device is a playback, capture, duplex or loopback device (loopback devices are not supported on all -backends). The `config.playback.format` member sets the sample format which can be one of the following (all formats are native-endian): +The configuration of the device is defined by the `ma_device_config` structure. The config object +is always initialized with `ma_device_config_init()`. It's important to always initialize the +config with this function as it initializes it with logical defaults and ensures your program +doesn't break when new members are added to the `ma_device_config` structure. The example above +uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` takes +a single parameter, which is whether or not the device is a playback, capture, duplex or loopback +device (loopback devices are not supported on all backends). The `config.playback.format` member +sets the sample format which can be one of the following (all formats are native-endian): +---------------+----------------------------------------+---------------------------+ | Symbol | Description | Range | @@ -90,22 +126,30 @@ backends). The `config.playback.format` member sets the sample format which can | ma_format_u8 | 8-bit unsigned integer | [0, 255] | +---------------+----------------------------------------+---------------------------+ -The `config.playback.channels` member sets the number of channels to use with the device. The channel count cannot exceed MA_MAX_CHANNELS. The -`config.sampleRate` member sets the sample rate (which must be the same for both playback and capture in full-duplex configurations). This is usually set to -44100 or 48000, but can be set to anything. It's recommended to keep this between 8000 and 384000, however. +The `config.playback.channels` member sets the number of channels to use with the device. The +channel count cannot exceed MA_MAX_CHANNELS. The `config.sampleRate` member sets the sample rate +(which must be the same for both playback and capture in full-duplex configurations). This is +usually set to 44100 or 48000, but can be set to anything. It's recommended to keep this between +8000 and 384000, however. -Note that leaving the format, channel count and/or sample rate at their default values will result in the internal device's native configuration being used -which is useful if you want to avoid the overhead of miniaudio's automatic data conversion. +Note that leaving the format, channel count and/or sample rate at their default values will result +in the internal device's native configuration being used which is useful if you want to avoid the +overhead of miniaudio's automatic data conversion. -In addition to the sample format, channel count and sample rate, the data callback and user data pointer are also set via the config. The user data pointer is -not passed into the callback as a parameter, but is instead set to the `pUserData` member of `ma_device` which you can access directly since all miniaudio -structures are transparent. +In addition to the sample format, channel count and sample rate, the data callback and user data +pointer are also set via the config. The user data pointer is not passed into the callback as a +parameter, but is instead set to the `pUserData` member of `ma_device` which you can access +directly since all miniaudio structures are transparent. -Initializing the device is done with `ma_device_init()`. This will return a result code telling you what went wrong, if anything. On success it will return -`MA_SUCCESS`. After initialization is complete the device will be in a stopped state. To start it, use `ma_device_start()`. Uninitializing the device will stop -it, which is what the example above does, but you can also stop the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again. -Note that it's important to never stop or start the device from inside the callback. This will result in a deadlock. Instead you set a variable or signal an -event indicating that the device needs to stop and handle it in a different thread. The following APIs must never be called inside the callback: +Initializing the device is done with `ma_device_init()`. This will return a result code telling you +what went wrong, if anything. On success it will return `MA_SUCCESS`. After initialization is +complete the device will be in a stopped state. To start it, use `ma_device_start()`. +Uninitializing the device will stop it, which is what the example above does, but you can also stop +the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again. +Note that it's important to never stop or start the device from inside the callback. This will +result in a deadlock. Instead you set a variable or signal an event indicating that the device +needs to stop and handle it in a different thread. The following APIs must never be called inside +the callback: ```c ma_device_init() @@ -115,12 +159,14 @@ event indicating that the device needs to stop and handle it in a different thre ma_device_stop() ``` -You must never try uninitializing and reinitializing a device inside the callback. You must also never try to stop and start it from inside the callback. There -are a few other things you shouldn't do in the callback depending on your requirements, however this isn't so much a thread-safety thing, but rather a -real-time processing thing which is beyond the scope of this introduction. +You must never try uninitializing and reinitializing a device inside the callback. You must also +never try to stop and start it from inside the callback. There are a few other things you shouldn't +do in the callback depending on your requirements, however this isn't so much a thread-safety +thing, but rather a real-time processing thing which is beyond the scope of this introduction. -The example above demonstrates the initialization of a playback device, but it works exactly the same for capture. All you need to do is change the device type -from `ma_device_type_playback` to `ma_device_type_capture` when setting up the config, like so: +The example above demonstrates the initialization of a playback device, but it works exactly the +same for capture. All you need to do is change the device type from `ma_device_type_playback` to +`ma_device_type_capture` when setting up the config, like so: ```c ma_device_config config = ma_device_config_init(ma_device_type_capture); @@ -128,8 +174,9 @@ from `ma_device_type_playback` to `ma_device_type_capture` when setting up the c config.capture.channels = MY_CHANNEL_COUNT; ``` -In the data callback you just read from the input buffer (`pInput` in the example above) and leave the output buffer alone (it will be set to NULL when the -device type is set to `ma_device_type_capture`). +In the data callback you just read from the input buffer (`pInput` in the example above) and leave +the output buffer alone (it will be set to NULL when the device type is set to +`ma_device_type_capture`). These are the available device types and how you should handle the buffers in the callback: @@ -142,23 +189,29 @@ These are the available device types and how you should handle the buffers in th | ma_device_type_loopback | Read from input buffer, leave output buffer untouched. | +-------------------------+--------------------------------------------------------+ -You will notice in the example above that the sample format and channel count is specified separately for playback and capture. This is to support different -data formats between the playback and capture devices in a full-duplex system. An example may be that you want to capture audio data as a monaural stream (one -channel), but output sound to a stereo speaker system. Note that if you use different formats between playback and capture in a full-duplex configuration you -will need to convert the data yourself. There are functions available to help you do this which will be explained later. +You will notice in the example above that the sample format and channel count is specified +separately for playback and capture. This is to support different data formats between the playback +and capture devices in a full-duplex system. An example may be that you want to capture audio data +as a monaural stream (one channel), but output sound to a stereo speaker system. Note that if you +use different formats between playback and capture in a full-duplex configuration you will need to +convert the data yourself. There are functions available to help you do this which will be +explained later. -The example above did not specify a physical device to connect to which means it will use the operating system's default device. If you have multiple physical -devices connected and you want to use a specific one you will need to specify the device ID in the configuration, like so: +The example above did not specify a physical device to connect to which means it will use the +operating system's default device. If you have multiple physical devices connected and you want to +use a specific one you will need to specify the device ID in the configuration, like so: ```c config.playback.pDeviceID = pMyPlaybackDeviceID; // Only if requesting a playback or duplex device. config.capture.pDeviceID = pMyCaptureDeviceID; // Only if requesting a capture, duplex or loopback device. ``` -To retrieve the device ID you will need to perform device enumeration, however this requires the use of a new concept called the "context". Conceptually -speaking the context sits above the device. There is one context to many devices. The purpose of the context is to represent the backend at a more global level -and to perform operations outside the scope of an individual device. Mainly it is used for performing run-time linking against backend libraries, initializing -backends and enumerating devices. The example below shows how to enumerate devices. +To retrieve the device ID you will need to perform device enumeration, however this requires the +use of a new concept called the "context". Conceptually speaking the context sits above the device. +There is one context to many devices. The purpose of the context is to represent the backend at a +more global level and to perform operations outside the scope of an individual device. Mainly it is +used for performing run-time linking against backend libraries, initializing backends and +enumerating devices. The example below shows how to enumerate devices. ```c ma_context context; @@ -199,44 +252,236 @@ backends and enumerating devices. The example below shows how to enumerate devic ma_context_uninit(&context); ``` -The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`. The first parameter is a pointer to a list of `ma_backend` -values which are used to override the default backend priorities. When this is NULL, as in this example, miniaudio's default priorities are used. The second -parameter is the number of backends listed in the array pointed to by the first parameter. The third parameter is a pointer to a `ma_context_config` object -which can be NULL, in which case defaults are used. The context configuration is used for setting the logging callback, custom memory allocation callbacks, -user-defined data and some backend-specific configurations. +The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`. +The first parameter is a pointer to a list of `ma_backend` values which are used to override the +default backend priorities. When this is NULL, as in this example, miniaudio's default priorities +are used. The second parameter is the number of backends listed in the array pointed to by the +first parameter. The third parameter is a pointer to a `ma_context_config` object which can be +NULL, in which case defaults are used. The context configuration is used for setting the logging +callback, custom memory allocation callbacks, user-defined data and some backend-specific +configurations. -Once the context has been initialized you can enumerate devices. In the example above we use the simpler `ma_context_get_devices()`, however you can also use a -callback for handling devices by using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer to a pointer that will, -upon output, be set to a pointer to a buffer containing a list of `ma_device_info` structures. You also provide a pointer to an unsigned integer that will -receive the number of items in the returned buffer. Do not free the returned buffers as their memory is managed internally by miniaudio. +Once the context has been initialized you can enumerate devices. In the example above we use the +simpler `ma_context_get_devices()`, however you can also use a callback for handling devices by +using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer +to a pointer that will, upon output, be set to a pointer to a buffer containing a list of +`ma_device_info` structures. You also provide a pointer to an unsigned integer that will receive +the number of items in the returned buffer. Do not free the returned buffers as their memory is +managed internally by miniaudio. -The `ma_device_info` structure contains an `id` member which is the ID you pass to the device config. It also contains the name of the device which is useful -for presenting a list of devices to the user via the UI. +The `ma_device_info` structure contains an `id` member which is the ID you pass to the device +config. It also contains the name of the device which is useful for presenting a list of devices +to the user via the UI. -When creating your own context you will want to pass it to `ma_device_init()` when initializing the device. Passing in NULL, like we do in the first example, -will result in miniaudio creating the context for you, which you don't want to do since you've already created a context. Note that internally the context is -only tracked by it's pointer which means you must not change the location of the `ma_context` object. If this is an issue, consider using `malloc()` to -allocate memory for the context. +When creating your own context you will want to pass it to `ma_device_init()` when initializing the +device. Passing in NULL, like we do in the first example, will result in miniaudio creating the +context for you, which you don't want to do since you've already created a context. Note that +internally the context is only tracked by it's pointer which means you must not change the location +of the `ma_context` object. If this is an issue, consider using `malloc()` to allocate memory for +the context. + + +1.2. High Level API +------------------- +The high level API consists of three main parts: + + * Resource management for loading and streaming sounds. + * A node graph for advanced mixing and effect processing. + * A high level "engine" that wraps around the resource manager and node graph. + +The resource manager (`ma_resource_manager`) is used for loading sounds. It supports loading sounds +fully into memory and also streaming. It will also deal with reference counting for you which +avoids the same sound being loaded multiple times. + +The node graph is used for mixing and effect processing. The idea is that you connect a number of +nodes into the graph by connecting each node's outputs to another node's inputs. Each node can +implement it's own effect. By chaining nodes together, advanced mixing and effect processing can +be achieved. + +The engine encapsulates both the resource manager and the node graph to create a simple, easy to +use high level API. The resource manager and node graph APIs are covered in more later sections of +this manual. + +The code below shows how you can initialize an engine using it's default configuration. + + ```c + ma_result result; + ma_engine engine; + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +This creates an engine instance which will initialize a device internally which you can access with +`ma_engine_get_device()`. It will also initialize a resource manager for you which can be accessed +with `ma_engine_get_resource_manager()`. The engine itself is a node graph (`ma_node_graph`) which +means you can pass a pointer to the engine object into any of the `ma_node_graph` APIs (with a +cast). Alternatively, you can use `ma_engine_get_node_graph()` instead of a cast. + +Note that all objects in miniaudio, including the `ma_engine` object in the example above, are +transparent structures. There are no handles to opaque structures in miniaudio which means you need +to be mindful of how you declare them. In the example above we are declaring it on the stack, but +this will result in the struct being invalidated once the function encapsulating it returns. If +allocating the engine on the heap is more appropriate, you can easily do so with a standard call +to `malloc()` or whatever heap allocation routine you like: + + ```c + ma_engine* pEngine = malloc(sizeof(*pEngine)); + ``` + +The `ma_engine` API uses the same config/init pattern used all throughout miniaudio. To configure +an engine, you can fill out a `ma_engine_config` object and pass it into the first parameter of +`ma_engine_init()`: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &myCustomResourceManager; // <-- Initialized as some earlier stage. + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; + } + ``` + +This creates an engine instance using a custom config. In this particular example it's showing how +you can specify a custom resource manager rather than having the engine initialize one internally. +This is particularly useful if you want to have multiple engine's share the same resource manager. + +The engine must be uninitialized with `ma_engine_uninit()` when it's no longer needed. + +By default the engine will be started, but nothing will be playing because no sounds have been +initialized. The easiest but least flexible way of playing a sound is like so: + + ```c + ma_engine_play_sound(&engine, "my_sound.wav", NULL); + ``` + +This plays what miniaudio calls an "inline" sound. It plays the sound once, and then puts the +internal sound up for recycling. The last parameter is used to specify which sound group the sound +should be associated with which will be explained later. This particular way of playing a sound is +simple, but lacks flexibility and features. A more flexible way of playing a sound is to first +initialize a sound: + + ```c + ma_result result; + ma_sound sound; + + result = ma_sound_init_from_file(&engine, "my_sound.wav", 0, NULL, NULL, &sound); + if (result != MA_SUCCESS) { + return result; + } + + ma_sound_start(&sound); + ``` + +This returns a `ma_sound` object which represents a single instance of the specified sound file. If +you want to play the same file multiple times simultaneously, you need to create one sound for each +instance. + +Sounds should be uninitialized with `ma_sound_uninit()`. + +Sounds are not started by default. Start a sound with `ma_sound_start()` and stop it with +`ma_sound_stop()`. When a sound is stopped, it is not rewound to the start. Use +`ma_sound_seek_to_pcm_frames(&sound, 0)` to seek back to the start of a sound. By default, starting +and stopping sounds happens immediately, but sometimes it might be convenient to schedule the sound +the be started and/or stopped at a specific time. This can be done with the following functions: + + ```c + ma_sound_set_start_time_in_pcm_frames() + ma_sound_set_start_time_in_milliseconds() + ma_sound_set_stop_time_in_pcm_frames() + ma_sound_set_stop_time_in_milliseconds() + ``` + +The start/stop time needs to be specified based on the absolute timer which is controlled by the +engine. The current global time time in PCM frames can be retrieved with `ma_engine_get_time()`. +The engine's global time can be changed with `ma_engine_set_time()` for synchronization purposes if +required. Note that scheduling a start time still requires an explicit call to `ma_sound_start()` +before anything will play: + + ```c + ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2); + ma_sound_start(&sound); + ``` + +The third parameter of `ma_sound_init_from_file()` is a set of flags that control how the sound be +loaded and a few options on which features should be enabled for that sound. By default, the sound +is synchronously loaded fully into memory straight from the file system without any kind of +decoding. If you want to decode the sound before storing it in memory, you need to specify the +`MA_SOUND_FLAG_DECODE` flag. This is useful if you want to incur the cost of decoding at an earlier +stage, such as a loading stage. Without this option, decoding will happen dynamically at mixing +time which might be too expensive on the audio thread. + +If you want to load the sound asynchronously, you can specify the `MA_SOUND_FLAG_ASYNC` flag. This +will result in `ma_sound_init_from_file()` returning quickly, but the sound will not start playing +until the sound has had some audio decoded. + +The fourth parameter is a pointer to sound group. A sound group is used as a mechanism to organise +sounds into groups which have their own effect processing and volume control. An example is a game +which might have separate groups for sfx, voice and music. Each of these groups have their own +independent volume control. Use `ma_sound_group_init()` or `ma_sound_group_init_ex()` to initialize +a sound group. + +Sounds and sound groups are nodes in the engine's node graph and can be plugged into any `ma_node` +API. This makes it possible to connect sounds and sound groups to effect nodes to produce complex +effect chains. + +A sound can have it's volume changed with `ma_sound_set_volume()`. If you prefer decibel volume +control you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear. + +Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you know +a sound will never have it's pitch changed with `ma_sound_set_pitch()` or via the doppler effect, +you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization. + +By default, sounds and sound groups have spatialization enabled. If you don't ever want to +spatialize your sounds, initialize the sound with the `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. The +spatialization model is fairly simple and is roughly on feature parity with OpenAL. HRTF and +environmental occlusion are not currently supported, but planned for the future. The supported +features include: + + * Sound and listener positioning and orientation with cones + * Attenuation models: none, inverse, linear and exponential + * Doppler effect + +Sounds can be faded in and out with `ma_sound_set_fade_in_pcm_frames()`. + +To check if a sound is currently playing, you can use `ma_sound_is_playing()`. To check if a sound +is at the end, use `ma_sound_at_end()`. Looping of a sound can be controlled with +`ma_sound_set_looping()`. Use `ma_sound_is_looping()` to check whether or not the sound is looping. 2. Building =========== -miniaudio should work cleanly out of the box without the need to download or install any dependencies. See below for platform-specific details. +miniaudio should work cleanly out of the box without the need to download or install any +dependencies. See below for platform-specific details. 2.1. Windows ------------ -The Windows build should compile cleanly on all popular compilers without the need to configure any include paths nor link to any libraries. +The Windows build should compile cleanly on all popular compilers without the need to configure any +include paths nor link to any libraries. + +The UWP build may require linking to mmdevapi.lib if you get errors about an unresolved external +symbol for `ActivateAudioInterfaceAsync()`. + 2.2. macOS and iOS ------------------ -The macOS build should compile cleanly without the need to download any dependencies nor link to any libraries or frameworks. The iOS build needs to be -compiled as Objective-C and will need to link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling through the command line -requires linking to `-lpthread` and `-lm`. +The macOS build should compile cleanly without the need to download any dependencies nor link to +any libraries or frameworks. The iOS build needs to be compiled as Objective-C and will need to +link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling +through the command line requires linking to `-lpthread` and `-lm`. -Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's notarization process. To fix this there are two options. The -first is to use the `MA_NO_RUNTIME_LINKING` option, like so: +Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's +notarization process. To fix this there are two options. The first is to use the +`MA_NO_RUNTIME_LINKING` option, like so: ```c #ifdef __APPLE__ @@ -246,8 +491,9 @@ first is to use the `MA_NO_RUNTIME_LINKING` option, like so: #include "miniaudio.h" ``` -This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. Alternatively, if you would rather keep using runtime -linking you can add the following to your entitlements.xcent file: +This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. +Alternatively, if you would rather keep using runtime linking you can add the following to your +entitlements.xcent file: ``` com.apple.security.cs.allow-dyld-environment-variables @@ -256,26 +502,37 @@ linking you can add the following to your entitlements.xcent file: ``` +See this discussion for more info: https://github.com/mackron/miniaudio/issues/203. + 2.3. Linux ---------- -The Linux build only requires linking to `-ldl`, `-lpthread` and `-lm`. You do not need any development packages. +The Linux build only requires linking to `-ldl`, `-lpthread` and `-lm`. You do not need any +development packages. You may need to link with `-latomic` if you're compiling for 32-bit ARM. + 2.4. BSD -------- -The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio(4), OpenBSD uses sndio and FreeBSD uses OSS. +The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio(4), OpenBSD uses +sndio and FreeBSD uses OSS. You may need to link with `-latomic` if you're compiling for 32-bit +ARM. + 2.5. Android ------------ -AAudio is the highest priority backend on Android. This should work out of the box without needing any kind of compiler configuration. Support for AAudio -starts with Android 8 which means older versions will fall back to OpenSL|ES which requires API level 16+. +AAudio is the highest priority backend on Android. This should work out of the box without needing +any kind of compiler configuration. Support for AAudio starts with Android 8 which means older +versions will fall back to OpenSL|ES which requires API level 16+. + +There have been reports that the OpenSL|ES backend fails to initialize on some Android based +devices due to `dlopen()` failing to open "libOpenSLES.so". If this happens on your platform +you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. -There have been reports that the OpenSL|ES backend fails to initialize on some Android based devices due to `dlopen()` failing to open "libOpenSLES.so". If -this happens on your platform you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. 2.6. Emscripten --------------- -The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. You cannot use -std=c* compiler flags, nor -ansi. +The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. +You cannot use `-std=c*` compiler flags, nor `-ansi`. 2.7. Build Options @@ -368,28 +625,26 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea +----------------------------------+--------------------------------------------------------------------+ | MA_NO_MP3 | Disables the built-in MP3 decoder. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_DEVICE_IO | Disables playback and recording. This will disable ma_context and | - | | ma_device APIs. This is useful if you only want to use miniaudio's | - | | data conversion and/or decoding APIs. | + | MA_NO_DEVICE_IO | Disables playback and recording. This will disable `ma_context` | + | | and `ma_device` APIs. This is useful if you only want to use | + | | miniaudio's data conversion and/or decoding APIs. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_THREADING | Disables the ma_thread, ma_mutex, ma_semaphore and ma_event APIs. | - | | This option is useful if you only need to use miniaudio for data | - | | conversion, decoding and/or encoding. Some families of APIs | - | | require threading which means the following options must also be | - | | set: | + | MA_NO_THREADING | Disables the `ma_thread`, `ma_mutex`, `ma_semaphore` and | + | | `ma_event` APIs. This option is useful if you only need to use | + | | miniaudio for data conversion, decoding and/or encoding. Some | + | | families of APIsrequire threading which means the following | + | | options must also be set: | | | | | | ``` | | | MA_NO_DEVICE_IO | | | ``` | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_GENERATION | Disables generation APIs such a ma_waveform and ma_noise. | + | MA_NO_GENERATION | Disables generation APIs such a `ma_waveform` and `ma_noise`. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_SSE2 | Disables SSE2 optimizations. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_AVX2 | Disables AVX2 optimizations. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_AVX512 | Disables AVX-512 optimizations. | - +----------------------------------+--------------------------------------------------------------------+ | MA_NO_NEON | Disables NEON optimizations. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_RUNTIME_LINKING | Disables runtime linking. This is useful for passing Apple's | @@ -401,47 +656,47 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea | | You may need to enable this if your target platform does not allow | | | runtime linking via `dlopen()`. | +----------------------------------+--------------------------------------------------------------------+ - | MA_DEBUG_OUTPUT | Enable processing of MA_LOG_LEVEL_DEBUG messages and `printf()` | - | | output. | + | MA_DEBUG_OUTPUT | Enable `printf()` output of debug logs (`MA_LOG_LEVEL_DEBUG`). | +----------------------------------+--------------------------------------------------------------------+ | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to | | | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | +----------------------------------+--------------------------------------------------------------------+ | MA_API | Controls how public APIs should be decorated. Default is `extern`. | +----------------------------------+--------------------------------------------------------------------+ - | MA_DLL | If set, configures MA_API to either import or export APIs | - | | depending on whether or not the implementation is being defined. | - | | If defining the implementation, MA_API will be configured to | - | | export. Otherwise it will be configured to import. This has no | - | | effect if MA_API is defined externally. | - +----------------------------------+--------------------------------------------------------------------+ 3. Definitions ============== -This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity in the use of terms throughout the audio space, so this -section is intended to clarify how miniaudio uses each term. +This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity +in the use of terms throughout the audio space, so this section is intended to clarify how miniaudio +uses each term. 3.1. Sample ----------- -A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit floating point number. +A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit +floating point number. 3.2. Frame / PCM Frame ---------------------- -A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 samples, a mono frame is 1 sample, a 5.1 surround sound frame -is 6 samples, etc. The terms "frame" and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. If ever miniaudio -needs to refer to a compressed frame, such as a FLAC frame, it will always clarify what it's referring to with something like "FLAC frame". +A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 +samples, a mono frame is 1 sample, a 5.1 surround sound frame is 6 samples, etc. The terms "frame" +and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. +If ever miniaudio needs to refer to a compressed frame, such as a FLAC frame, it will always +clarify what it's referring to with something like "FLAC frame". 3.3. Channel ------------ -A stream of monaural audio that is emitted from an individual speaker in a speaker system, or received from an individual microphone in a microphone system. A -stereo stream has two channels (a left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio systems refer to a channel as -a complex audio stream that's mixed with other channels to produce the final mix - this is completely different to miniaudio's use of the term "channel" and -should not be confused. +A stream of monaural audio that is emitted from an individual speaker in a speaker system, or +received from an individual microphone in a microphone system. A stereo stream has two channels (a +left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio +systems refer to a channel as a complex audio stream that's mixed with other channels to produce +the final mix - this is completely different to miniaudio's use of the term "channel" and should +not be confused. 3.4. Sample Rate ---------------- -The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number of PCM frames that are processed per second. +The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number +of PCM frames that are processed per second. 3.5. Formats ------------ @@ -461,10 +716,1685 @@ All formats are native-endian. -4. Decoding +4. Data Sources +=============== +The data source abstraction in miniaudio is used for retrieving audio data from some source. A few +examples include `ma_decoder`, `ma_noise` and `ma_waveform`. You will need to be familiar with data +sources in order to make sense of some of the higher level concepts in miniaudio. + +The `ma_data_source` API is a generic interface for reading from a data source. Any object that +implements the data source interface can be plugged into any `ma_data_source` function. + +To read data from a data source: + + ```c + ma_result result; + ma_uint64 framesRead; + + result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, &framesRead, loop); + if (result != MA_SUCCESS) { + return result; // Failed to read data from the data source. + } + ``` + +If you don't need the number of frames that were successfully read you can pass in `NULL` to the +`pFramesRead` parameter. If this returns a value less than the number of frames requested it means +the end of the file has been reached. `MA_AT_END` will be returned only when the number of frames +read is 0. + +When calling any data source function, with the exception of `ma_data_source_init()` and +`ma_data_source_uninit()`, you can pass in any object that implements a data source. For example, +you could plug in a decoder like so: + + ```c + ma_result result; + ma_uint64 framesRead; + ma_decoder decoder; // <-- This would be initialized with `ma_decoder_init_*()`. + + result = ma_data_source_read_pcm_frames(&decoder, pFramesOut, frameCount, &framesRead, loop); + if (result != MA_SUCCESS) { + return result; // Failed to read data from the decoder. + } + ``` + +If you want to seek forward you can pass in `NULL` to the `pFramesOut` parameter. Alternatively you +can use `ma_data_source_seek_pcm_frames()`. + +To seek to a specific PCM frame: + + ```c + result = ma_data_source_seek_to_pcm_frame(pDataSource, frameIndex); + if (result != MA_SUCCESS) { + return result; // Failed to seek to PCM frame. + } + ``` + +You can retrieve the total length of a data source in PCM frames, but note that some data sources +may not have the notion of a length, such as noise and waveforms, and others may just not have a +way of determining the length such as some decoders. To retrieve the length: + + ```c + ma_uint64 length; + + result = ma_data_source_get_length_in_pcm_frames(pDataSource, &length); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve the length. + } + ``` + +Care should be taken when retrieving the length of a data source where the underlying decoder is +pulling data from a data stream with an undefined length, such as internet radio or some kind of +broadcast. If you do this, `ma_data_source_get_length_in_pcm_frames()` may never return. + +The current position of the cursor in PCM frames can also be retrieved: + + ```c + ma_uint64 cursor; + + result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursor); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve the cursor. + } + ``` + +You will often need to know the data format that will be returned after reading. This can be +retrieved like so: + + ```c + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_channel channelMap[MA_MAX_CHANNELS]; + + result = ma_data_source_get_data_format(pDataSource, &format, &channels, &sampleRate, channelMap, MA_MAX_CHANNELS); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve data format. + } + ``` + +If you do not need a specific data format property, just pass in NULL to the respective parameter. + +There may be cases where you want to implement something like a sound bank where you only want to +read data within a certain range of the underlying data. To do this you can use a range: + + ```c + result = ma_data_source_set_range_in_pcm_frames(pDataSource, rangeBegInFrames, rangeEndInFrames); + if (result != MA_SUCCESS) { + return result; // Failed to set the range. + } + ``` + +This is useful if you have a sound bank where many sounds are stored in the same file and you want +the data source to only play one of those sub-sounds. + +Custom loop points can also be used with data sources. By default, data sources will loop after +they reach the end of the data source, but if you need to loop at a specific location, you can do +the following: + + ```c + result = ma_data_set_loop_point_in_pcm_frames(pDataSource, loopBegInFrames, loopEndInFrames); + if (result != MA_SUCCESS) { + return result; // Failed to set the loop point. + } + ``` + +The loop point is relative to the current range. + +It's sometimes useful to chain data sources together so that a seamless transition can be achieved. +To do this, you can use chaining: + + ```c + ma_decoder decoder1; + ma_decoder decoder2; + + // ... initialize decoders with ma_decoder_init_*() ... + + result = ma_data_source_set_next(&decoder1, &decoder2); + if (result != MA_SUCCESS) { + return result; // Failed to set the next data source. + } + + result = ma_data_source_read_pcm_frames(&decoder1, pFramesOut, frameCount, pFramesRead, MA_FALSE); + if (result != MA_SUCCESS) { + return result; // Failed to read from the decoder. + } + ``` + +In the example above we're using decoders. When reading from a chain, you always want to read from +the top level data source in the chain. In the example above, `decoder1` is the top level data +source in the chain. When `decoder1` reaches the end, `decoder2` will start seamlessly without any +gaps. + +Note that the `loop` parameter is set to false in the example above. When this is set to true, only +the current data source will be looped. You can loop the entire chain by linking in a loop like so: + + ```c + ma_data_source_set_next(&decoder1, &decoder2); // decoder1 -> decoder2 + ma_data_source_set_next(&decoder2, &decoder1); // decoder2 -> decoder1 (loop back to the start). + ``` + +Note that setting up chaining is not thread safe, so care needs to be taken if you're dynamically +changing links while the audio thread is in the middle of reading. + +Do not use `ma_decoder_seek_to_pcm_frame()` as a means to reuse a data source to play multiple +instances of the same sound simultaneously. Instead, initialize multiple data sources for each +instance. This can be extremely inefficient depending on the data source and can result in +glitching due to subtle changes to the state of internal filters. + + +4.1. Custom Data Sources +------------------------ +You can implement a custom data source by implementing the functions in `ma_data_source_vtable`. +Your custom object must have `ma_data_source_base` as it's first member: + + ```c + struct my_data_source + { + ma_data_source_base base; + ... + }; + ``` + +In your initialization routine, you need to call `ma_data_source_init()` in order to set up the +base object (`ma_data_source_base`): + + ```c + static ma_result my_data_source_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) + { + // Read data here. Output in the same format returned by my_data_source_get_data_format(). + } + + static ma_result my_data_source_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) + { + // Seek to a specific PCM frame here. Return MA_NOT_IMPLEMENTED if seeking is not supported. + } + + static ma_result my_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) + { + // Return the format of the data here. + } + + static ma_result my_data_source_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) + { + // Retrieve the current position of the cursor here. Return MA_NOT_IMPLEMENTED and set *pCursor to 0 if there is no notion of a cursor. + } + + static ma_result my_data_source_get_length(ma_data_source* pDataSource, ma_uint64* pLength) + { + // Retrieve the length in PCM frames here. Return MA_NOT_IMPLEMENTED and set *pLength to 0 if there is no notion of a length or if the length is unknown. + } + + static g_my_data_source_vtable = + { + my_data_source_read, + my_data_source_seek, + my_data_source_get_data_format, + my_data_source_get_cursor, + my_data_source_get_length + }; + + ma_result my_data_source_init(my_data_source* pMyDataSource) + { + ma_result result; + ma_data_source_config baseConfig; + + baseConfig = ma_data_source_config_init(); + baseConfig.vtable = &g_my_data_source_vtable; + + result = ma_data_source_init(&baseConfig, &pMyDataSource->base); + if (result != MA_SUCCESS) { + return result; + } + + // ... do the initialization of your custom data source here ... + + return MA_SUCCESS; + } + + void my_data_source_uninit(my_data_source* pMyDataSource) + { + // ... do the uninitialization of your custom data source here ... + + // You must uninitialize the base data source. + ma_data_source_uninit(&pMyDataSource->base); + } + ``` + +Note that `ma_data_source_init()` and `ma_data_source_uninit()` are never called directly outside +of the custom data source. It's up to the custom data source itself to call these within their own +init/uninit functions. + + + +5. Engine +========= +The `ma_engine` API is a high level API for managing and mixing sounds and effect processing. The +`ma_engine` object encapsulates a resource manager and a node graph, both of which will be +explained in more detail later. + +Sounds are called `ma_sound` and are created from an engine. Sounds can be associated with a mixing +group called `ma_sound_group` which are also created from the engine. Both `ma_sound` and +`ma_sound_group` objects are nodes within the engine's node graph. + +When the engine is initialized, it will normally create a device internally. If you would rather +manage the device yourself, you can do so and just pass a pointer to it via the engine config when +you initialize the engine. You can also just use the engine without a device, which again can be +configured via the engine config. + +The most basic way to initialize the engine is with a default config, like so: + + ```c + ma_result result; + ma_engine engine; + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +This will result in the engine initializing a playback device using the operating system's default +device. This will be sufficient for many use cases, but if you need more flexibility you'll want to +configure the engine with an engine config: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pPlaybackDevice = &myDevice; + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +In the example above we're passing in a pre-initialized device. Since the caller is the one in +control of the device's data callback, it's their responsibility to manually call +`ma_engine_read_pcm_frames()` from inside their data callback: + + ```c + void playback_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) + { + ma_engine_read_pcm_frames(&g_Engine, pOutput, frameCount, NULL); + } + ``` + +You can also use the engine independent of a device entirely: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.noDevice = MA_TRUE; + engineConfig.channels = 2; // Must be set when not using a device. + engineConfig.sampleRate = 48000; // Must be set when not using a device. + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +Note that when you're not using a device, you must set the channel count and sample rate in the +config or else miniaudio won't know what to use (miniaudio will use the device to determine this +normally). When not using a device, you need to use `ma_engine_read_pcm_frames()` to process audio +data from the engine. This kind of setup is useful if you want to do something like offline +processing. + +When a sound is loaded it goes through a resource manager. By default the engine will initialize a +resource manager internally, but you can also specify a pre-initialized resource manager: + + ```c + ma_result result; + ma_engine engine1; + ma_engine engine2; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &myResourceManager; + + ma_engine_init(&engineConfig, &engine1); + ma_engine_init(&engineConfig, &engine2); + ``` + +In this example we are initializing two engines, both of which are sharing the same resource +manager. This is especially useful for saving memory when loading the same file across multiple +engines. If you were not to use a shared resource manager, each engine instance would use their own +which would result in any sounds that are used between both engine's being loaded twice. By using +a shared resource manager, it would only be loaded once. Using multiple engine's is useful when you +need to output to multiple playback devices, such as in a local multiplayer game where each player +is using their own set of headphones. + +By default an engine will be in a started state. To make it so the engine is not automatically +started you can configure it as such: + + ```c + engineConfig.noAutoStart = MA_TRUE; + + // The engine will need to be started manually. + ma_engine_start(&engine); + + // Later on the engine can be stopped with ma_engine_stop(). + ma_engine_stop(&engine); + ``` + +The concept of starting or stopping an engine is only relevant when using the engine with a +device. Attempting to start or stop an engine that is not associated with a device will result in +`MA_INVALID_OPERATION`. + +The master volume of the engine can be controlled with `ma_engine_set_volume()` which takes a +linear scale, with 0 resulting in silence and anything above 1 resulting in amplification. If you +prefer decibel based volume control, use `ma_volume_db_to_linear()` to convert from dB to linear. + +When a sound is spatialized, it is done so relative to a listener. An engine can be configured to +have multiple listeners which can be configured via the config: + + ```c + engineConfig.listenerCount = 2; + ``` + +The maximum number of listeners is restricted to `MA_ENGINE_MAX_LISTENERS`. By default, when a +sound is spatialized, it will be done so relative to the closest listener. You can also pin a sound +to a specific listener which will be explained later. Listener's have a position, direction, cone, +and velocity (for doppler effect). A listener is referenced by an index, the meaning of which is up +to the caller (the index is 0 based and cannot go beyond the listener count, minus 1). The +position, direction and velocity are all specified in absolute terms: + + ```c + ma_engine_listener_set_position(&engine, listenerIndex, worldPosX, worldPosY, worldPosZ); + ``` + +The direction of the listener represents it's forward vector. The listener's up vector can also be +specified and defaults to +1 on the Y axis. + + ```c + ma_engine_listener_set_direction(&engine, listenerIndex, forwardX, forwardY, forwardZ); + ma_engine_listener_set_world_up(&engine, listenerIndex, 0, 1, 0); + ``` + +The engine supports directional attenuation. The listener can have a cone the controls how sound is +attenuated based on the listener's direction. When a sound is between the inner and outer cones, it +will be attenuated between 1 and the cone's outer gain: + + ```c + ma_engine_listener_set_cone(&engine, listenerIndex, innerAngleInRadians, outerAngleInRadians, outerGain); + ``` + +When a sound is inside the inner code, no directional attenuation is applied. When the sound is +outside of the outer cone, the attenuation will be set to `outerGain` in the example above. When +the sound is in between the inner and outer cones, the attenuation will be interpolated between 1 +and the outer gain. + +The engine's coordinate system follows the OpenGL coordinate system where positive X points right, +positive Y points up and negative Z points forward. + +The simplest and least flexible way to play a sound is like so: + + ```c + ma_engine_play_sound(&engine, "my_sound.wav", pGroup); + ``` + +This is a "fire and forget" style of function. The engine will manage the `ma_sound` object +internally. When the sound finishes playing, it'll be put up for recycling. For more flexibility +you'll want to initialize a sound object: + + ```c + ma_sound sound; + + result = ma_sound_init_from_file(&engine, "my_sound.wav", flags, pGroup, NULL, &sound); + if (result != MA_SUCCESS) { + return result; // Failed to load sound. + } + ``` + +Sounds need to be uninitialized with `ma_sound_uninit()`. + +The example above loads a sound from a file. If the resource manager has been disabled you will not +be able to use this function and instead you'll need to initialize a sound directly from a data +source: + + ```c + ma_sound sound; + + result = ma_sound_init_from_data_source(&engine, &dataSource, flags, pGroup, &sound); + if (result != MA_SUCCESS) { + return result; + } + ``` + +Each `ma_sound` object represents a single instance of the sound. If you want to play the same +sound multiple times at the same time, you need to initialize a separate `ma_sound` object. + +For the most flexibility when initializing sounds, use `ma_sound_init_ex()`. This uses miniaudio's +standard config/init pattern: + + ```c + ma_sound sound; + ma_sound_config soundConfig; + + soundConfig = ma_sound_config_init(); + soundConfig.pFilePath = NULL; // Set this to load from a file path. + soundConfig.pDataSource = NULL; // Set this to initialize from an existing data source. + soundConfig.pInitialAttachment = &someNodeInTheNodeGraph; + soundConfig.initialAttachmentInputBusIndex = 0; + soundConfig.channelsIn = 1; + soundConfig.channelsOut = 0; // Set to 0 to use the engine's native channel count. + + result = ma_sound_init_ex(&soundConfig, &sound); + if (result != MA_SUCCESS) { + return result; + } + ``` + +In the example above, the sound is being initialized without a file nor a data source. This is +valid, in which case the sound acts as a node in the middle of the node graph. This means you can +connect other sounds to this sound and allow it to act like a sound group. Indeed, this is exactly +what a `ma_sound_group` is. + +When loading a sound, you specify a set of flags that control how the sound is loaded and what +features are enabled for that sound. When no flags are set, the sound will be fully loaded into +memory in exactly the same format as how it's stored on the file system. The resource manager will +allocate a block of memory and then load the file directly into it. When reading audio data, it +will be decoded dynamically on the fly. In order to save processing time on the audio thread, it +might be beneficial to pre-decode the sound. You can do this with the `MA_SOUND_FLAG_DECODE` flag: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE, pGroup, NULL, &sound); + ``` + +By default, sounds will be loaded synchronously, meaning `ma_sound_init_*()` will not return until +the sound has been fully loaded. If this is prohibitive you can instead load sounds asynchronously +by specificying the `MA_SOUND_FLAG_ASYNC` flag: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, NULL, &sound); + ``` + +This will result in `ma_sound_init_*()` returning quickly, but the sound won't yet have been fully +loaded. When you start the sound, it won't output anything until some sound is available. The sound +will start outputting audio before the sound has been fully decoded when the `MA_SOUND_FLAG_DECODE` +is specified. + +If you need to wait for an asynchronously loaded sound to be fully loaded, you can use a fence. A +fence in miniaudio is a simple synchronization mechanism which simply blocks until it's internal +counter hit's zero. You can specify a fence like so: + + ```c + ma_result result; + ma_fence fence; + ma_sound sounds[4]; + + result = ma_fence_init(&fence); + if (result != MA_SUCCES) { + return result; + } + + // Load some sounds asynchronously. + for (int iSound = 0; iSound < 4; iSound += 1) { + ma_sound_init_from_file(&engine, mySoundFilesPaths[iSound], MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, &fence, &sounds[iSound]); + } + + // ... do some other stuff here in the mean time ... + + // Wait for all sounds to finish loading. + ma_fence_wait(&fence); + ``` + +If loading the entire sound into memory is prohibitive, you can also configure the engine to stream +the audio data: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_STREAM, pGroup, NULL, &sound); + ``` + +When streaming sounds, 2 seconds worth of audio data is stored in memory. Although it should work +fine, it's inefficient to use streaming for short sounds. Streaming is useful for things like music +tracks in games. + +When you initialize a sound, if you specify a sound group the sound will be attached to that group +automatically. If you set it to NULL, it will be automatically attached to the engine's endpoint. +If you would instead rather leave the sound unattached by default, you can can specify the +`MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT` flag. This is useful if you want to set up a complex node +graph. + +Sounds are not started by default. To start a sound, use `ma_sound_start()`. Stop a sound with +`ma_sound_stop()`. + +Sounds can have their volume controlled with `ma_sound_set_volume()` in the same way as the +engine's master volume. + +Sounds support stereo panning and pitching. Set the pan with `ma_sound_set_pan()`. Setting the pan +to 0 will result in an unpanned sound. Setting it to -1 will shift everything to the left, whereas ++1 will shift it to the right. The pitch can be controlled with `ma_sound_set_pitch()`. A larger +value will result in a higher pitch. The pitch must be greater than 0. + +The engine supports 3D spatialization of sounds. By default sounds will have spatialization +enabled, but if a sound does not need to be spatialized it's best to disable it. There are two ways +to disable spatialization of a sound: + + ```c + // Disable spatialization at initialization time via a flag: + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_NO_SPATIALIZATION, NULL, NULL, &sound); + + // Dynamically disable or enable spatialization post-initialization: + ma_sound_set_spatialization_enabled(&sound, isSpatializationEnabled); + ``` + +By default sounds will be spatialized based on the closest listener. If a sound should always be +spatialized relative to a specific listener it can be pinned to one: + + ```c + ma_sound_set_pinned_listener_index(&sound, listenerIndex); + ``` + +Like listeners, sounds have a position. By default, the position of a sound is in absolute space, +but it can be changed to be relative to a listener: + + ```c + ma_sound_set_positioning(&sound, ma_positioning_relative); + ``` + +Note that relative positioning of a sound only makes sense if there is either only one listener, or +the sound is pinned to a specific listener. To set the position of a sound: + + ```c + ma_sound_set_position(&sound, posX, posY, posZ); + ``` + +The direction works the same way as a listener and represents the sound's forward direction: + + ```c + ma_sound_set_direction(&sound, forwardX, forwardY, forwardZ); + ``` + +Sound's also have a cone for controlling directional attenuation. This works exactly the same as +listeners: + + ```c + ma_sound_set_cone(&sound, innerAngleInRadians, outerAngleInRadians, outerGain); + ``` + +The velocity of a sound is used for doppler effect and can be set as such: + + ```c + ma_sound_set_velocity(&sound, velocityX, velocityY, velocityZ); + ``` + +The engine supports different attenuation models which can be configured on a per-sound basis. By +default the attenuation model is set to `ma_attenuation_model_inverse` which is the equivalent to +OpenAL's `AL_INVERSE_DISTANCE_CLAMPED`. Configure the attenuation model like so: + + ```c + ma_sound_set_attenuation_model(&sound, ma_attenuation_model_inverse); + ``` + +The supported attenuation models include the following: + + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_none | No distance attenuation. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_inverse | Equivalent to `AL_INVERSE_DISTANCE_CLAMPED`. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_linear | Linear attenuation. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_exponential | Exponential attenuation. | + +----------------------------------+----------------------------------------------+ + +To control how quickly a sound rolls off as it moves away from the listener, you need to configure +the rolloff: + + ```c + ma_sound_set_rolloff(&sound, rolloff); + ``` + +You can control the minimum and maximum gain to apply from spatialization: + + ```c + ma_sound_set_min_gain(&sound, minGain); + ma_sound_set_max_gain(&sound, maxGain); + ``` + +Likewise, in the calculation of attenuation, you can control the minimum and maximum distances for +the attenuation calculation. This is useful if you want to ensure sounds don't drop below a certain +volume after the listener moves further away and to have sounds play a maximum volume when the +listener is within a certain distance: + + ```c + ma_sound_set_min_distance(&sound, minDistance); + ma_sound_set_max_distance(&sound, maxDistance); + ``` + +The engine's spatialization system supports doppler effect. The doppler factor can be configure on +a per-sound basis like so: + + ```c + ma_sound_set_doppler_factor(&sound, dopplerFactor); + ``` + +You can fade sounds in and out with `ma_sound_set_fade_in_pcm_frames()` and +`ma_sound_set_fade_in_milliseconds()`. Set the volume to -1 to use the current volume as the +starting volume: + + ```c + // Fade in over 1 second. + ma_sound_set_fade_in_milliseconds(&sound, 0, 1, 1000); + + // ... sometime later ... + + // Fade out over 1 second, starting from the current volume. + ma_sound_set_fade_in_milliseconds(&sound, -1, 0, 1000); + ``` + +By default sounds will start immediately, but sometimes for timing and synchronization purposes it +can be useful to schedule a sound to start or stop: + + ```c + // Start the sound in 1 second from now. + ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 1)); + + // Stop the sound in 2 seconds from now. + ma_sound_set_stop_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2)); + ``` + +Note that scheduling a start time still requires an explicit call to `ma_sound_start()` before +anything will play. + +The time is specified in global time which is controlled by the engine. You can get the engine's +current time with `ma_engine_get_time()`. The engine's global time is incremented automatically as +audio data is read, but it can be reset with `ma_engine_set_time()` in case it needs to be +resynchronized for some reason. + +To determine whether or not a sound is currently playing, use `ma_sound_is_playing()`. This will +take the scheduled start and stop times into account. + +Whether or not a sound should loop can be controlled with `ma_sound_set_looping()`. Sounds will not +be looping by default. Use `ma_sound_is_looping()` to determine whether or not a sound is looping. + +Use `ma_sound_at_end()` to determine whether or not a sound is currently at the end. For a looping +sound this should never return true. + +Internally a sound wraps around a data source. Some APIs exist to control the underlying data +source, mainly for convenience: + + ```c + ma_sound_seek_to_pcm_frame(&sound, frameIndex); + ma_sound_get_data_format(&sound, &format, &channels, &sampleRate, pChannelMap, channelMapCapacity); + ma_sound_get_cursor_in_pcm_frames(&sound, &cursor); + ma_sound_get_length_in_pcm_frames(&sound, &length); + ``` + +Sound groups have the same API as sounds, only they are called `ma_sound_group`, and since they do +not have any notion of a data source, anything relating to a data source is unavailable. + +Internally, sound data is loaded via the `ma_decoder` API which means by default in only supports +file formats that have built-in support in miniaudio. You can extend this to support any kind of +file format through the use of custom decoders. To do this you'll need to use a self-managed +resource manager and configure it appropriately. See the "Resource Management" section below for +details on how to set this up. + + +6. Resource Management +====================== +Many programs will want to manage sound resources for things such as reference counting and +streaming. This is supported by miniaudio via the `ma_resource_manager` API. + +The resource manager is mainly responsible for the following: + + * Loading of sound files into memory with reference counting. + * Streaming of sound data + +When loading a sound file, the resource manager will give you back a `ma_data_source` compatible +object called `ma_resource_manager_data_source`. This object can be passed into any +`ma_data_source` API which is how you can read and seek audio data. When loading a sound file, you +specify whether or not you want the sound to be fully loaded into memory (and optionally +pre-decoded) or streamed. When loading into memory, you can also specify whether or not you want +the data to be loaded asynchronously. + +The example below is how you can initialize a resource manager using it's default configuration: + + ```c + ma_resource_manager_config config; + ma_resource_manager resourceManager; + + config = ma_resource_manager_config_init(); + result = ma_resource_manager_init(&config, &resourceManager); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to initialize the resource manager."); + return -1; + } + ``` + +You can configure the format, channels and sample rate of the decoded audio data. By default it +will use the file's native data format, but you can configure it to use a consistent format. This +is useful for offloading the cost of data conversion to load time rather than dynamically +converting at mixing time. To do this, you configure the decoded format, channels and sample rate +like the code below: + + ```c + config = ma_resource_manager_config_init(); + config.decodedFormat = device.playback.format; + config.decodedChannels = device.playback.channels; + config.decodedSampleRate = device.sampleRate; + ``` + +In the code above, the resource manager will be configured so that any decoded audio data will be +pre-converted at load time to the device's native data format. If instead you used defaults and +the data format of the file did not match the device's data format, you would need to convert the +data at mixing time which may be prohibitive in high-performance and large scale scenarios like +games. + +Internally the resource manager uses the `ma_decoder` API to load sounds. This means by default it +only supports decoders that are built into miniaudio. It's possible to support additional encoding +formats through the use of custom decoders. To do so, pass in your `ma_decoding_backend_vtable` +vtables into the resource manager config: + + ```c + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + ... + + resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables; + resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + resourceManagerConfig.pCustomDecodingBackendUserData = NULL; + ``` + +This system can allow you to support any kind of file format. See the "Decoding" section for +details on how to implement custom decoders. The miniaudio repository includes examples for Opus +via libopus and libopusfile and Vorbis via libvorbis and libvorbisfile. + +Asynchronicity is achieved via a job system. When an operation needs to be performed, such as the +decoding of a page, a job will be posted to a queue which will then be processed by a job thread. +By default there will be only one job thread running, but this can be configured, like so: + + ```c + config = ma_resource_manager_config_init(); + config.jobThreadCount = MY_JOB_THREAD_COUNT; + ``` + +By default job threads are managed internally by the resource manager, however you can also self +manage your job threads if, for example, you want to integrate the job processing into your +existing job infrastructure, or if you simply don't like the way the resource manager does it. To +do this, just set the job thread count to 0 and process jobs manually. To process jobs, you first +need to retrieve a job using `ma_resource_manager_next_job()` and then process it using +`ma_job_process()`: + + ```c + config = ma_resource_manager_config_init(); + config.jobThreadCount = 0; // Don't manage any job threads internally. + config.flags = MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; // Optional. Makes `ma_resource_manager_next_job()` non-blocking. + + // ... Initialize your custom job threads ... + + void my_custom_job_thread(...) + { + for (;;) { + ma_job job; + ma_result result = ma_resource_manager_next_job(pMyResourceManager, &job); + if (result != MA_SUCCESS) { + if (result == MA_NOT_DATA_AVAILABLE) { + // No jobs are available. Keep going. Will only get this if the resource manager was initialized + // with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. + continue; + } else if (result == MA_CANCELLED) { + // MA_JOB_TYPE_QUIT was posted. Exit. + break; + } else { + // Some other error occurred. + break; + } + } + + ma_job_process(&job); + } + } + ``` + +In the example above, the `MA_JOB_TYPE_QUIT` event is the used as the termination +indicator, but you can use whatever you would like to terminate the thread. The call to +`ma_resource_manager_next_job()` is blocking by default, but can be configured to be non-blocking +by initializing the resource manager with the `MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING` configuration +flag. Note that the `MA_JOB_TYPE_QUIT` will never be removed from the job queue. This +is to give every thread the opportunity to catch the event and terminate naturally. + +When loading a file, it's sometimes convenient to be able to customize how files are opened and +read instead of using standard `fopen()`, `fclose()`, etc. which is what miniaudio will use by +default. This can be done by setting `pVFS` member of the resource manager's config: + + ```c + // Initialize your custom VFS object. See documentation for VFS for information on how to do this. + my_custom_vfs vfs = my_custom_vfs_init(); + + config = ma_resource_manager_config_init(); + config.pVFS = &vfs; + ``` + +This is particularly useful in programs like games where you want to read straight from an archive +rather than the normal file system. If you do not specify a custom VFS, the resource manager will +use the operating system's normal file operations. This is default. + +To load a sound file and create a data source, call `ma_resource_manager_data_source_init()`. When +loading a sound you need to specify the file path and options for how the sounds should be loaded. +By default a sound will be loaded synchronously. The returned data source is owned by the caller +which means the caller is responsible for the allocation and freeing of the data source. Below is +an example for initializing a data source: + + ```c + ma_resource_manager_data_source dataSource; + ma_result result = ma_resource_manager_data_source_init(pResourceManager, pFilePath, flags, &dataSource); + if (result != MA_SUCCESS) { + // Error. + } + + // ... + + // A ma_resource_manager_data_source object is compatible with the `ma_data_source` API. To read data, just call + // the `ma_data_source_read_pcm_frames()` like you would with any normal data source. + result = ma_data_source_read_pcm_frames(&dataSource, pDecodedData, frameCount, &framesRead); + if (result != MA_SUCCESS) { + // Failed to read PCM frames. + } + + // ... + + ma_resource_manager_data_source_uninit(pResourceManager, &dataSource); + ``` + +The `flags` parameter specifies how you want to perform loading of the sound file. It can be a +combination of the following flags: + + ``` + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT + ``` + +When no flags are specified (set to 0), the sound will be fully loaded into memory, but not +decoded, meaning the raw file data will be stored in memory, and then dynamically decoded when +`ma_data_source_read_pcm_frames()` is called. To instead decode the audio data before storing it in +memory, use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` flag. By default, the sound file will +be loaded synchronously, meaning `ma_resource_manager_data_source_init()` will only return after +the entire file has been loaded. This is good for simplicity, but can be prohibitively slow. You +can instead load the sound asynchronously using the `MA_RESOURCE_MANAGER_DATA_SOURCE_ASYNC` flag. +This will result in `ma_resource_manager_data_source_init()` returning quickly, but no data will be +returned by `ma_data_source_read_pcm_frames()` until some data is available. When no data is +available because the asynchronous decoding hasn't caught up, `MA_BUSY` will be returned by +`ma_data_source_read_pcm_frames()`. + +For large sounds, it's often prohibitive to store the entire file in memory. To mitigate this, you +can instead stream audio data which you can do by specifying the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag. When streaming, data will be decoded in 1 +second pages. When a new page needs to be decoded, a job will be posted to the job queue and then +subsequently processed in a job thread. + +For in-memory sounds, reference counting is used to ensure the data is loaded only once. This means +multiple calls to `ma_resource_manager_data_source_init()` with the same file path will result in +the file data only being loaded once. Each call to `ma_resource_manager_data_source_init()` must be +matched up with a call to `ma_resource_manager_data_source_uninit()`. Sometimes it can be useful +for a program to register self-managed raw audio data and associate it with a file path. Use the +`ma_resource_manager_register_*()` and `ma_resource_manager_unregister_*()` APIs to do this. +`ma_resource_manager_register_decoded_data()` is used to associate a pointer to raw, self-managed +decoded audio data in the specified data format with the specified name. Likewise, +`ma_resource_manager_register_encoded_data()` is used to associate a pointer to raw self-managed +encoded audio data (the raw file data) with the specified name. Note that these names need not be +actual file paths. When `ma_resource_manager_data_source_init()` is called (without the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag), the resource manager will look for these +explicitly registered data buffers and, if found, will use it as the backing data for the data +source. Note that the resource manager does *not* make a copy of this data so it is up to the +caller to ensure the pointer stays valid for it's lifetime. Use +`ma_resource_manager_unregister_data()` to unregister the self-managed data. You can also use +`ma_resource_manager_register_file()` and `ma_resource_manager_unregister_file()` to register and +unregister a file. It does not make sense to use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` +flag with a self-managed data pointer. + + +6.1. Asynchronous Loading and Synchronization +--------------------------------------------- +When loading asynchronously, it can be useful to poll whether or not loading has finished. Use +`ma_resource_manager_data_source_result()` to determine this. For in-memory sounds, this will +return `MA_SUCCESS` when the file has been *entirely* decoded. If the sound is still being decoded, +`MA_BUSY` will be returned. Otherwise, some other error code will be returned if the sound failed +to load. For streaming data sources, `MA_SUCCESS` will be returned when the first page has been +decoded and the sound is ready to be played. If the first page is still being decoded, `MA_BUSY` +will be returned. Otherwise, some other error code will be returned if the sound failed to load. + +In addition to polling, you can also use a simple synchronization object called a "fence" to wait +for asynchronously loaded sounds to finish. This is called `ma_fence`. The advantage to using a +fence is that it can be used to wait for a group of sounds to finish loading rather than waiting +for sounds on an individual basis. There are two stages to loading a sound: + + * Initialization of the internal decoder; and + * Completion of decoding of the file (the file is fully decoded) + +You can specify separate fences for each of the different stages. Waiting for the initialization +of the internal decoder is important for when you need to know the sample format, channels and +sample rate of the file. + +The example below shows how you could use a fence when loading a number of sounds: + + ```c + // This fence will be released when all sounds are finished loading entirely. + ma_fence fence; + ma_fence_init(&fence); + + // This will be passed into the initialization routine for each sound. + ma_resource_manager_pipeline_notifications notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pFence = &fence; + + // Now load a bunch of sounds: + for (iSound = 0; iSound < soundCount; iSound += 1) { + ma_resource_manager_data_source_init(pResourceManager, pSoundFilePaths[iSound], flags, ¬ifications, &pSoundSources[iSound]); + } + + // ... DO SOMETHING ELSE WHILE SOUNDS ARE LOADING ... + + // Wait for loading of sounds to finish. + ma_fence_wait(&fence); + ``` + +In the example above we used a fence for waiting until the entire file has been fully decoded. If +you only need to wait for the initialization of the internal decoder to complete, you can use the +`init` member of the `ma_resource_manager_pipeline_notifications` object: + + ```c + notifications.init.pFence = &fence; + ``` + +If a fence is not appropriate for your situation, you can instead use a callback that is fired on +an individual sound basis. This is done in a very similar way to fences: + + ```c + typedef struct + { + ma_async_notification_callbacks cb; + void* pMyData; + } my_notification; + + void my_notification_callback(ma_async_notification* pNotification) + { + my_notification* pMyNotification = (my_notification*)pNotification; + + // Do something in response to the sound finishing loading. + } + + ... + + my_notification myCallback; + myCallback.cb.onSignal = my_notification_callback; + myCallback.pMyData = pMyData; + + ma_resource_manager_pipeline_notifications notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pNotification = &myCallback; + + ma_resource_manager_data_source_init(pResourceManager, "my_sound.wav", flags, ¬ifications, &mySound); + ``` + +In the example above we just extend the `ma_async_notification_callbacks` object and pass an +instantiation into the `ma_resource_manager_pipeline_notifications` in the same way as we did with +the fence, only we set `pNotification` instead of `pFence`. You can set both of these at the same +time and they should both work as expected. If using the `pNotification` system, you need to ensure +your `ma_async_notification_callbacks` object stays valid. + + + +6.2. Resource Manager Implementation Details +-------------------------------------------- +Resources are managed in two main ways: + + * By storing the entire sound inside an in-memory buffer (referred to as a data buffer) + * By streaming audio data on the fly (referred to as a data stream) + +A resource managed data source (`ma_resource_manager_data_source`) encapsulates a data buffer or +data stream, depending on whether or not the data source was initialized with the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag. If so, it will make use of a +`ma_resource_manager_data_stream` object. Otherwise it will use a `ma_resource_manager_data_buffer` +object. Both of these objects are data sources which means they can be used with any +`ma_data_source_*()` API. + +Another major feature of the resource manager is the ability to asynchronously decode audio files. +This relieves the audio thread of time-consuming decoding which can negatively affect scalability +due to the audio thread needing to complete it's work extremely quickly to avoid glitching. +Asynchronous decoding is achieved through a job system. There is a central multi-producer, +multi-consumer, fixed-capacity job queue. When some asynchronous work needs to be done, a job is +posted to the queue which is then read by a job thread. The number of job threads can be +configured for improved scalability, and job threads can all run in parallel without needing to +worry about the order of execution (how this is achieved is explained below). + +When a sound is being loaded asynchronously, playback can begin before the sound has been fully +decoded. This enables the application to start playback of the sound quickly, while at the same +time allowing to resource manager to keep loading in the background. Since there may be less +threads than the number of sounds being loaded at a given time, a simple scheduling system is used +to keep decoding time balanced and fair. The resource manager solves this by splitting decoding +into chunks called pages. By default, each page is 1 second long. When a page has been decoded, a +new job will be posted to start decoding the next page. By dividing up decoding into pages, an +individual sound shouldn't ever delay every other sound from having their first page decoded. Of +course, when loading many sounds at the same time, there will always be an amount of time required +to process jobs in the queue so in heavy load situations there will still be some delay. To +determine if a data source is ready to have some frames read, use +`ma_resource_manager_data_source_get_available_frames()`. This will return the number of frames +available starting from the current position. + + +6.2.1. Job Queue +---------------- +The resource manager uses a job queue which is multi-producer, multi-consumer, and fixed-capacity. +This job queue is not currently lock-free, and instead uses a spinlock to achieve thread-safety. +Only a fixed number of jobs can be allocated and inserted into the queue which is done through a +lock-free data structure for allocating an index into a fixed sized array, with reference counting +for mitigation of the ABA problem. The reference count is 32-bit. + +For many types of jobs it's important that they execute in a specific order. In these cases, jobs +are executed serially. For the resource manager, serial execution of jobs is only required on a +per-object basis (per data buffer or per data stream). Each of these objects stores an execution +counter. When a job is posted it is associated with an execution counter. When the job is +processed, it checks if the execution counter of the job equals the execution counter of the +owning object and if so, processes the job. If the counters are not equal, the job will be posted +back onto the job queue for later processing. When the job finishes processing the execution order +of the main object is incremented. This system means the no matter how many job threads are +executing, decoding of an individual sound will always get processed serially. The advantage to +having multiple threads comes into play when loading multiple sounds at the same time. + +The resource manager's job queue is not 100% lock-free and will use a spinlock to achieve +thread-safety for a very small section of code. This is only relevant when the resource manager +uses more than one job thread. If only using a single job thread, which is the default, the +lock should never actually wait in practice. The amount of time spent locking should be quite +short, but it's something to be aware of for those who have pedantic lock-free requirements and +need to use more than one job thread. There are plans to remove this lock in a future version. + +In addition, posting a job will release a semaphore, which on Win32 is implemented with +`ReleaseSemaphore` and on POSIX platforms via a condition variable: + + ```c + pthread_mutex_lock(&pSemaphore->lock); + { + pSemaphore->value += 1; + pthread_cond_signal(&pSemaphore->cond); + } + pthread_mutex_unlock(&pSemaphore->lock); + ``` + +Again, this is relevant for those with strict lock-free requirements in the audio thread. To avoid +this, you can use non-blocking mode (via the `MA_JOB_QUEUE_FLAG_NON_BLOCKING` +flag) and implement your own job processing routine (see the "Resource Manager" section above for +details on how to do this). + + + +6.2.2. Data Buffers +------------------- +When the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag is excluded at initialization time, the +resource manager will try to load the data into an in-memory data buffer. Before doing so, however, +it will first check if the specified file is already loaded. If so, it will increment a reference +counter and just use the already loaded data. This saves both time and memory. When the data buffer +is uninitialized, the reference counter will be decremented. If the counter hits zero, the file +will be unloaded. This is a detail to keep in mind because it could result in excessive loading and +unloading of a sound. For example, the following sequence will result in a file be loaded twice, +once after the other: + + ```c + ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer0); // Refcount = 1. Initial load. + ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer0); // Refcount = 0. Unloaded. + + ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer1); // Refcount = 1. Reloaded because previous uninit() unloaded it. + ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer1); // Refcount = 0. Unloaded. + ``` + +A binary search tree (BST) is used for storing data buffers as it has good balance between +efficiency and simplicity. The key of the BST is a 64-bit hash of the file path that was passed +into `ma_resource_manager_data_source_init()`. The advantage of using a hash is that it saves +memory over storing the entire path, has faster comparisons, and results in a mostly balanced BST +due to the random nature of the hash. The disadvantage is that file names are case-sensitive. If +this is an issue, you should normalize your file names to upper- or lower-case before initializing +your data sources. + +When a sound file has not already been loaded and the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` +flag is excluded, the file will be decoded synchronously by the calling thread. There are two +options for controlling how the audio is stored in the data buffer - encoded or decoded. When the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` option is excluded, the raw file data will be stored +in memory. Otherwise the sound will be decoded before storing it in memory. Synchronous loading is +a very simple and standard process of simply adding an item to the BST, allocating a block of +memory and then decoding (if `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` is specified). + +When the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` flag is specified, loading of the data buffer +is done asynchronously. In this case, a job is posted to the queue to start loading and then the +function immediately returns, setting an internal result code to `MA_BUSY`. This result code is +returned when the program calls `ma_resource_manager_data_source_result()`. When decoding has fully +completed `MA_SUCCESS` will be returned. This can be used to know if loading has fully completed. + +When loading asynchronously, a single job is posted to the queue of the type +`MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE`. This involves making a copy of the file path and +associating it with job. When the job is processed by the job thread, it will first load the file +using the VFS associated with the resource manager. When using a custom VFS, it's important that it +be completely thread-safe because it will be used from one or more job threads at the same time. +Individual files should only ever be accessed by one thread at a time, however. After opening the +file via the VFS, the job will determine whether or not the file is being decoded. If not, it +simply allocates a block of memory and loads the raw file contents into it and returns. On the +other hand, when the file is being decoded, it will first allocate a decoder on the heap and +initialize it. Then it will check if the length of the file is known. If so it will allocate a +block of memory to store the decoded output and initialize it to silence. If the size is unknown, +it will allocate room for one page. After memory has been allocated, the first page will be +decoded. If the sound is shorter than a page, the result code will be set to `MA_SUCCESS` and the +completion event will be signalled and loading is now complete. If, however, there is more to +decode, a job with the code `MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE` is posted. This job +will decode the next page and perform the same process if it reaches the end. If there is more to +decode, the job will post another `MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE` job which will +keep on happening until the sound has been fully decoded. For sounds of an unknown length, each +page will be linked together as a linked list. Internally this is implemented via the +`ma_paged_audio_buffer` object. + + +6.2.3. Data Streams +------------------- +Data streams only ever store two pages worth of data for each instance. They are most useful for +large sounds like music tracks in games that would consume too much memory if fully decoded in +memory. After every frame from a page has been read, a job will be posted to load the next page +which is done from the VFS. + +For data streams, the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` flag will determine whether or +not initialization of the data source waits until the two pages have been decoded. When unset, +`ma_resource_manager_data_source_init()` will wait until the two pages have been loaded, otherwise +it will return immediately. + +When frames are read from a data stream using `ma_resource_manager_data_source_read_pcm_frames()`, +`MA_BUSY` will be returned if there are no frames available. If there are some frames available, +but less than the number requested, `MA_SUCCESS` will be returned, but the actual number of frames +read will be less than the number requested. Due to the asynchronous nature of data streams, +seeking is also asynchronous. If the data stream is in the middle of a seek, `MA_BUSY` will be +returned when trying to read frames. + +When `ma_resource_manager_data_source_read_pcm_frames()` results in a page getting fully consumed +a job is posted to load the next page. This will be posted from the same thread that called +`ma_resource_manager_data_source_read_pcm_frames()`. + +Data streams are uninitialized by posting a job to the queue, but the function won't return until +that job has been processed. The reason for this is that the caller owns the data stream object and +therefore miniaudio needs to ensure everything completes before handing back control to the caller. +Also, if the data stream is uninitialized while pages are in the middle of decoding, they must +complete before destroying any underlying object and the job system handles this cleanly. + +Note that when a new page needs to be loaded, a job will be posted to the resource manager's job +thread from the audio thread. You must keep in mind the details mentioned in the "Job Queue" +section above regarding locking when posting an event if you require a strictly lock-free audio +thread. + + + +7. Node Graph +============= +miniaudio's routing infrastructure follows a node graph paradigm. The idea is that you create a +node whose outputs are attached to inputs of another node, thereby creating a graph. There are +different types of nodes, with each node in the graph processing input data to produce output, +which is then fed through the chain. Each node in the graph can apply their own custom effects. At +the start of the graph will usually be one or more data source nodes which have no inputs, but +instead pull their data from a data source. At the end of the graph is an endpoint which represents +the end of the chain and is where the final output is ultimately extracted from. + +Each node has a number of input buses and a number of output buses. An output bus from a node is +attached to an input bus of another. Multiple nodes can connect their output buses to another +node's input bus, in which case their outputs will be mixed before processing by the node. Below is +a diagram that illustrates a hypothetical node graph setup: + + ``` + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Data flows left to right >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + +---------------+ +-----------------+ + | Data Source 1 =----+ +----------+ +----= Low Pass Filter =----+ + +---------------+ | | =----+ +-----------------+ | +----------+ + +----= Splitter | +----= ENDPOINT | + +---------------+ | | =----+ +-----------------+ | +----------+ + | Data Source 2 =----+ +----------+ +----= Echo / Delay =----+ + +---------------+ +-----------------+ + ``` + +In the above graph, it starts with two data sources whose outputs are attached to the input of a +splitter node. It's at this point that the two data sources are mixed. After mixing, the splitter +performs it's processing routine and produces two outputs which is simply a duplication of the +input stream. One output is attached to a low pass filter, whereas the other output is attached to +a echo/delay. The outputs of the the low pass filter and the echo are attached to the endpoint, and +since they're both connected to the same input but, they'll be mixed. + +Each input bus must be configured to accept the same number of channels, but the number of channels +used by input buses can be different to the number of channels for output buses in which case +miniaudio will automatically convert the input data to the output channel count before processing. +The number of channels of an output bus of one node must match the channel count of the input bus +it's attached to. The channel counts cannot be changed after the node has been initialized. If you +attempt to attach an output bus to an input bus with a different channel count, attachment will +fail. + +To use a node graph, you first need to initialize a `ma_node_graph` object. This is essentially a +container around the entire graph. The `ma_node_graph` object is required for some thread-safety +issues which will be explained later. A `ma_node_graph` object is initialized using miniaudio's +standard config/init system: + + ```c + ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(myChannelCount); + + result = ma_node_graph_init(&nodeGraphConfig, NULL, &nodeGraph); // Second parameter is a pointer to allocation callbacks. + if (result != MA_SUCCESS) { + // Failed to initialize node graph. + } + ``` + +When you initialize the node graph, you're specifying the channel count of the endpoint. The +endpoint is a special node which has one input bus and one output bus, both of which have the +same channel count, which is specified in the config. Any nodes that connect directly to the +endpoint must be configured such that their output buses have the same channel count. When you read +audio data from the node graph, it'll have the channel count you specified in the config. To read +data from the graph: + + ```c + ma_uint32 framesRead; + result = ma_node_graph_read_pcm_frames(&nodeGraph, pFramesOut, frameCount, &framesRead); + if (result != MA_SUCCESS) { + // Failed to read data from the node graph. + } + ``` + +When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in +data from it's input attachments, which in turn recusively pull in data from their inputs, and so +on. At the start of the graph there will be some kind of data source node which will have zero +inputs and will instead read directly from a data source. The base nodes don't literally need to +read from a `ma_data_source` object, but they will always have some kind of underlying object that +sources some kind of audio. The `ma_data_source_node` node can be used to read from a +`ma_data_source`. Data is always in floating-point format and in the number of channels you +specified when the graph was initialized. The sample rate is defined by the underlying data sources. +It's up to you to ensure they use a consistent and appropraite sample rate. + +The `ma_node` API is designed to allow custom nodes to be implemented with relative ease, but +miniaudio includes a few stock nodes for common functionality. This is how you would initialize a +node which reads directly from a data source (`ma_data_source_node`) which is an example of one +of the stock nodes that comes with miniaudio: + + ```c + ma_data_source_node_config config = ma_data_source_node_config_init(pMyDataSource); + + ma_data_source_node dataSourceNode; + result = ma_data_source_node_init(&nodeGraph, &config, NULL, &dataSourceNode); + if (result != MA_SUCCESS) { + // Failed to create data source node. + } + ``` + +The data source node will use the output channel count to determine the channel count of the output +bus. There will be 1 output bus and 0 input buses (data will be drawn directly from the data +source). The data source must output to floating-point (`ma_format_f32`) or else an error will be +returned from `ma_data_source_node_init()`. + +By default the node will not be attached to the graph. To do so, use `ma_node_attach_output_bus()`: + + ```c + result = ma_node_attach_output_bus(&dataSourceNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); + if (result != MA_SUCCESS) { + // Failed to attach node. + } + ``` + +The code above connects the data source node directly to the endpoint. Since the data source node +has only a single output bus, the index will always be 0. Likewise, the endpoint only has a single +input bus which means the input bus index will also always be 0. + +To detach a specific output bus, use `ma_node_detach_output_bus()`. To detach all output buses, use +`ma_node_detach_all_output_buses()`. If you want to just move the output bus from one attachment to +another, you do not need to detach first. You can just call `ma_node_attach_output_bus()` and it'll +deal with it for you. + +Less frequently you may want to create a specialized node. This will be a node where you implement +your own processing callback to apply a custom effect of some kind. This is similar to initalizing +one of the stock node types, only this time you need to specify a pointer to a vtable containing a +pointer to the processing function and the number of input and output buses. Example: + + ```c + static void my_custom_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) + { + // Do some processing of ppFramesIn (one stream of audio data per input bus) + const float* pFramesIn_0 = ppFramesIn[0]; // Input bus @ index 0. + const float* pFramesIn_1 = ppFramesIn[1]; // Input bus @ index 1. + float* pFramesOut_0 = ppFramesOut[0]; // Output bus @ index 0. + + // Do some processing. On input, `pFrameCountIn` will be the number of input frames in each + // buffer in `ppFramesIn` and `pFrameCountOut` will be the capacity of each of the buffers + // in `ppFramesOut`. On output, `pFrameCountIn` should be set to the number of input frames + // your node consumed and `pFrameCountOut` should be set the number of output frames that + // were produced. + // + // You should process as many frames as you can. If your effect consumes input frames at the + // same rate as output frames (always the case, unless you're doing resampling), you need + // only look at `ppFramesOut` and process that exact number of frames. If you're doing + // resampling, you'll need to be sure to set both `pFrameCountIn` and `pFrameCountOut` + // properly. + } + + static ma_node_vtable my_custom_node_vtable = + { + my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing. + NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames. + 2, // 2 input buses. + 1, // 1 output bus. + 0 // Default flags. + }; + + ... + + // Each bus needs to have a channel count specified. To do this you need to specify the channel + // counts in an array and then pass that into the node config. + ma_uint32 inputChannels[2]; // Equal in size to the number of input channels specified in the vtable. + ma_uint32 outputChannels[1]; // Equal in size to the number of output channels specicied in the vtable. + + inputChannels[0] = channelsIn; + inputChannels[1] = channelsIn; + outputChannels[0] = channelsOut; + + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.pInputChannels = inputChannels; + nodeConfig.pOutputChannels = outputChannels; + + ma_node_base node; + result = ma_node_init(&nodeGraph, &nodeConfig, NULL, &node); + if (result != MA_SUCCESS) { + // Failed to initialize node. + } + ``` + +When initializing a custom node, as in the code above, you'll normally just place your vtable in +static space. The number of input and output buses are specified as part of the vtable. If you need +a variable number of buses on a per-node bases, the vtable should have the relevant bus count set +to `MA_NODE_BUS_COUNT_UNKNOWN`. In this case, the bus count should be set in the node config: + + ```c + static ma_node_vtable my_custom_node_vtable = + { + my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing. + NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames. + MA_NODE_BUS_COUNT_UNKNOWN, // The number of input buses is determined on a per-node basis. + 1, // 1 output bus. + 0 // Default flags. + }; + + ... + + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.inputBusCount = myBusCount; // <-- Since the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN, the input bus count should be set here. + nodeConfig.pInputChannels = inputChannels; // <-- Make sure there are nodeConfig.inputBusCount elements in this array. + nodeConfig.pOutputChannels = outputChannels; // <-- The vtable specifies 1 output bus, so there must be 1 element in this array. + ``` + +In the above example it's important to never set the `inputBusCount` and `outputBusCount` members +to anything other than their defaults if the vtable specifies an explicit count. They can only be +set if the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN in the relevant bus count. + +Most often you'll want to create a structure to encapsulate your node with some extra data. You +need to make sure the `ma_node_base` object is your first member of the structure: + + ```c + typedef struct + { + ma_node_base base; // <-- Make sure this is always the first member. + float someCustomData; + } my_custom_node; + ``` + +By doing this, your object will be compatible with all `ma_node` APIs and you can attach it to the +graph just like any other node. + +In the custom processing callback (`my_custom_node_process_pcm_frames()` in the example above), the +number of channels for each bus is what was specified by the config when the node was initialized +with `ma_node_init()`. In addition, all attachments to each of the input buses will have been +pre-mixed by miniaudio. The config allows you to specify different channel counts for each +individual input and output bus. It's up to the effect to handle it appropriate, and if it can't, +return an error in it's initialization routine. + +Custom nodes can be assigned some flags to describe their behaviour. These are set via the vtable +and include the following: + + +-----------------------------------------+---------------------------------------------------+ + | Flag Name | Description | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_PASSTHROUGH | Useful for nodes that do not do any kind of audio | + | | processing, but are instead used for tracking | + | | time, handling events, etc. Also used by the | + | | internal endpoint node. It reads directly from | + | | the input bus to the output bus. Nodes with this | + | | flag must have exactly 1 input bus and 1 output | + | | bus, and both buses must have the same channel | + | | counts. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_CONTINUOUS_PROCESSING | Causes the processing callback to be called even | + | | when no data is available to be read from input | + | | attachments. This is useful for effects like | + | | echos where there will be a tail of audio data | + | | that still needs to be processed even when the | + | | original data sources have reached their ends. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_ALLOW_NULL_INPUT | Used in conjunction with | + | | `MA_NODE_FLAG_CONTINUOUS_PROCESSING`. When this | + | | is set, the `ppFramesIn` parameter of the | + | | processing callback will be set to NULL when | + | | there are no input frames are available. When | + | | this is unset, silence will be posted to the | + | | processing callback. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES | Used to tell miniaudio that input and output | + | | frames are processed at different rates. You | + | | should set this for any nodes that perform | + | | resampling. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_SILENT_OUTPUT | Used to tell miniaudio that a node produces only | + | | silent output. This is useful for nodes where you | + | | don't want the output to contribute to the final | + | | mix. An example might be if you want split your | + | | stream and have one branch be output to a file. | + | | When using this flag, you should avoid writing to | + | | the output buffer of the node's processing | + | | callback because miniaudio will ignore it anyway. | + +-----------------------------------------+---------------------------------------------------+ + + +If you need to make a copy of an audio stream for effect processing you can use a splitter node +called `ma_splitter_node`. This takes has 1 input bus and splits the stream into 2 output buses. +You can use it like this: + + ```c + ma_splitter_node_config splitterNodeConfig = ma_splitter_node_config_init(channelsIn, channelsOut); + + ma_splitter_node splitterNode; + result = ma_splitter_node_init(&nodeGraph, &splitterNodeConfig, NULL, &splitterNode); + if (result != MA_SUCCESS) { + // Failed to create node. + } + + // Attach your output buses to two different input buses (can be on two different nodes). + ma_node_attach_output_bus(&splitterNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); // Attach directly to the endpoint. + ma_node_attach_output_bus(&splitterNode, 1, &myEffectNode, 0); // Attach to input bus 0 of some effect node. + ``` + +The volume of an output bus can be configured on a per-bus basis: + + ```c + ma_node_set_output_bus_volume(&splitterNode, 0, 0.5f); + ma_node_set_output_bus_volume(&splitterNode, 1, 0.5f); + ``` + +In the code above we're using the splitter node from before and changing the volume of each of the +copied streams. + +You can start and stop a node with the following: + + ```c + ma_node_set_state(&splitterNode, ma_node_state_started); // The default state. + ma_node_set_state(&splitterNode, ma_node_state_stopped); + ``` + +By default the node is in a started state, but since it won't be connected to anything won't +actually be invoked by the node graph until it's connected. When you stop a node, data will not be +read from any of it's input connections. You can use this property to stop a group of sounds +atomically. + +You can configure the initial state of a node in it's config: + + ```c + nodeConfig.initialState = ma_node_state_stopped; + ``` + +Note that for the stock specialized nodes, all of their configs will have a `nodeConfig` member +which is the config to use with the base node. This is where the initial state can be configured +for specialized nodes: + + ```c + dataSourceNodeConfig.nodeConfig.initialState = ma_node_state_stopped; + ``` + +When using a specialized node like `ma_data_source_node` or `ma_splitter_node`, be sure to not +modify the `vtable` member of the `nodeConfig` object. + + +7.1. Timing +----------- +The node graph supports starting and stopping nodes at scheduled times. This is especially useful +for data source nodes where you want to get the node set up, but only start playback at a specific +time. There are two clocks: local and global. + +A local clock is per-node, whereas the global clock is per graph. Scheduling starts and stops can +only be done based on the global clock because the local clock will not be running while the node +is stopped. The global clocks advances whenever `ma_node_graph_read_pcm_frames()` is called. On the +other hand, the local clock only advances when the node's processing callback is fired, and is +advanced based on the output frame count. + +To retrieve the global time, use `ma_node_graph_get_time()`. The global time can be set with +`ma_node_graph_set_time()` which might be useful if you want to do seeking on a global timeline. +Getting and setting the local time is similar. Use `ma_node_get_time()` to retrieve the local time, +and `ma_node_set_time()` to set the local time. The global and local times will be advanced by the +audio thread, so care should be taken to avoid data races. Ideally you should avoid calling these +outside of the node processing callbacks which are always run on the audio thread. + +There is basic support for scheduling the starting and stopping of nodes. You can only schedule one +start and one stop at a time. This is mainly intended for putting nodes into a started or stopped +state in a frame-exact manner. Without this mechanism, starting and stopping of a node is limited +to the resolution of a call to `ma_node_graph_read_pcm_frames()` which would typically be in blocks +of several milliseconds. The following APIs can be used for scheduling node states: + + ```c + ma_node_set_state_time() + ma_node_get_state_time() + ``` + +The time is absolute and must be based on the global clock. An example is below: + + ```c + ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1); // Delay starting to 1 second. + ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5); // Delay stopping to 5 seconds. + ``` + +An example for changing the state using a relative time. + + ```c + ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1 + ma_node_graph_get_time(&myNodeGraph)); + ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5 + ma_node_graph_get_time(&myNodeGraph)); + ``` + +Note that due to the nature of multi-threading the times may not be 100% exact. If this is an +issue, consider scheduling state changes from within a processing callback. An idea might be to +have some kind of passthrough trigger node that is used specifically for tracking time and handling +events. + + + +7.2. Thread Safety and Locking +------------------------------ +When processing audio, it's ideal not to have any kind of locking in the audio thread. Since it's +expected that `ma_node_graph_read_pcm_frames()` would be run on the audio thread, it does so +without the use of any locks. This section discusses the implementation used by miniaudio and goes +over some of the compromises employed by miniaudio to achieve this goal. Note that the current +implementation may not be ideal - feedback and critiques are most welcome. + +The node graph API is not *entirely* lock-free. Only `ma_node_graph_read_pcm_frames()` is expected +to be lock-free. Attachment, detachment and uninitialization of nodes use locks to simplify the +implementation, but are crafted in a way such that such locking is not required when reading audio +data from the graph. Locking in these areas are achieved by means of spinlocks. + +The main complication with keeping `ma_node_graph_read_pcm_frames()` lock-free stems from the fact +that a node can be uninitialized, and it's memory potentially freed, while in the middle of being +processed on the audio thread. There are times when the audio thread will be referencing a node, +which means the uninitialization process of a node needs to make sure it delays returning until the +audio thread is finished so that control is not handed back to the caller thereby giving them a +chance to free the node's memory. + +When the audio thread is processing a node, it does so by reading from each of the output buses of +the node. In order for a node to process data for one of it's output buses, it needs to read from +each of it's input buses, and so on an so forth. It follows that once all output buses of a node +are detached, the node as a whole will be disconnected and no further processing will occur unless +it's output buses are reattached, which won't be happening when the node is being uninitialized. +By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can +simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By +doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output +nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean +up. + +With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as +it takes to process the output bus being detached. This will happen if it's called at just the +wrong moment where the audio thread has just iterated it and has just started processing. The +caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which +includes the cost of recursively processing it's inputs. This is the biggest compromise made with +the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes +earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching +higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass +detachments, detach starting from the lowest level nodes and work your way towards the final +endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not +running, detachment will be fast and detachment in any order will be the same. The reason nodes +need to wait for their input attachments to complete is due to the potential for desyncs between +data sources. If the node was to terminate processing mid way through processing it's inputs, +there's a chance that some of the underlying data sources will have been read, but then others not. +That will then result in a potential desynchronization when detaching and reattaching higher-level +nodes. A possible solution to this is to have an option when detaching to terminate processing +before processing all input attachments which should be fairly simple. + +Another compromise, albeit less significant, is locking when attaching and detaching nodes. This +locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present +for each input bus and output bus. When an output bus is connected to an input bus, both the output +bus and input bus is locked. This locking is specifically for attaching and detaching across +different threads and does not affect `ma_node_graph_read_pcm_frames()` in any way. The locking and +unlocking is mostly self-explanatory, but a slightly less intuitive aspect comes into it when +considering that iterating over attachments must not break as a result of attaching or detaching a +node while iteration is occuring. + +Attaching and detaching are both quite simple. When an output bus of a node is attached to an input +bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where +each item in the list is and output bus. We have some intentional (and convenient) restrictions on +what can done with the linked list in order to simplify the implementation. First of all, whenever +something needs to iterate over the list, it must do so in a forward direction. Backwards iteration +is not supported. Also, items can only be added to the start of the list. + +The linked list is a doubly-linked list where each item in the list (an output bus) holds a pointer +to the next item in the list, and another to the previous item. A pointer to the previous item is +only required for fast detachment of the node - it is never used in iteration. This is an +important property because it means from the perspective of iteration, attaching and detaching of +an item can be done with a single atomic assignment. This is exploited by both the attachment and +detachment process. When attaching the node, the first thing that is done is the setting of the +local "next" and "previous" pointers of the node. After that, the item is "attached" to the list +by simply performing an atomic exchange with the head pointer. After that, the node is "attached" +to the list from the perspective of iteration. Even though the "previous" pointer of the next item +hasn't yet been set, from the perspective of iteration it's been attached because iteration will +only be happening in a forward direction which means the "previous" pointer won't actually ever get +used. The same general process applies to detachment. See `ma_node_attach_output_bus()` and +`ma_node_detach_output_bus()` for the implementation of this mechanism. + + + +8. Decoding =========== -The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from devices and can be used independently. The following formats are -supported: +The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from +devices and can be used independently. The following formats are supported: +---------+------------------+----------+ | Format | Decoding Backend | Built-In | @@ -475,7 +2405,8 @@ supported: | Vorbis | stb_vorbis | No | +---------+------------------+----------+ -Vorbis is supported via stb_vorbis which can be enabled by including the header section before the implementation of miniaudio, like the following: +Vorbis is supported via stb_vorbis which can be enabled by including the header section before the +implementation of miniaudio, like the following: ```c #define STB_VORBIS_HEADER_ONLY @@ -491,8 +2422,9 @@ Vorbis is supported via stb_vorbis which can be enabled by including the header A copy of stb_vorbis is included in the "extras" folder in the miniaudio repository (https://github.com/mackron/miniaudio). -Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the built-in decoders by specifying one or more of the -following options before the miniaudio implementation: +Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the +built-in decoders by specifying one or more of the following options before the miniaudio +implementation: ```c #define MA_NO_WAV @@ -500,10 +2432,12 @@ following options before the miniaudio implementation: #define MA_NO_FLAC ``` -Disabling built-in decoding libraries is useful if you use these libraries independantly of the `ma_decoder` API. +Disabling built-in decoding libraries is useful if you use these libraries independantly of the +`ma_decoder` API. -A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with `ma_decoder_init_memory()`, or from data delivered via callbacks -with `ma_decoder_init()`. Here is an example for loading a decoder from a file: +A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with +`ma_decoder_init_memory()`, or from data delivered via callbacks with `ma_decoder_init()`. Here is +an example for loading a decoder from a file: ```c ma_decoder decoder; @@ -517,20 +2451,23 @@ with `ma_decoder_init()`. Here is an example for loading a decoder from a file: ma_decoder_uninit(&decoder); ``` -When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object (the `NULL` argument in the example above) which allows you -to configure the output format, channel count, sample rate and channel map: +When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object +(the `NULL` argument in the example above) which allows you to configure the output format, channel +count, sample rate and channel map: ```c ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, 48000); ``` -When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the same as that defined by the decoding backend. +When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the +same as that defined by the decoding backend. -Data is read from the decoder as PCM frames. This will return the number of PCM frames actually read. If the return value is less than the requested number of -PCM frames it means you've reached the end: +Data is read from the decoder as PCM frames. This will output the number of PCM frames actually +read. If this is less than the requested number of PCM frames it means you've reached the end. The +return value will be `MA_AT_END` if no samples have been read and the end has been reached. ```c - ma_uint64 framesRead = ma_decoder_read_pcm_frames(pDecoder, pFrames, framesToRead); + ma_result result = ma_decoder_read_pcm_frames(pDecoder, pFrames, framesToRead, &framesRead); if (framesRead < framesToRead) { // Reached the end. } @@ -551,8 +2488,10 @@ If you want to loop back to the start, you can simply seek back to the first PCM ma_decoder_seek_to_pcm_frame(pDecoder, 0); ``` -When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding backend. This can be unnecessarily inefficient if the type -is already known. In this case you can use `encodingFormat` variable in the device config to specify a specific encoding format you want to decode: +When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding +backend. This can be unnecessarily inefficient if the type is already known. In this case you can +use `encodingFormat` variable in the device config to specify a specific encoding format you want +to decode: ```c decoderConfig.encodingFormat = ma_encoding_format_wav; @@ -560,24 +2499,95 @@ is already known. In this case you can use `encodingFormat` variable in the devi See the `ma_encoding_format` enum for possible encoding formats. -The `ma_decoder_init_file()` API will try using the file extension to determine which decoding backend to prefer. +The `ma_decoder_init_file()` API will try using the file extension to determine which decoding +backend to prefer. + + +8.1. Custom Decoders +-------------------- +It's possible to implement a custom decoder and plug it into miniaudio. This is extremely useful +when you want to use the `ma_decoder` API, but need to support an encoding format that's not one of +the stock formats supported by miniaudio. This can be put to particularly good use when using the +`ma_engine` and/or `ma_resource_manager` APIs because they use `ma_decoder` internally. If, for +example, you wanted to support Opus, you can do so with a custom decoder (there if a reference +Opus decoder in the "extras" folder of the miniaudio repository which uses libopus + libopusfile). + +A custom decoder must implement a data source. A vtable called `ma_decoding_backend_vtable` needs +to be implemented which is then passed into the decoder config: + + ```c + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + ... + + decoderConfig = ma_decoder_config_init_default(); + decoderConfig.pCustomBackendUserData = NULL; + decoderConfig.ppCustomBackendVTables = pCustomBackendVTables; + decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + ``` + +The `ma_decoding_backend_vtable` vtable has the following functions: + + ``` + onInit + onInitFile + onInitFileW + onInitMemory + onUninit + ``` + +There are only two functions that must be implemented - `onInit` and `onUninit`. The other +functions can be implemented for a small optimization for loading from a file path or memory. If +these are not specified, miniaudio will deal with it for you via a generic implementation. + +When you initialize a custom data source (by implementing the `onInit` function in the vtable) you +will need to output a pointer to a `ma_data_source` which implements your custom decoder. See the +section about data sources for details on how to implemen this. Alternatively, see the +"custom_decoders" example in the miniaudio repository. + +The `onInit` function takes a pointer to some callbacks for the purpose of reading raw audio data +from some abitrary source. You'll use these functions to read from the raw data and perform the +decoding. When you call them, you will pass in the `pReadSeekTellUserData` pointer to the relevant +parameter. + +The `pConfig` parameter in `onInit` can be used to configure the backend if appropriate. It's only +used as a hint and can be ignored. However, if any of the properties are relevant to your decoder, +an optimal implementation will handle the relevant properties appropriately. + +If memory allocation is required, it should be done so via the specified allocation callbacks if +possible (the `pAllocationCallbacks` parameter). + +If an error occurs when initializing the decoder, you should leave `ppBackend` unset, or set to +NULL, and make sure everything is cleaned up appropriately and an appropriate result code returned. +When multiple custom backends are specified, miniaudio will cycle through the vtables in the order +they're listed in the array that's passed into the decoder config so it's important that your +initialization routine is clean. + +When a decoder is uninitialized, the `onUninit` callback will be fired which will give you an +opportunity to clean up and internal data. -5. Encoding +9. Encoding =========== -The `ma_encoding` API is used for writing audio files. The only supported output format is WAV which is achieved via dr_wav which is amalgamated into the -implementation section of miniaudio. This can be disabled by specifying the following option before the implementation of miniaudio: +The `ma_encoding` API is used for writing audio files. The only supported output format is WAV +which is achieved via dr_wav which is amalgamated into the implementation section of miniaudio. +This can be disabled by specifying the following option before the implementation of miniaudio: ```c #define MA_NO_WAV ``` -An encoder can be initialized to write to a file with `ma_encoder_init_file()` or from data delivered via callbacks with `ma_encoder_init()`. Below is an -example for initializing an encoder to output to a file. +An encoder can be initialized to write to a file with `ma_encoder_init_file()` or from data +delivered via callbacks with `ma_encoder_init()`. Below is an example for initializing an encoder +to output to a file. ```c - ma_encoder_config config = ma_encoder_config_init(ma_resource_format_wav, FORMAT, CHANNELS, SAMPLE_RATE); + ma_encoder_config config = ma_encoder_config_init(ma_encoding_format_wav, FORMAT, CHANNELS, SAMPLE_RATE); ma_encoder encoder; ma_result result = ma_encoder_init_file("my_file.wav", &config, &encoder); if (result != MA_SUCCESS) { @@ -589,17 +2599,20 @@ example for initializing an encoder to output to a file. ma_encoder_uninit(&encoder); ``` -When initializing an encoder you must specify a config which is initialized with `ma_encoder_config_init()`. Here you must specify the file type, the output -sample format, output channel count and output sample rate. The following file types are supported: +When initializing an encoder you must specify a config which is initialized with +`ma_encoder_config_init()`. Here you must specify the file type, the output sample format, output +channel count and output sample rate. The following file types are supported: +------------------------+-------------+ | Enum | Description | +------------------------+-------------+ - | ma_resource_format_wav | WAV | + | ma_encoding_format_wav | WAV | +------------------------+-------------+ -If the format, channel count or sample rate is not supported by the output file type an error will be returned. The encoder will not perform data conversion so -you will need to convert it before outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frames()`, like in the example below: +If the format, channel count or sample rate is not supported by the output file type an error will +be returned. The encoder will not perform data conversion so you will need to convert it before +outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frames()`, like in the +example below: ```c framesWritten = ma_encoder_write_pcm_frames(&encoder, pPCMFramesToWrite, framesToWrite); @@ -608,21 +2621,25 @@ you will need to convert it before outputting any audio data. To output audio da Encoders must be uninitialized with `ma_encoder_uninit()`. -6. Data Conversion -================== -A data conversion API is included with miniaudio which supports the majority of data conversion requirements. This supports conversion between sample formats, -channel counts (with channel mapping) and sample rates. + +10. Data Conversion +=================== +A data conversion API is included with miniaudio which supports the majority of data conversion +requirements. This supports conversion between sample formats, channel counts (with channel +mapping) and sample rates. -6.1. Sample Format Conversion ------------------------------ -Conversion between sample formats is achieved with the `ma_pcm_*_to_*()`, `ma_pcm_convert()` and `ma_convert_pcm_frames_format()` APIs. Use `ma_pcm_*_to_*()` -to convert between two specific formats. Use `ma_pcm_convert()` to convert based on a `ma_format` variable. Use `ma_convert_pcm_frames_format()` to convert -PCM frames where you want to specify the frame count and channel count as a variable instead of the total sample count. +10.1. Sample Format Conversion +------------------------------ +Conversion between sample formats is achieved with the `ma_pcm_*_to_*()`, `ma_pcm_convert()` and +`ma_convert_pcm_frames_format()` APIs. Use `ma_pcm_*_to_*()` to convert between two specific +formats. Use `ma_pcm_convert()` to convert based on a `ma_format` variable. Use +`ma_convert_pcm_frames_format()` to convert PCM frames where you want to specify the frame count +and channel count as a variable instead of the total sample count. -6.1.1. Dithering ----------------- +10.1.1. Dithering +----------------- Dithering can be set using the ditherMode parameter. The different dithering modes include the following, in order of efficiency: @@ -635,8 +2652,9 @@ The different dithering modes include the following, in order of efficiency: | Triangle | ma_dither_mode_triangle | +-----------+--------------------------+ -Note that even if the dither mode is set to something other than `ma_dither_mode_none`, it will be ignored for conversions where dithering is not needed. -Dithering is available for the following conversions: +Note that even if the dither mode is set to something other than `ma_dither_mode_none`, it will be +ignored for conversions where dithering is not needed. Dithering is available for the following +conversions: ``` s16 -> u8 @@ -648,14 +2666,16 @@ Dithering is available for the following conversions: f32 -> s16 ``` -Note that it is not an error to pass something other than ma_dither_mode_none for conversions where dither is not used. It will just be ignored. +Note that it is not an error to pass something other than ma_dither_mode_none for conversions where +dither is not used. It will just be ignored. -6.2. Channel Conversion ------------------------ -Channel conversion is used for channel rearrangement and conversion from one channel count to another. The `ma_channel_converter` API is used for channel -conversion. Below is an example of initializing a simple channel converter which converts from mono to stereo. +10.2. Channel Conversion +------------------------ +Channel conversion is used for channel rearrangement and conversion from one channel count to +another. The `ma_channel_converter` API is used for channel conversion. Below is an example of +initializing a simple channel converter which converts from mono to stereo. ```c ma_channel_converter_config config = ma_channel_converter_config_init( @@ -666,7 +2686,7 @@ conversion. Below is an example of initializing a simple channel converter which NULL, // Output channel map ma_channel_mix_mode_default); // The mixing algorithm to use when combining channels. - result = ma_channel_converter_init(&config, &converter); + result = ma_channel_converter_init(&config, NULL, &converter); if (result != MA_SUCCESS) { // Error. } @@ -681,34 +2701,43 @@ To perform the conversion simply call `ma_channel_converter_process_pcm_frames() } ``` -It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM frames. +It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM +frames. Input and output PCM frames are always interleaved. Deinterleaved layouts are not supported. -6.2.1. Channel Mapping ----------------------- -In addition to converting from one channel count to another, like the example above, the channel converter can also be used to rearrange channels. When -initializing the channel converter, you can optionally pass in channel maps for both the input and output frames. If the channel counts are the same, and each -channel map contains the same channel positions with the exception that they're in a different order, a simple shuffling of the channels will be performed. If, -however, there is not a 1:1 mapping of channel positions, or the channel counts differ, the input channels will be mixed based on a mixing mode which is -specified when initializing the `ma_channel_converter_config` object. +10.2.1. Channel Mapping +----------------------- +In addition to converting from one channel count to another, like the example above, the channel +converter can also be used to rearrange channels. When initializing the channel converter, you can +optionally pass in channel maps for both the input and output frames. If the channel counts are the +same, and each channel map contains the same channel positions with the exception that they're in +a different order, a simple shuffling of the channels will be performed. If, however, there is not +a 1:1 mapping of channel positions, or the channel counts differ, the input channels will be mixed +based on a mixing mode which is specified when initializing the `ma_channel_converter_config` +object. -When converting from mono to multi-channel, the mono channel is simply copied to each output channel. When going the other way around, the audio of each output -channel is simply averaged and copied to the mono channel. +When converting from mono to multi-channel, the mono channel is simply copied to each output +channel. When going the other way around, the audio of each output channel is simply averaged and +copied to the mono channel. -In more complicated cases blending is used. The `ma_channel_mix_mode_simple` mode will drop excess channels and silence extra channels. For example, converting -from 4 to 2 channels, the 3rd and 4th channels will be dropped, whereas converting from 2 to 4 channels will put silence into the 3rd and 4th channels. +In more complicated cases blending is used. The `ma_channel_mix_mode_simple` mode will drop excess +channels and silence extra channels. For example, converting from 4 to 2 channels, the 3rd and 4th +channels will be dropped, whereas converting from 2 to 4 channels will put silence into the 3rd and +4th channels. -The `ma_channel_mix_mode_rectangle` mode uses spacial locality based on a rectangle to compute a simple distribution between input and output. Imagine sitting -in the middle of a room, with speakers on the walls representing channel positions. The MA_CHANNEL_FRONT_LEFT position can be thought of as being in the corner -of the front and left walls. +The `ma_channel_mix_mode_rectangle` mode uses spacial locality based on a rectangle to compute a +simple distribution between input and output. Imagine sitting in the middle of a room, with +speakers on the walls representing channel positions. The `MA_CHANNEL_FRONT_LEFT` position can be +thought of as being in the corner of the front and left walls. -Finally, the `ma_channel_mix_mode_custom_weights` mode can be used to use custom user-defined weights. Custom weights can be passed in as the last parameter of +Finally, the `ma_channel_mix_mode_custom_weights` mode can be used to use custom user-defined +weights. Custom weights can be passed in as the last parameter of `ma_channel_converter_config_init()`. -Predefined channel maps can be retrieved with `ma_get_standard_channel_map()`. This takes a `ma_standard_channel_map` enum as it's first parameter, which can -be one of the following: +Predefined channel maps can be retrieved with `ma_channel_map_init_standard()`. This takes a +`ma_standard_channel_map` enum as it's first parameter, which can be one of the following: +-----------------------------------+-----------------------------------------------------------+ | Name | Description | @@ -780,9 +2809,10 @@ Below are the channel maps used by default in miniaudio (`ma_standard_channel_ma -6.3. Resampling ---------------- -Resampling is achieved with the `ma_resampler` object. To create a resampler object, do something like the following: +10.3. Resampling +---------------- +Resampling is achieved with the `ma_resampler` object. To create a resampler object, do something +like the following: ```c ma_resampler_config config = ma_resampler_config_init( @@ -819,104 +2849,128 @@ The following example shows how data can be processed // number of output frames written. ``` -To initialize the resampler you first need to set up a config (`ma_resampler_config`) with `ma_resampler_config_init()`. You need to specify the sample format -you want to use, the number of channels, the input and output sample rate, and the algorithm. +To initialize the resampler you first need to set up a config (`ma_resampler_config`) with +`ma_resampler_config_init()`. You need to specify the sample format you want to use, the number of +channels, the input and output sample rate, and the algorithm. -The sample format can be either `ma_format_s16` or `ma_format_f32`. If you need a different format you will need to perform pre- and post-conversions yourself -where necessary. Note that the format is the same for both input and output. The format cannot be changed after initialization. +The sample format can be either `ma_format_s16` or `ma_format_f32`. If you need a different format +you will need to perform pre- and post-conversions yourself where necessary. Note that the format +is the same for both input and output. The format cannot be changed after initialization. -The resampler supports multiple channels and is always interleaved (both input and output). The channel count cannot be changed after initialization. +The resampler supports multiple channels and is always interleaved (both input and output). The +channel count cannot be changed after initialization. -The sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the -only configuration property that can be changed after initialization. +The sample rates can be anything other than zero, and are always specified in hertz. They should be +set to something like 44100, etc. The sample rate is the only configuration property that can be +changed after initialization. -The miniaudio resampler supports multiple algorithms: +The miniaudio resampler has built-in support for the following algorithms: +-----------+------------------------------+ | Algorithm | Enum Token | +-----------+------------------------------+ | Linear | ma_resample_algorithm_linear | - | Speex | ma_resample_algorithm_speex | + | Custom | ma_resample_algorithm_custom | +-----------+------------------------------+ -Because Speex is not public domain it is strictly opt-in and the code is stored in separate files. if you opt-in to the Speex backend you will need to consider -it's license, the text of which can be found in it's source files in "extras/speex_resampler". Details on how to opt-in to the Speex resampler is explained in -the Speex Resampler section below. - The algorithm cannot be changed after initialization. -Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process -frames, use `ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number of -input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer and the -number of input frames that were consumed in the process. You can pass in NULL for the input buffer in which case it will be treated as an infinitely large -buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. +Processing always happens on a per PCM frame basis and always assumes interleaved input and output. +De-interleaved processing is not supported. To process frames, use +`ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you +can fit in the output buffer and the number of input frames contained in the input buffer. On +output these variables contain the number of output frames that were written to the output buffer +and the number of input frames that were consumed in the process. You can pass in NULL for the +input buffer in which case it will be treated as an infinitely large buffer of zeros. The output +buffer can also be NULL, in which case the processing will be treated as seek. -The sample rate can be changed dynamically on the fly. You can change this with explicit sample rates with `ma_resampler_set_rate()` and also with a decimal -ratio with `ma_resampler_set_rate_ratio()`. The ratio is in/out. +The sample rate can be changed dynamically on the fly. You can change this with explicit sample +rates with `ma_resampler_set_rate()` and also with a decimal ratio with +`ma_resampler_set_rate_ratio()`. The ratio is in/out. -Sometimes it's useful to know exactly how many input frames will be required to output a specific number of frames. You can calculate this with -`ma_resampler_get_required_input_frame_count()`. Likewise, it's sometimes useful to know exactly how many frames would be output given a certain number of -input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. +Sometimes it's useful to know exactly how many input frames will be required to output a specific +number of frames. You can calculate this with `ma_resampler_get_required_input_frame_count()`. +Likewise, it's sometimes useful to know exactly how many frames would be output given a certain +number of input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. -Due to the nature of how resampling works, the resampler introduces some latency. This can be retrieved in terms of both the input rate and the output rate -with `ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. +Due to the nature of how resampling works, the resampler introduces some latency. This can be +retrieved in terms of both the input rate and the output rate with +`ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. -6.3.1. Resampling Algorithms ----------------------------- -The choice of resampling algorithm depends on your situation and requirements. The linear resampler is the most efficient and has the least amount of latency, -but at the expense of poorer quality. The Speex resampler is higher quality, but slower with more latency. It also performs several heap allocations internally -for memory management. +10.3.1. Resampling Algorithms +----------------------------- +The choice of resampling algorithm depends on your situation and requirements. -6.3.1.1. Linear Resampling --------------------------- -The linear resampler is the fastest, but comes at the expense of poorer quality. There is, however, some control over the quality of the linear resampler which -may make it a suitable option depending on your requirements. +10.3.1.1. Linear Resampling +--------------------------- +The linear resampler is the fastest, but comes at the expense of poorer quality. There is, however, +some control over the quality of the linear resampler which may make it a suitable option depending +on your requirements. -The linear resampler performs low-pass filtering before or after downsampling or upsampling, depending on the sample rates you're converting between. When -decreasing the sample rate, the low-pass filter will be applied before downsampling. When increasing the rate it will be performed after upsampling. By default -a fourth order low-pass filter will be applied. This can be configured via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. +The linear resampler performs low-pass filtering before or after downsampling or upsampling, +depending on the sample rates you're converting between. When decreasing the sample rate, the +low-pass filter will be applied before downsampling. When increasing the rate it will be performed +after upsampling. By default a fourth order low-pass filter will be applied. This can be configured +via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. -The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of the input and output sample rates (Nyquist Frequency). This -can be controlled with the `lpfNyquistFactor` config variable. This defaults to 1, and should be in the range of 0..1, although a value of 0 does not make -sense and should be avoided. A value of 1 will use the Nyquist Frequency as the cutoff. A value of 0.5 will use half the Nyquist Frequency as the cutoff, etc. -Values less than 1 will result in more washed out sound due to more of the higher frequencies being removed. This config variable has no impact on performance -and is a purely perceptual configuration. +The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of +the input and output sample rates (Nyquist Frequency). -The API for the linear resampler is the same as the main resampler API, only it's called `ma_linear_resampler`. +The API for the linear resampler is the same as the main resampler API, only it's called +`ma_linear_resampler`. -6.3.1.2. Speex Resampling +10.3.2. Custom Resamplers ------------------------- -The Speex resampler is made up of third party code which is released under the BSD license. Because it is licensed differently to miniaudio, which is public -domain, it is strictly opt-in and all of it's code is stored in separate files. If you opt-in to the Speex resampler you must consider the license text in it's -source files. To opt-in, you must first `#include` the following file before the implementation of miniaudio.h: +You can implement a custom resampler by using the `ma_resample_algorithm_custom` resampling +algorithm and setting a vtable in the resampler config: ```c - #include "extras/speex_resampler/ma_speex_resampler.h" + ma_resampler_config config = ma_resampler_config_init(..., ma_resample_algorithm_custom); + config.pBackendVTable = &g_customResamplerVTable; ``` -Both the header and implementation is contained within the same file. The implementation can be included in your program like so: +Custom resamplers are useful if the stock algorithms are not appropriate for your use case. You +need to implement the required functions in `ma_resampling_backend_vtable`. Note that not all +functions in the vtable need to be implemented, but if it's possible to implement, they should be. - ```c - #define MINIAUDIO_SPEEX_RESAMPLER_IMPLEMENTATION - #include "extras/speex_resampler/ma_speex_resampler.h" - ``` +You can use the `ma_linear_resampler` object for an example on how to implement the vtable. The +`onGetHeapSize` callback is used to calculate the size of any internal heap allocation the custom +resampler will need to make given the supplied config. When you initialize the resampler via the +`onInit` callback, you'll be given a pointer to a heap allocation which is where you should store +the heap allocated data. You should not free this data in `onUninit` because miniaudio will manage +it for you. -Note that even if you opt-in to the Speex backend, miniaudio won't use it unless you explicitly ask for it in the respective config of the object you are -initializing. If you try to use the Speex resampler without opting in, initialization of the `ma_resampler` object will fail with `MA_NO_BACKEND`. +The `onProcess` callback is where the actual resampling takes place. On input, `pFrameCountIn` +points to a variable containing the number of frames in the `pFramesIn` buffer and +`pFrameCountOut` points to a variable containing the capacity in frames of the `pFramesOut` buffer. +On output, `pFrameCountIn` should be set to the number of input frames that were fully consumed, +whereas `pFrameCountOut` should be set to the number of frames that were written to `pFramesOut`. -The only configuration option to consider with the Speex resampler is the `speex.quality` config variable. This is a value between 0 and 10, with 0 being -the fastest with the poorest quality and 10 being the slowest with the highest quality. The default value is 3. +The `onSetRate` callback is optional and is used for dynamically changing the sample rate. If +dynamic rate changes are not supported, you can set this callback to NULL. + +The `onGetInputLatency` and `onGetOutputLatency` functions are used for retrieving the latency in +input and output rates respectively. These can be NULL in which case latency calculations will be +assumed to be NULL. + +The `onGetRequiredInputFrameCount` callback is used to give miniaudio a hint as to how many input +frames are required to be available to produce the given number of output frames. Likewise, the +`onGetExpectedOutputFrameCount` callback is used to determine how many output frames will be +produced given the specified number of input frames. miniaudio will use these as a hint, but they +are optional and can be set to NULL if you're unable to implement them. -6.4. General Data Conversion ----------------------------- -The `ma_data_converter` API can be used to wrap sample format conversion, channel conversion and resampling into one operation. This is what miniaudio uses -internally to convert between the format requested when the device was initialized and the format of the backend's native device. The API for general data -conversion is very similar to the resampling API. Create a `ma_data_converter` object like this: +10.4. General Data Conversion +----------------------------- +The `ma_data_converter` API can be used to wrap sample format conversion, channel conversion and +resampling into one operation. This is what miniaudio uses internally to convert between the format +requested when the device was initialized and the format of the backend's native device. The API +for general data conversion is very similar to the resampling API. Create a `ma_data_converter` +object like this: ```c ma_data_converter_config config = ma_data_converter_config_init( @@ -929,14 +2983,15 @@ conversion is very similar to the resampling API. Create a `ma_data_converter` o ); ma_data_converter converter; - ma_result result = ma_data_converter_init(&config, &converter); + ma_result result = ma_data_converter_init(&config, NULL, &converter); if (result != MA_SUCCESS) { // An error occurred... } ``` -In the example above we use `ma_data_converter_config_init()` to initialize the config, however there's many more properties that can be configured, such as -channel maps and resampling quality. Something like the following may be more suitable depending on your requirements: +In the example above we use `ma_data_converter_config_init()` to initialize the config, however +there's many more properties that can be configured, such as channel maps and resampling quality. +Something like the following may be more suitable depending on your requirements: ```c ma_data_converter_config config = ma_data_converter_config_init_default(); @@ -946,14 +3001,14 @@ channel maps and resampling quality. Something like the following may be more su config.channelsOut = outputChannels; config.sampleRateIn = inputSampleRate; config.sampleRateOut = outputSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_flac, config.channelCountIn, config.channelMapIn); + ma_channel_map_init_standard(ma_standard_channel_map_flac, config.channelMapIn, sizeof(config.channelMapIn)/sizeof(config.channelMapIn[0]), config.channelCountIn); config.resampling.linear.lpfOrder = MA_MAX_FILTER_ORDER; ``` Do the following to uninitialize the data converter: ```c - ma_data_converter_uninit(&converter); + ma_data_converter_uninit(&converter, NULL); ``` The following example shows how data can be processed @@ -970,33 +3025,42 @@ The following example shows how data can be processed // of output frames written. ``` -The data converter supports multiple channels and is always interleaved (both input and output). The channel count cannot be changed after initialization. +The data converter supports multiple channels and is always interleaved (both input and output). +The channel count cannot be changed after initialization. -Sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the only -configuration property that can be changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of `ma_data_converter_config` is -set to `MA_TRUE`. To change the sample rate, use `ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. The -resampling algorithm cannot be changed after initialization. +Sample rates can be anything other than zero, and are always specified in hertz. They should be set +to something like 44100, etc. The sample rate is the only configuration property that can be +changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of +`ma_data_converter_config` is set to `MA_TRUE`. To change the sample rate, use +`ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. +The resampling algorithm cannot be changed after initialization. -Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process -frames, use `ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number -of input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer and the -number of input frames that were consumed in the process. You can pass in NULL for the input buffer in which case it will be treated as an infinitely large -buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. +Processing always happens on a per PCM frame basis and always assumes interleaved input and output. +De-interleaved processing is not supported. To process frames, use +`ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames +you can fit in the output buffer and the number of input frames contained in the input buffer. On +output these variables contain the number of output frames that were written to the output buffer +and the number of input frames that were consumed in the process. You can pass in NULL for the +input buffer in which case it will be treated as an infinitely large +buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated +as seek. -Sometimes it's useful to know exactly how many input frames will be required to output a specific number of frames. You can calculate this with -`ma_data_converter_get_required_input_frame_count()`. Likewise, it's sometimes useful to know exactly how many frames would be output given a certain number of -input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. +Sometimes it's useful to know exactly how many input frames will be required to output a specific +number of frames. You can calculate this with `ma_data_converter_get_required_input_frame_count()`. +Likewise, it's sometimes useful to know exactly how many frames would be output given a certain +number of input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. -Due to the nature of how resampling works, the data converter introduces some latency if resampling is required. This can be retrieved in terms of both the -input rate and the output rate with `ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. +Due to the nature of how resampling works, the data converter introduces some latency if resampling +is required. This can be retrieved in terms of both the input rate and the output rate with +`ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. -7. Filtering -============ +11. Filtering +============= -7.1. Biquad Filtering ---------------------- +11.1. Biquad Filtering +---------------------- Biquad filtering is achieved with the `ma_biquad` API. Example: ```c @@ -1011,28 +3075,33 @@ Biquad filtering is achieved with the `ma_biquad` API. Example: ma_biquad_process_pcm_frames(&biquad, pFramesOut, pFramesIn, frameCount); ``` -Biquad filtering is implemented using transposed direct form 2. The numerator coefficients are b0, b1 and b2, and the denominator coefficients are a0, a1 and -a2. The a0 coefficient is required and coefficients must not be pre-normalized. +Biquad filtering is implemented using transposed direct form 2. The numerator coefficients are b0, +b1 and b2, and the denominator coefficients are a0, a1 and a2. The a0 coefficient is required and +coefficients must not be pre-normalized. -Supported formats are `ma_format_s16` and `ma_format_f32`. If you need to use a different format you need to convert it yourself beforehand. When using -`ma_format_s16` the biquad filter will use fixed point arithmetic. When using `ma_format_f32`, floating point arithmetic will be used. +Supported formats are `ma_format_s16` and `ma_format_f32`. If you need to use a different format +you need to convert it yourself beforehand. When using `ma_format_s16` the biquad filter will use +fixed point arithmetic. When using `ma_format_f32`, floating point arithmetic will be used. Input and output frames are always interleaved. -Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so: +Filtering can be applied in-place by passing in the same pointer for both the input and output +buffers, like so: ```c ma_biquad_process_pcm_frames(&biquad, pMyData, pMyData, frameCount); ``` -If you need to change the values of the coefficients, but maintain the values in the registers you can do so with `ma_biquad_reinit()`. This is useful if you -need to change the properties of the filter while keeping the values of registers valid to avoid glitching. Do not use `ma_biquad_init()` for this as it will -do a full initialization which involves clearing the registers to 0. Note that changing the format or channel count after initialization is invalid and will -result in an error. +If you need to change the values of the coefficients, but maintain the values in the registers you +can do so with `ma_biquad_reinit()`. This is useful if you need to change the properties of the +filter while keeping the values of registers valid to avoid glitching. Do not use +`ma_biquad_init()` for this as it will do a full initialization which involves clearing the +registers to 0. Note that changing the format or channel count after initialization is invalid and +will result in an error. -7.2. Low-Pass Filtering ------------------------ +11.2. Low-Pass Filtering +------------------------ Low-pass filtering is achieved with the following APIs: +---------+------------------------------------------+ @@ -1057,16 +3126,18 @@ Low-pass filter example: ma_lpf_process_pcm_frames(&lpf, pFramesOut, pFramesIn, frameCount); ``` -Supported formats are `ma_format_s16` and` ma_format_f32`. If you need to use a different format you need to convert it yourself beforehand. Input and output -frames are always interleaved. +Supported formats are `ma_format_s16` and` ma_format_f32`. If you need to use a different format +you need to convert it yourself beforehand. Input and output frames are always interleaved. -Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so: +Filtering can be applied in-place by passing in the same pointer for both the input and output +buffers, like so: ```c ma_lpf_process_pcm_frames(&lpf, pMyData, pMyData, frameCount); ``` -The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, you can chain first and second order filters together. +The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, +you can chain first and second order filters together. ```c for (iFilter = 0; iFilter < filterCount; iFilter += 1) { @@ -1074,19 +3145,22 @@ The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. } ``` -If you need to change the configuration of the filter, but need to maintain the state of internal registers you can do so with `ma_lpf_reinit()`. This may be -useful if you need to change the sample rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the format or channel -count after initialization is invalid and will result in an error. +If you need to change the configuration of the filter, but need to maintain the state of internal +registers you can do so with `ma_lpf_reinit()`. This may be useful if you need to change the sample +rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the +format or channel count after initialization is invalid and will result in an error. -The `ma_lpf` object supports a configurable order, but if you only need a first order filter you may want to consider using `ma_lpf1`. Likewise, if you only -need a second order filter you can use `ma_lpf2`. The advantage of this is that they're lighter weight and a bit more efficient. +The `ma_lpf` object supports a configurable order, but if you only need a first order filter you +may want to consider using `ma_lpf1`. Likewise, if you only need a second order filter you can use +`ma_lpf2`. The advantage of this is that they're lighter weight and a bit more efficient. -If an even filter order is specified, a series of second order filters will be processed in a chain. If an odd filter order is specified, a first order filter -will be applied, followed by a series of second order filters in a chain. +If an even filter order is specified, a series of second order filters will be processed in a +chain. If an odd filter order is specified, a first order filter will be applied, followed by a +series of second order filters in a chain. -7.3. High-Pass Filtering ------------------------- +11.3. High-Pass Filtering +------------------------- High-pass filtering is achieved with the following APIs: +---------+-------------------------------------------+ @@ -1097,12 +3171,12 @@ High-pass filtering is achieved with the following APIs: | ma_hpf | High order high-pass filter (Butterworth) | +---------+-------------------------------------------+ -High-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_hpf1`, `ma_hpf2` and `ma_hpf`. See example code for low-pass filters -for example usage. +High-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_hpf1`, +`ma_hpf2` and `ma_hpf`. See example code for low-pass filters for example usage. -7.4. Band-Pass Filtering ------------------------- +11.4. Band-Pass Filtering +------------------------- Band-pass filtering is achieved with the following APIs: +---------+-------------------------------+ @@ -1112,13 +3186,14 @@ Band-pass filtering is achieved with the following APIs: | ma_bpf | High order band-pass filter | +---------+-------------------------------+ -Band-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_bpf2` and `ma_hpf`. See example code for low-pass filters for example -usage. Note that the order for band-pass filters must be an even number which means there is no first order band-pass filter, unlike low-pass and high-pass -filters. +Band-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_bpf2` and +`ma_hpf`. See example code for low-pass filters for example usage. Note that the order for +band-pass filters must be an even number which means there is no first order band-pass filter, +unlike low-pass and high-pass filters. -7.5. Notch Filtering --------------------- +11.5. Notch Filtering +--------------------- Notch filtering is achieved with the following APIs: +-----------+------------------------------------------+ @@ -1128,7 +3203,7 @@ Notch filtering is achieved with the following APIs: +-----------+------------------------------------------+ -7.6. Peaking EQ Filtering +11.6. Peaking EQ Filtering ------------------------- Peaking filtering is achieved with the following APIs: @@ -1139,8 +3214,8 @@ Peaking filtering is achieved with the following APIs: +----------+------------------------------------------+ -7.7. Low Shelf Filtering ------------------------- +11.7. Low Shelf Filtering +------------------------- Low shelf filtering is achieved with the following APIs: +-------------+------------------------------------------+ @@ -1149,11 +3224,12 @@ Low shelf filtering is achieved with the following APIs: | ma_loshelf2 | Second order low shelf filter | +-------------+------------------------------------------+ -Where a high-pass filter is used to eliminate lower frequencies, a low shelf filter can be used to just turn them down rather than eliminate them entirely. +Where a high-pass filter is used to eliminate lower frequencies, a low shelf filter can be used to +just turn them down rather than eliminate them entirely. -7.8. High Shelf Filtering -------------------------- +11.8. High Shelf Filtering +-------------------------- High shelf filtering is achieved with the following APIs: +-------------+------------------------------------------+ @@ -1162,18 +3238,20 @@ High shelf filtering is achieved with the following APIs: | ma_hishelf2 | Second order high shelf filter | +-------------+------------------------------------------+ -The high shelf filter has the same API as the low shelf filter, only you would use `ma_hishelf` instead of `ma_loshelf`. Where a low shelf filter is used to -adjust the volume of low frequencies, the high shelf filter does the same thing for high frequencies. +The high shelf filter has the same API as the low shelf filter, only you would use `ma_hishelf` +instead of `ma_loshelf`. Where a low shelf filter is used to adjust the volume of low frequencies, +the high shelf filter does the same thing for high frequencies. -8. Waveform and Noise Generation -================================ +12. Waveform and Noise Generation +================================= -8.1. Waveforms --------------- -miniaudio supports generation of sine, square, triangle and sawtooth waveforms. This is achieved with the `ma_waveform` API. Example: +12.1. Waveforms +--------------- +miniaudio supports generation of sine, square, triangle and sawtooth waveforms. This is achieved +with the `ma_waveform` API. Example: ```c ma_waveform_config config = ma_waveform_config_init( @@ -1195,11 +3273,12 @@ miniaudio supports generation of sine, square, triangle and sawtooth waveforms. ma_waveform_read_pcm_frames(&waveform, pOutput, frameCount); ``` -The amplitude, frequency, type, and sample rate can be changed dynamically with `ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, -`ma_waveform_set_type()`, and `ma_waveform_set_sample_rate()` respectively. +The amplitude, frequency, type, and sample rate can be changed dynamically with +`ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, `ma_waveform_set_type()`, and +`ma_waveform_set_sample_rate()` respectively. -You can invert the waveform by setting the amplitude to a negative value. You can use this to control whether or not a sawtooth has a positive or negative -ramp, for example. +You can invert the waveform by setting the amplitude to a negative value. You can use this to +control whether or not a sawtooth has a positive or negative ramp, for example. Below are the supported waveform types: @@ -1214,8 +3293,8 @@ Below are the supported waveform types: -8.2. Noise ----------- +12.2. Noise +----------- miniaudio supports generation of white, pink and Brownian noise via the `ma_noise` API. Example: ```c @@ -1237,13 +3316,16 @@ miniaudio supports generation of white, pink and Brownian noise via the `ma_nois ma_noise_read_pcm_frames(&noise, pOutput, frameCount); ``` -The noise API uses simple LCG random number generation. It supports a custom seed which is useful for things like automated testing requiring reproducibility. -Setting the seed to zero will default to `MA_DEFAULT_LCG_SEED`. +The noise API uses simple LCG random number generation. It supports a custom seed which is useful +for things like automated testing requiring reproducibility. Setting the seed to zero will default +to `MA_DEFAULT_LCG_SEED`. -The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, `ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. +The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, +`ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. -By default, the noise API will use different values for different channels. So, for example, the left side in a stereo stream will be different to the right -side. To instead have each channel use the same random value, set the `duplicateChannels` member of the noise config to true, like so: +By default, the noise API will use different values for different channels. So, for example, the +left side in a stereo stream will be different to the right side. To instead have each channel use +the same random value, set the `duplicateChannels` member of the noise config to true, like so: ```c config.duplicateChannels = MA_TRUE; @@ -1261,10 +3343,11 @@ Below are the supported noise types. -9. Audio Buffers -================ -miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can read from memory that's managed by the application, but -can also handle the memory management for you internally. Memory management is flexible and should support most use cases. +13. Audio Buffers +================= +miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can +read from memory that's managed by the application, but can also handle the memory management for +you internally. Memory management is flexible and should support most use cases. Audio buffers are initialised using the standard configuration system used everywhere in miniaudio: @@ -1287,11 +3370,14 @@ Audio buffers are initialised using the standard configuration system used every ma_audio_buffer_uninit(&buffer); ``` -In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an application can do self-managed memory allocation. If you -would rather make a copy of the data, use `ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. +In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an +application can do self-managed memory allocation. If you would rather make a copy of the data, use +`ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. -Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the raw audio data in a contiguous block of memory. That is, -the raw audio data will be located immediately after the `ma_audio_buffer` structure. To do this, use `ma_audio_buffer_alloc_and_init()`: +Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the +raw audio data in a contiguous block of memory. That is, the raw audio data will be located +immediately after the `ma_audio_buffer` structure. To do this, use +`ma_audio_buffer_alloc_and_init()`: ```c ma_audio_buffer_config config = ma_audio_buffer_config_init( @@ -1312,13 +3398,18 @@ the raw audio data will be located immediately after the `ma_audio_buffer` struc ma_audio_buffer_uninit_and_free(&buffer); ``` -If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it with `ma_audio_buffer_uninit_and_free()`. In the example above, -the memory pointed to by `pExistingData` will be copied into the buffer, which is contrary to the behavior of `ma_audio_buffer_init()`. +If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it +with `ma_audio_buffer_uninit_and_free()`. In the example above, the memory pointed to by +`pExistingData` will be copied into the buffer, which is contrary to the behavior of +`ma_audio_buffer_init()`. -An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the cursor moves forward. The last parameter (`loop`) can be -used to determine if the buffer should loop. The return value is the number of frames actually read. If this is less than the number of frames requested it -means the end has been reached. This should never happen if the `loop` parameter is set to true. If you want to manually loop back to the start, you can do so -with with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an audio buffer. +An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the +cursor moves forward. The last parameter (`loop`) can be used to determine if the buffer should +loop. The return value is the number of frames actually read. If this is less than the number of +frames requested it means the end has been reached. This should never happen if the `loop` +parameter is set to true. If you want to manually loop back to the start, you can do so with with +`ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an +audio buffer. ```c ma_uint64 framesRead = ma_audio_buffer_read_pcm_frames(pAudioBuffer, pFramesOut, desiredFrameCount, isLooping); @@ -1327,8 +3418,8 @@ with with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an exam } ``` -Sometimes you may want to avoid the cost of data movement between the internal buffer and the output buffer. Instead you can use memory mapping to retrieve a -pointer to a segment of data: +Sometimes you may want to avoid the cost of data movement between the internal buffer and the +output buffer. Instead you can use memory mapping to retrieve a pointer to a segment of data: ```c void* pMappedFrames; @@ -1344,23 +3435,30 @@ pointer to a segment of data: } ``` -When you use memory mapping, the read cursor is increment by the frame count passed in to `ma_audio_buffer_unmap()`. If you decide not to process every frame -you can pass in a value smaller than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is that it does not handle looping -for you. You can determine if the buffer is at the end for the purpose of looping with `ma_audio_buffer_at_end()` or by inspecting the return value of -`ma_audio_buffer_unmap()` and checking if it equals `MA_AT_END`. You should not treat `MA_AT_END` as an error when returned by `ma_audio_buffer_unmap()`. +When you use memory mapping, the read cursor is increment by the frame count passed in to +`ma_audio_buffer_unmap()`. If you decide not to process every frame you can pass in a value smaller +than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is +that it does not handle looping for you. You can determine if the buffer is at the end for the +purpose of looping with `ma_audio_buffer_at_end()` or by inspecting the return value of +`ma_audio_buffer_unmap()` and checking if it equals `MA_AT_END`. You should not treat `MA_AT_END` +as an error when returned by `ma_audio_buffer_unmap()`. -10. Ring Buffers +14. Ring Buffers ================ -miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates -on bytes, whereas the `ma_pcm_rb` operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around `ma_rb`. +miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via +the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates on bytes, whereas the `ma_pcm_rb` +operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around +`ma_rb`. -Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved streams. The caller can also allocate their own backing memory for -the ring buffer to use internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for you. +Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved +streams. The caller can also allocate their own backing memory for the ring buffer to use +internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for +you. -The examples below use the PCM frame variant of the ring buffer since that's most likely the one you will want to use. To initialize a ring buffer, do -something like the following: +The examples below use the PCM frame variant of the ring buffer since that's most likely the one +you will want to use. To initialize a ring buffer, do something like the following: ```c ma_pcm_rb rb; @@ -1370,39 +3468,53 @@ something like the following: } ``` -The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because it's the PCM varient of the ring buffer API. For the regular -ring buffer that operates on bytes you would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes instead of frames. The -fourth parameter is an optional pre-allocated buffer and the fifth parameter is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation -routines. Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. +The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because +it's the PCM varient of the ring buffer API. For the regular ring buffer that operates on bytes you +would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes +instead of frames. The fourth parameter is an optional pre-allocated buffer and the fifth parameter +is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation routines. +Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. -Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is offset from each other based on the stride. To manage your -sub-buffers you can use `ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and `ma_pcm_rb_get_subbuffer_ptr()`. +Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is +offset from each other based on the stride. To manage your sub-buffers you can use +`ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and +`ma_pcm_rb_get_subbuffer_ptr()`. -Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section of the ring buffer. You specify the number of frames you -need, and on output it will set to what was actually acquired. If the read or write pointer is positioned such that the number of frames requested will require -a loop, it will be clamped to the end of the buffer. Therefore, the number of frames you're given may be less than the number you requested. +Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section +of the ring buffer. You specify the number of frames you need, and on output it will set to what +was actually acquired. If the read or write pointer is positioned such that the number of frames +requested will require a loop, it will be clamped to the end of the buffer. Therefore, the number +of frames you're given may be less than the number you requested. -After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the buffer and then "commit" it with `ma_pcm_rb_commit_read()` or -`ma_pcm_rb_commit_write()`. This is where the read/write pointers are updated. When you commit you need to pass in the buffer that was returned by the earlier -call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and -`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was originally requested. +After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the +buffer and then "commit" it with `ma_pcm_rb_commit_read()` or `ma_pcm_rb_commit_write()`. This is +where the read/write pointers are updated. When you commit you need to pass in the buffer that was +returned by the earlier call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is +only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and +`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was +originally requested. -If you want to correct for drift between the write pointer and the read pointer you can use a combination of `ma_pcm_rb_pointer_distance()`, -`ma_pcm_rb_seek_read()` and `ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only move the read pointer forward via -the consumer thread, and the write pointer forward by the producer thread. If there is too much space between the pointers, move the read pointer forward. If +If you want to correct for drift between the write pointer and the read pointer you can use a +combination of `ma_pcm_rb_pointer_distance()`, `ma_pcm_rb_seek_read()` and +`ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only +move the read pointer forward via the consumer thread, and the write pointer forward by the +producer thread. If there is too much space between the pointers, move the read pointer forward. If there is too little space between the pointers, move the write pointer forward. -You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` API. This is exactly the same, only you will use the `ma_rb` -functions instead of `ma_pcm_rb` and instead of frame counts you will pass around byte counts. +You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` +API. This is exactly the same, only you will use the `ma_rb` functions instead of `ma_pcm_rb` and +instead of frame counts you will pass around byte counts. -The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most significant bit being used to encode a loop flag and the internally -managed buffers always being aligned to MA_SIMD_ALIGNMENT. +The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most +significant bit being used to encode a loop flag and the internally managed buffers always being +aligned to `MA_SIMD_ALIGNMENT`. -Note that the ring buffer is only thread safe when used by a single consumer thread and single producer thread. +Note that the ring buffer is only thread safe when used by a single consumer thread and single +producer thread. -11. Backends +15. Backends ============ The following backends are supported by miniaudio. @@ -1428,28 +3540,36 @@ The following backends are supported by miniaudio. Some backends have some nuance details you may want to be aware of. -11.1. WASAPI +15.1. WASAPI ------------ -- Low-latency shared mode will be disabled when using an application-defined sample rate which is different to the device's native sample rate. To work around - this, set `wasapi.noAutoConvertSRC` to true in the device config. This is due to IAudioClient3_InitializeSharedAudioStream() failing when the - `AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM` flag is specified. Setting wasapi.noAutoConvertSRC will result in miniaudio's internal resampler being used instead - which will in turn enable the use of low-latency shared mode. +- Low-latency shared mode will be disabled when using an application-defined sample rate which is + different to the device's native sample rate. To work around this, set `wasapi.noAutoConvertSRC` + to true in the device config. This is due to IAudioClient3_InitializeSharedAudioStream() failing + when the `AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM` flag is specified. Setting wasapi.noAutoConvertSRC + will result in miniaudio's internal resampler being used instead which will in turn enable the + use of low-latency shared mode. -11.2. PulseAudio +15.2. PulseAudio ---------------- - If you experience bad glitching/noise on Arch Linux, consider this fix from the Arch wiki: - https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling. Alternatively, consider using a different backend such as ALSA. + https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling. + Alternatively, consider using a different backend such as ALSA. -11.3. Android +15.3. Android ------------- -- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: `` -- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a limitation with OpenSL|ES. -- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration API (devices are enumerated through Java). You can however - perform your own device enumeration through Java and then set the ID in the ma_device_id structure (ma_device_id.aaudio) and pass it to ma_device_init(). -- The backend API will perform resampling where possible. The reason for this as opposed to using miniaudio's built-in resampler is to take advantage of any - potential device-specific optimizations the driver may implement. +- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: + `` +- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a + limitation with OpenSL|ES. +- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration + API (devices are enumerated through Java). You can however perform your own device enumeration + through Java and then set the ID in the ma_device_id structure (ma_device_id.aaudio) and pass it + to ma_device_init(). +- The backend API will perform resampling where possible. The reason for this as opposed to using + miniaudio's built-in resampler is to take advantage of any potential device-specific + optimizations the driver may implement. -11.4. UWP +15.4. UWP --------- - UWP only supports default playback and capture devices. - UWP requires the Microphone capability to be enabled in the application's manifest (Package.appxmanifest): @@ -1463,28 +3583,49 @@ Some backends have some nuance details you may want to be aware of. ``` -11.5. Web Audio / Emscripten +15.5. Web Audio / Emscripten ---------------------------- - You cannot use `-std=c*` compiler flags, nor `-ansi`. This only applies to the Emscripten build. -- The first time a context is initialized it will create a global object called "miniaudio" whose primary purpose is to act as a factory for device objects. -- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as they've been deprecated. -- Google has implemented a policy in their browsers that prevent automatic media output without first receiving some kind of user input. The following web page - has additional details: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device may fail if you try to start playback - without first handling some kind of user input. +- The first time a context is initialized it will create a global object called "miniaudio" whose + primary purpose is to act as a factory for device objects. +- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as + they've been deprecated. +- Google has implemented a policy in their browsers that prevent automatic media output without + first receiving some kind of user input. The following web page has additional details: + https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device + may fail if you try to start playback without first handling some kind of user input. -12. Miscellaneous Notes +16. Optimization Tips +===================== + +16.1. High Level API +-------------------- +- If a sound does not require doppler or pitch shifting, consider disabling pitching by + initializing the sound with the `MA_SOUND_FLAG_NO_PITCH` flag. +- If a sound does not require spatialization, disable it by initialzing the sound with the + `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. It can be renabled again post-initialization with + `ma_sound_set_spatialization_enabled()`. + + + +17. Miscellaneous Notes ======================= -- Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for WASAPI and Core Audio, however other backends such as - PulseAudio may naturally support it, though not all have been tested. -- The contents of the output buffer passed into the data callback will always be pre-initialized to silence unless the `noPreZeroedOutputBuffer` config variable - in `ma_device_config` is set to true, in which case it'll be undefined which will require you to write something to the entire buffer. -- By default miniaudio will automatically clip samples. This only applies when the playback sample format is configured as `ma_format_f32`. If you are doing - clipping yourself, you can disable this overhead by setting `noClip` to true in the device config. -- The sndio backend is currently only enabled on OpenBSD builds. -- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can use it. +- Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for + WASAPI and Core Audio, however other backends such as PulseAudio may naturally support it, though + not all have been tested. +- The contents of the output buffer passed into the data callback will always be pre-initialized to + silence unless the `noPreSilencedOutputBuffer` config variable in `ma_device_config` is set to + true, in which case it'll be undefined which will require you to write something to the entire + buffer. +- By default miniaudio will automatically clip samples. This only applies when the playback sample + format is configured as `ma_format_f32`. If you are doing clipping yourself, you can disable this + overhead by setting `noClip` to true in the device config. - Note that GCC and Clang requires `-msse2`, `-mavx2`, etc. for SIMD optimizations. -- When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This is due to 64-bit file APIs not being available. +- The sndio backend is currently only enabled on OpenBSD builds. +- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can + use it. +- When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This + is due to 64-bit file APIs not being available. */ - diff --git a/vendor/miniaudio/effects.odin b/vendor/miniaudio/effects.odin new file mode 100644 index 000000000..e86d670d9 --- /dev/null +++ b/vendor/miniaudio/effects.odin @@ -0,0 +1,300 @@ +package miniaudio + +import c "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/* +Delay +*/ +delay_config :: struct { + channels: u32, + sampleRate: u32, + delayInFrames: u32, + delayStart: b32, /* Set to true to delay the start of the output; false otherwise. */ + wet: f32, /* 0..1. Default = 1. */ + dry: f32, /* 0..1. Default = 1. */ + decay: f32, /* 0..1. Default = 0 (no feedback). Feedback decay. Use this for echo. */ +} + +delay :: struct { + config: delay_config, + cursor: u32, /* Feedback is written to this cursor. Always equal or in front of the read cursor. */ + bufferSizeInFrames: u32, /* The maximum of config.startDelayInFrames and config.feedbackDelayInFrames. */ + pBuffer: [^]f32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + delay_config_init :: proc(channels, sampleRate, delayInFrames: u32, decay: f32) -> delay_config --- + + delay_init :: proc(pConfig: ^delay_config, pAllocationCallbacks: ^allocation_callbacks, pDelay: ^delay) -> result --- + delay_uninit :: proc(pDelay: ^delay, pAllocationCallbacks: ^allocation_callbacks) --- + delay_process_pcm_frames :: proc(pDelay: ^delay, pFramesOut, pFramesIn: rawptr, frameCount: u32) -> result --- + delay_set_wet :: proc(pDelay: ^delay, value: f32) --- + delay_get_wet :: proc(pDelay: ^delay) -> f32 --- + delay_set_dry :: proc(pDelay: ^delay, value: f32) --- + delay_get_dry :: proc(pDelay: ^delay) -> f32 --- + delay_set_decay :: proc(pDelay: ^delay, value: f32) --- + delay_get_decay :: proc(pDelay: ^delay) -> f32 --- +} + + +/* Gainer for smooth volume changes. */ +gainer_config :: struct { + channels: u32, + smoothTimeInFrames: u32, +} + +gainer :: struct { + config: gainer_config, + t: u32, + pOldGains: [^]f32, + pNewGains: [^]f32, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + gainer_config_init :: proc(channels, smoothTimeInFrames: u32) -> gainer_config --- + + gainer_get_heap_size :: proc(pConfig: ^gainer_config, pHeapSizeInBytes: ^c.size_t) -> result --- + gainer_init_preallocated :: proc(pConfig: ^gainer_config, pHeap: rawptr, pGainer: ^gainer) -> result --- + gainer_init :: proc(pConfig: ^gainer_config, pAllocationCallbacks: ^allocation_callbacks, pGainer: ^gainer) -> result --- + gainer_uninit :: proc(pGainer: ^gainer, pAllocationCallbacks: ^allocation_callbacks) --- + gainer_process_pcm_frames :: proc(pGainer: ^gainer, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- + gainer_set_gain :: proc(pGainer: ^gainer, newGain: f32) -> result --- + gainer_set_gains :: proc(pGainer: ^gainer, pNewGains: [^]f32) -> result --- +} + + +/* Stereo panner. */ +pan_mode :: enum c.int { + balance = 0, /* Does not blend one side with the other. Technically just a balance. Compatible with other popular audio engines and therefore the default. */ + pan, /* A true pan. The sound from one side will "move" to the other side and blend with it. */ +} + +panner_config :: struct { + format: format, + channels: u32, + mode: pan_mode, + pan: f32, +} + +panner :: struct { + format: format, + channels: u32, + mode: pan_mode, + pan: f32, /* -1..1 where 0 is no pan, -1 is left side, +1 is right side. Defaults to 0. */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + panner_config_init :: proc(format: format, channels: u32) -> panner_config --- + + panner_init :: proc(pConfig: ^panner_config, pPanner: ^panner) -> result --- + panner_process_pcm_frames :: proc(pPanner: ^panner, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- + panner_set_mode :: proc(pPanner: ^panner, mode: pan_mode) --- + panner_get_mode :: proc(pPanner: ^panner) -> pan_mode --- + panner_set_pan :: proc(pPanner: ^panner, pan: f32) --- + panner_get_pan :: proc(pPanner: ^panner) -> f32 --- +} + + +/* Fader. */ +fader_config :: struct { + format: format, + channels: u32, + sampleRate: u32, +} + +fader :: struct { + config: fader_config, + volumeBeg: f32, /* If volumeBeg and volumeEnd is equal to 1, no fading happens (ma_fader_process_pcm_frames() will run as a passthrough). */ + volumeEnd: f32, + lengthInFrames: u64, /* The total length of the fade. */ + cursorInFrames: u64, /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + fader_config_init :: proc(format: format, channels, sampleRate: u32) -> fader_config --- + + fader_init :: proc(pConfig: ^fader_config, pFader: ^fader) -> result --- + fader_process_pcm_frames :: proc(pFader: ^fader, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- + fader_get_data_format :: proc(pFader: ^fader, pFormat: ^format, pChannels, pSampleRate: ^u32) --- + fader_set_fade :: proc(pFader: ^fader, volumeBeg, volumeEnd: f32, lengthInFrames: u64) --- + fader_get_current_volume :: proc(pFader: ^fader) -> f32 --- +} + + +/* Spatializer. */ +vec3f :: struct { + x: f32, + y: f32, + z: f32, +} + +attenuation_model :: enum c.int { + none, /* No distance attenuation and no spatialization. */ + inverse, /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */ + linear, /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */ + exponential, /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */ +} + +positioning :: enum c.int { + absolute, + relative, +} + +handedness :: enum c.int { + right, + left, +} + +spatializer_listener_config :: struct { + channelsOut: u32, + pChannelMapOut: [^]channel, + handedness: handedness, /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + coneInnerAngleInRadians: f32, + coneOuterAngleInRadians: f32, + coneOuterGain: f32, + speedOfSound: f32, + worldUp: vec3f, +} + +spatializer_listener :: struct { + config: spatializer_listener_config, + position: vec3f, /* The absolute position of the listener. */ + direction: vec3f, /* The direction the listener is facing. The world up vector is config.worldUp. */ + velocity: vec3f, + isEnabled: b32, + + /* Memory management. */ + _ownsHeap: b32, + _pHeap: rawptr, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + spatializer_listener_config_init :: proc(channelsOut: u32) -> spatializer_listener_config --- + + spatializer_listener_get_heap_size :: proc(pConfig: ^spatializer_listener_config, pHeapSizeInBytes: ^c.size_t) -> result --- + spatializer_listener_init_preallocated :: proc(pConfig: ^spatializer_listener_config, pHeap: rawptr, pListener: ^spatializer_listener) -> result --- + spatializer_listener_init :: proc(pConfig: ^spatializer_listener_config, pAllocationCallbacks: ^allocation_callbacks, pListener: ^spatializer_listener) -> result --- + spatializer_listener_uninit :: proc(pListener: ^spatializer_listener, pAllocationCallbacks: ^allocation_callbacks) --- + spatializer_listener_get_channel_map :: proc(pListener: ^spatializer_listener) -> ^channel --- + spatializer_listener_set_cone :: proc(pListener: ^spatializer_listener, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + spatializer_listener_get_cone :: proc(pListener: ^spatializer_listener, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + spatializer_listener_set_position :: proc(pListener: ^spatializer_listener, x, y, z: f32) --- + spatializer_listener_get_position :: proc(pListener: ^spatializer_listener) -> vec3f --- + spatializer_listener_set_direction :: proc(pListener: ^spatializer_listener, x, y, z: f32) --- + spatializer_listener_get_direction :: proc(pListener: ^spatializer_listener) -> vec3f --- + spatializer_listener_set_velocity :: proc(pListener: ^spatializer_listener, x, y, z: f32) --- + spatializer_listener_get_velocity :: proc(pListener: ^spatializer_listener) -> vec3f --- + spatializer_listener_set_speed_of_sound :: proc(pListener: ^spatializer_listener, speedOfSound: f32) --- + spatializer_listener_get_speed_of_sound :: proc(pListener: ^spatializer_listener) -> f32 --- + spatializer_listener_set_world_up :: proc(pListener: ^spatializer_listener, x, y, z: f32) --- + spatializer_listener_get_world_up :: proc(pListener: ^spatializer_listener) -> vec3f --- + spatializer_listener_set_enabled :: proc(pListener: ^spatializer_listener, isEnabled: b32) --- + spatializer_listener_is_enabled :: proc(pListener: ^spatializer_listener) -> b32 --- +} + +spatializer_config :: struct { + channelsIn: u32, + channelsOut: u32, + pChannelMapIn: [^]channel, + attenuationModel: attenuation_model, + positioning: positioning, + handedness: handedness, /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + minGain: f32, + maxGain: f32, + minDistance: f32, + maxDistance: f32, + rolloff: f32, + coneInnerAngleInRadians: f32, + coneOuterAngleInRadians: f32, + coneOuterGain: f32, + dopplerFactor: f32, /* Set to 0 to disable doppler effect. */ + directionalAttenuationFactor: f32, /* Set to 0 to disable directional attenuation. */ + gainSmoothTimeInFrames: u32, /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ +} + +spatializer :: struct { + channelsIn: u32, + channelsOut: u32, + pChannelMapIn: [^]channel, + attenuationModel: attenuation_model, + positioning: positioning, + handedness: handedness, /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + minGain: f32, + maxGain: f32, + minDistance: f32, + maxDistance: f32, + rolloff: f32, + coneInnerAngleInRadians: f32, + coneOuterAngleInRadians: f32, + coneOuterGain: f32, + dopplerFactor: f32, /* Set to 0 to disable doppler effect. */ + directionalAttenuationFactor: f32, /* Set to 0 to disable directional attenuation. */ + gainSmoothTimeInFrames: u32, /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ + position: vec3f, + direction: vec3f, + velocity: vec3f, /* For doppler effect. */ + dopplerPitch: f32, /* Will be updated by ma_spatializer_process_pcm_frames() and can be used by higher level functions to apply a pitch shift for doppler effect. */ + gainer: gainer, /* For smooth gain transitions. */ + pNewChannelGainsOut: [^]f32, /* An offset of _pHeap. Used by ma_spatializer_process_pcm_frames() to store new channel gains. The number of elements in this array is equal to config.channelsOut. */ + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + spatializer_config_init :: proc(channelsIn, channelsOut: u32) -> spatializer_config --- + + spatializer_get_heap_size :: proc(pConfig: ^spatializer_config, pHeapSizeInBytes: ^c.size_t) -> result --- + spatializer_init_preallocated :: proc(pConfig: ^spatializer_config, pHeap: rawptr, pSpatializer: ^spatializer) -> result --- + spatializer_init :: proc(pConfig: ^spatializer_config, pAllocationCallbacks: ^allocation_callbacks, pSpatializer: ^spatializer) -> result --- + spatializer_uninit :: proc(pSpatializer: ^spatializer, pAllocationCallbacks: ^allocation_callbacks) --- + spatializer_process_pcm_frames :: proc(pSpatializer: ^spatializer, pListener: ^spatializer_listener, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- + spatializer_get_input_channels :: proc(pSpatializer: ^spatializer) -> u32 --- + spatializer_get_output_channels :: proc(pSpatializer: ^spatializer) -> u32 --- + spatializer_set_attenuation_model :: proc(pSpatializer: ^spatializer, attenuationModel: attenuation_model) --- + spatializer_get_attenuation_model :: proc(pSpatializer: ^spatializer) -> attenuation_model --- + spatializer_set_positioning :: proc(pSpatializer: ^spatializer, positioning: positioning) --- + spatializer_get_positioning :: proc(pSpatializer: ^spatializer) -> positioning --- + spatializer_set_rolloff :: proc(pSpatializer: ^spatializer, rolloff: f32) --- + spatializer_get_rolloff :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_min_gain :: proc(pSpatializer: ^spatializer, minGain: f32) --- + spatializer_get_min_gain :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_max_gain :: proc(pSpatializer: ^spatializer, maxGain: f32) --- + spatializer_get_max_gain :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_min_distance :: proc(pSpatializer: ^spatializer, minDistance: f32) --- + spatializer_get_min_distance :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_max_distance :: proc(pSpatializer: ^spatializer, maxDistance: f32) --- + spatializer_get_max_distance :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_cone :: proc(pSpatializer: ^spatializer, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + spatializer_get_cone :: proc(pSpatializer: ^spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + spatializer_set_doppler_factor :: proc(pSpatializer: ^spatializer, dopplerFactor: f32) --- + spatializer_get_doppler_factor :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_directional_attenuation_factor :: proc(pSpatializer: ^spatializer, directionalAttenuationFactor: f32) --- + spatializer_get_directional_attenuation_factor :: proc(pSpatializer: ^spatializer) -> f32 --- + spatializer_set_position :: proc(pSpatializer: ^spatializer, x, y, z: f32) --- + spatializer_get_position :: proc(pSpatializer: ^spatializer) -> vec3f --- + spatializer_set_direction :: proc(pSpatializer: ^spatializer, x, y, z: f32) --- + spatializer_get_direction :: proc(pSpatializer: ^spatializer) -> vec3f --- + spatializer_set_velocity :: proc(pSpatializer: ^spatializer, x, y, z: f32) --- + spatializer_get_velocity :: proc(pSpatializer: ^spatializer) -> vec3f --- + spatializer_get_relative_position_and_direction :: proc(pSpatializer: ^spatializer, pListener: ^spatializer_listener, pRelativePos, pRelativeDir: ^vec3f) --- +} diff --git a/vendor/miniaudio/encoding.odin b/vendor/miniaudio/encoding.odin index 9b84108dc..ee396466a 100644 --- a/vendor/miniaudio/encoding.odin +++ b/vendor/miniaudio/encoding.odin @@ -19,14 +19,14 @@ Encoders do not perform any format conversion for you. If your target format doe ************************************************************************************************************************************************************/ -encoder_write_proc :: proc "c" (pEncoder: ^encoder, pBufferIn: rawptr, bytesToWrite: c.size_t) -> c.size_t /* Returns the number of bytes written. */ -encoder_seek_proc :: proc "c" (pEncoder: ^encoder, byteOffset: c.int, origin: seek_origin) -> b32 +encoder_write_proc :: proc "c" (pEncoder: ^encoder, pBufferIn: rawptr, bytesToWrite: c.size_t, pBytesWritten: ^c.size_t) -> result +encoder_seek_proc :: proc "c" (pEncoder: ^encoder, offset: i64, origin: seek_origin) -> result encoder_init_proc :: proc "c" (pEncoder: ^encoder) -> result -encoder_uninit_proc :: proc "c" (pEncoder: ^encoder) -encoder_write_pcm_frames_proc :: proc "c" (pEncoder: ^encoder, pFramesIn: rawptr, frameCount: u64) -> u64 +encoder_uninit_proc :: proc "c" (pEncoder: ^encoder) +encoder_write_pcm_frames_proc :: proc "c" (pEncoder: ^encoder, pFramesIn: rawptr, frameCount: u64, pFramesWritten: ^u64) -> result encoder_config :: struct { - resourceFormat: resource_format, + encodingFormat: encoding_format, format: format, channels: u32, sampleRate: u32, @@ -42,16 +42,23 @@ encoder :: struct { onWritePCMFrames: encoder_write_pcm_frames_proc, pUserData: rawptr, pInternalEncoder: rawptr, /* <-- The drwav/drflac/stb_vorbis/etc. objects. */ - pFile: rawptr, /* FILE*. Only used when initialized with ma_encoder_init_file(). */ + data: struct #raw_union { + vfs: struct { + pVFS: ^vfs, + file: vfs_file, + }, + }, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { - encoder_config_init :: proc(resourceFormat: resource_format, format: format, channels: u32, sampleRate: u32) -> encoder_config --- + encoder_config_init :: proc(encodingFormat: encoding_format, format: format, channels: u32, sampleRate: u32) -> encoder_config --- encoder_init :: proc(onWrite: encoder_write_proc, onSeek: encoder_seek_proc, pUserData: rawptr, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- + encoder_init_vfs :: proc(pVFS: ^vfs, pFilePath: cstring, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- + encoder_init_vfs_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- encoder_init_file :: proc(pFilePath: cstring, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- encoder_init_file_w :: proc(pFilePath: [^]c.wchar_t, pConfig: ^encoder_config, pEncoder: ^encoder) -> result --- encoder_uninit :: proc(pEncoder: ^encoder) --- - encoder_write_pcm_frames :: proc(pEncoder: ^encoder, FramesIn: rawptr, frameCount: u64) -> u64 --- + encoder_write_pcm_frames :: proc(pEncoder: ^encoder, FramesIn: rawptr, frameCount: u64, pFramesWritten: ^u64) -> result --- } diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin new file mode 100644 index 000000000..935d54744 --- /dev/null +++ b/vendor/miniaudio/engine.odin @@ -0,0 +1,341 @@ +package miniaudio + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/************************************************************************************************************************************************************ + +Engine + +************************************************************************************************************************************************************/ + +/* Sound flags. */ +sound_flags :: enum c.int { + STREAM = 0x00000001, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ + DECODE = 0x00000002, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ + ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ + WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ + NO_DEFAULT_ATTACHMENT = 0x00000010, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ + NO_PITCH = 0x00000020, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ + NO_SPATIALIZATION = 0x00000040, /* Disable spatialization. */ +} + +ENGINE_MAX_LISTENERS :: 4 + +LISTENER_INDEX_CLOSEST :: 255 + +engine_node_type :: enum c.int { + sound, + group, +} + +engine_node_config :: struct { + pEngine: ^engine, + type: engine_node_type, + channelsIn: u32, + channelsOut: u32, + sampleRate: u32, /* Only used when the type is set to ma_engine_node_type_sound. */ + isPitchDisabled: b8, /* Pitching can be explicitly disable with MA_SOUND_FLAG_NO_PITCH to optimize processing. */ + isSpatializationDisabled: b8, /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */ + pinnedListenerIndex: u8, /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ +} + +/* Base node object for both ma_sound and ma_sound_group. */ +engine_node :: struct { + baseNode: node_base, /* Must be the first member for compatiblity with the ma_node API. */ + pEngine: ^engine, /* A pointer to the engine. Set based on the value from the config. */ + sampleRate: u32, /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ + fader: fader, + resampler: linear_resampler, /* For pitch shift. */ + spatializer: spatializer, + panner: panner, + pitch: f32, /*atomic*/ + oldPitch: f32, /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */ + oldDopplerPitch: f32, /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */ + isPitchDisabled: b32, /*atomic*/ /* When set to true, pitching will be disabled which will allow the resampler to be bypassed to save some computation. */ + isSpatializationDisabled: b32, /*atomic*/ /* Set to false by default. When set to false, will not have spatialisation applied. */ + pinnedListenerIndex: u32, /*atomic*/ /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ + + /* Memory management. */ + _ownsHeap: b8, + _pHeap: rawptr, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + engine_node_config_init :: proc(pEngine: ^engine, type: engine_node_type, flags: u32) -> engine_node_config --- + + engine_node_get_heap_size :: proc(pConfig: ^engine_node_config, pHeapSizeInBytes: ^c.size_t) -> result --- + engine_node_init_preallocated :: proc(pConfig: ^engine_node_config, pHeap: rawptr, pEngineNode: ^engine_node) -> result --- + engine_node_init :: proc(pConfig: ^engine_node_config, pAllocationCallbacks: ^allocation_callbacks, pEngineNode: ^engine_node) -> result --- + engine_node_uninit :: proc(pEngineNode: ^engine_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +SOUND_SOURCE_CHANNEL_COUNT :: 0xFFFFFFFF + +sound_config :: struct { + pFilePath: cstring, /* Set this to load from the resource manager. */ + pFilePathW: [^]c.wchar_t, /* Set this to load from the resource manager. */ + pDataSource: ^data_source, /* Set this to load from an existing data source. */ + pInitialAttachment: ^node, /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ + initialAttachmentInputBusIndex: u32, /* The index of the input bus of pInitialAttachment to attach the sound to. */ + channelsIn: u32, /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ + channelsOut: u32, /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ + flags: u32, /* A combination of MA_SOUND_FLAG_* flags. */ + initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */ + rangeBegInPCMFrames: u64, + rangeEndInPCMFrames: u64, + loopPointBegInPCMFrames: u64, + loopPointEndInPCMFrames: u64, + isLooping: b32, + pDoneFence: ^fence, /* Released when the resource manager has finished decoding the entire sound. Not used with streams. */ +} + +sound :: struct { + engineNode: engine_node, /* Must be the first member for compatibility with the ma_node API. */ + pDataSource: ^data_source, + seekTarget: u64, /*atomic*/ /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */ + atEnd: b32, /*atomic*/ + ownsDataSource: b8, + + /* + We're declaring a resource manager data source object here to save us a malloc when loading a + sound via the resource manager, which I *think* will be the most common scenario. + */ + pResourceManagerDataSource: ^resource_manager_data_source, +} + +/* Structure specifically for sounds played with ma_engine_play_sound(). Making this a separate structure to reduce overhead. */ +sound_inlined :: struct { + sound: sound, + pNext: ^sound_inlined, + pPrev: ^sound_inlined, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + sound_config_init :: proc() -> sound_config --- + + sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- + sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- + sound_init_copy :: proc(pEngine: ^engine, pExistingSound: ^sound, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- + sound_init_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- + sound_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_config, pSound: ^sound) -> result --- + sound_uninit :: proc(pSound: ^sound) --- + sound_get_engine :: proc(pSound: ^sound) -> ^engine --- + sound_get_data_source :: proc(pSound: ^sound) -> ^data_source --- + sound_start :: proc(pSound: ^sound) -> result --- + sound_stop :: proc(pSound: ^sound) -> result --- + sound_set_volume :: proc(pSound: ^sound, volume: f32) --- + sound_get_volume :: proc(pSound: ^sound) -> f32 --- + sound_set_pan :: proc(pSound: ^sound, pan: f32) --- + sound_get_pan :: proc(pSound: ^sound) -> f32 --- + sound_set_pan_mode :: proc(pSound: ^sound, panMode: pan_mode) --- + sound_get_pan_mode :: proc(pSound: ^sound) -> pan_mode --- + sound_set_pitch :: proc(pSound: ^sound, pitch: f32) --- + sound_get_pitch :: proc(pSound: ^sound) -> f32 --- + sound_set_spatialization_enabled :: proc(pSound: ^sound, enabled: b32) --- + sound_is_spatialization_enabled :: proc(pSound: ^sound) -> b32 --- + sound_set_pinned_listener_index :: proc(pSound: ^sound, listenerIndex: u32) --- + sound_get_pinned_listener_index :: proc(pSound: ^sound) -> u32 --- + sound_get_listener_index :: proc(pSound: ^sound) -> u32 --- + sound_get_direction_to_listener :: proc(pSound: ^sound) -> vec3f --- + sound_set_position :: proc(pSound: ^sound, x, y, z: f32) --- + sound_get_position :: proc(pSound: ^sound) -> vec3f --- + sound_set_direction :: proc(pSound: ^sound, x, y, z: f32) --- + sound_get_direction :: proc(pSound: ^sound) -> vec3f --- + sound_set_velocity :: proc(pSound: ^sound, x, y, z: f32) --- + sound_get_velocity :: proc(pSound: ^sound) -> vec3f --- + sound_set_attenuation_model :: proc(pSound: ^sound, attenuationModel: attenuation_model) --- + sound_get_attenuation_model :: proc(pSound: ^sound) -> attenuation_model --- + sound_set_positioning :: proc(pSound: ^sound, positioning: positioning) --- + sound_get_positioning :: proc(pSound: ^sound) -> positioning --- + sound_set_rolloff :: proc(pSound: ^sound, rolloff: f32) --- + sound_get_rolloff :: proc(pSound: ^sound) -> f32 --- + sound_set_min_gain :: proc(pSound: ^sound, minGain: f32) --- + sound_get_min_gain :: proc(pSound: ^sound) -> f32 --- + sound_set_max_gain :: proc(pSound: ^sound, maxGain: f32) --- + sound_get_max_gain :: proc(pSound: ^sound) -> f32 --- + sound_set_min_distance :: proc(pSound: ^sound, minDistance: f32) --- + sound_get_min_distance :: proc(pSound: ^sound) -> f32 --- + sound_set_max_distance :: proc(pSound: ^sound, maxDistance: f32) --- + sound_get_max_distance :: proc(pSound: ^sound) -> f32 --- + sound_set_cone :: proc(pSound: ^sound, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + sound_get_cone :: proc(pSound: ^sound, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + sound_set_doppler_factor :: proc(pSound: ^sound, dopplerFactor: f32) --- + sound_get_doppler_factor :: proc(pSound: ^sound) -> f32 --- + sound_set_directional_attenuation_factor :: proc(pSound: ^sound, directionalAttenuationFactor: f32) --- + sound_get_directional_attenuation_factor :: proc(pSound: ^sound) -> f32 --- + sound_set_fade_in_pcm_frames :: proc(pSound: ^sound, volumeBeg, volumeEnd: f32, fadeLengthInFrames: u64) --- + sound_set_fade_in_milliseconds :: proc(pSound: ^sound, volumeBeg, volumeEnd: f32, fadeLengthInMilliseconds: u64) --- + sound_get_current_fade_volume :: proc(pSound: ^sound) -> f32 --- + sound_set_start_time_in_pcm_frames :: proc(pSound: ^sound, absoluteGlobalTimeInFrames: u64) --- + sound_set_start_time_in_milliseconds :: proc(pSound: ^sound, absoluteGlobalTimeInMilliseconds: u64) --- + sound_set_stop_time_in_pcm_frames :: proc(pSound: ^sound, absoluteGlobalTimeInFrames: u64) --- + sound_set_stop_time_in_milliseconds :: proc(pSound: ^sound, absoluteGlobalTimeInMilliseconds: u64) --- + sound_is_playing :: proc(pSound: ^sound) -> b32 --- + sound_get_time_in_pcm_frames :: proc(pSound: ^sound) -> u64 --- + sound_set_looping :: proc(pSound: ^sound, isLooping: b32) --- + sound_is_looping :: proc(pSound: ^sound) -> b32 --- + sound_at_end :: proc(pSound: ^sound) -> b32 --- + sound_seek_to_pcm_frame :: proc(pSound: ^sound, frameIndex: u64) -> result --- /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ + sound_get_data_format :: proc(pSound: ^sound, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: ^channel, channelMapCap: c.size_t) -> result --- + sound_get_cursor_in_pcm_frames :: proc(pSound: ^sound, pCursor: ^u64) -> result --- + sound_get_length_in_pcm_frames :: proc(pSound: ^sound, pLength: ^u64) -> result --- + sound_get_cursor_in_seconds :: proc(pSound: ^sound, pCursor: ^f32) -> result --- + sound_get_length_in_seconds :: proc(pSound: ^sound, pLength: ^f32) -> result --- +} + + +/* A sound group is just a sound. */ +sound_group_config :: distinct sound_config +sound_group :: distinct sound + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + sound_group_config_init :: proc() -> sound_group_config --- + + sound_group_init :: proc(pEngine: ^engine, flags: u32, pParentGroup, pGroup: ^sound_group) -> result --- + sound_group_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_group_config, pGroup: ^sound_group) -> result --- + sound_group_uninit :: proc(pGroup: ^sound_group) --- + sound_group_get_engine :: proc(pGroup: ^sound_group) -> ^engine --- + sound_group_start :: proc(pGroup: ^sound_group) -> result --- + sound_group_stop :: proc(pGroup: ^sound_group) -> result --- + sound_group_set_volume :: proc(pGroup: ^sound_group, volume: f32) --- + sound_group_get_volume :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_pan :: proc(pGroup: ^sound_group, pan: f32) --- + sound_group_get_pan :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_pan_mode :: proc(pGroup: ^sound_group, panMode: pan_mode) --- + sound_group_get_pan_mode :: proc(pGroup: ^sound_group) -> pan_mode --- + sound_group_set_pitch :: proc(pGroup: ^sound_group, pitch: f32) --- + sound_group_get_pitch :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_spatialization_enabled :: proc(pGroup: ^sound_group, enabled: b32) --- + sound_group_is_spatialization_enabled :: proc(pGroup: ^sound_group) -> b32 --- + sound_group_set_pinned_listener_index :: proc(pGroup: ^sound_group, listenerIndex: u32) --- + sound_group_get_pinned_listener_index :: proc(pGroup: ^sound_group) -> u32 --- + sound_group_get_listener_index :: proc(pGroup: ^sound_group) -> u32 --- + sound_group_get_direction_to_listener :: proc(pGroup: ^sound_group) -> vec3f --- + sound_group_set_position :: proc(pGroup: ^sound_group, x, y, z: f32) --- + sound_group_get_position :: proc(pGroup: ^sound_group) -> vec3f --- + sound_group_set_direction :: proc(pGroup: ^sound_group, x, y, z: f32) --- + sound_group_get_direction :: proc(pGroup: ^sound_group) -> vec3f --- + sound_group_set_velocity :: proc(pGroup: ^sound_group, x, y, z: f32) --- + sound_group_get_velocity :: proc(pGroup: ^sound_group) -> vec3f --- + sound_group_set_attenuation_model :: proc(pGroup: ^sound_group, attenuationModel: attenuation_model) --- + sound_group_get_attenuation_model :: proc(pGroup: ^sound_group) -> attenuation_model --- + sound_group_set_positioning :: proc(pGroup: ^sound_group, positioning: positioning) --- + sound_group_get_positioning :: proc(pGroup: ^sound_group) -> positioning --- + sound_group_set_rolloff :: proc(pGroup: ^sound_group, rolloff: f32) --- + sound_group_get_rolloff :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_min_gain :: proc(pGroup: ^sound_group, minGain: f32) --- + sound_group_get_min_gain :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_max_gain :: proc(pGroup: ^sound_group, maxGain: f32) --- + sound_group_get_max_gain :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_min_distance :: proc(pGroup: ^sound_group, minDistance: f32) --- + sound_group_get_min_distance :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_max_distance :: proc(pGroup: ^sound_group, maxDistance: f32) --- + sound_group_get_max_distance :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_cone :: proc(pGroup: ^sound_group, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + sound_group_get_cone :: proc(pGroup: ^sound_group, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + sound_group_set_doppler_factor :: proc(pGroup: ^sound_group, dopplerFactor: f32) --- + sound_group_get_doppler_factor :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_directional_attenuation_factor :: proc(pGroup: ^sound_group, directionalAttenuationFactor: f32) --- + sound_group_get_directional_attenuation_factor :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_fade_in_pcm_frames :: proc(pGroup: ^sound_group, volumeBeg, volumeEnd: f32, fadeLengthInFrames: u64) --- + sound_group_set_fade_in_milliseconds :: proc(pGroup: ^sound_group, volumeBeg, volumeEnd: f32, fadeLengthInMilliseconds: u64) --- + sound_group_get_current_fade_volume :: proc(pGroup: ^sound_group) -> f32 --- + sound_group_set_start_time_in_pcm_frames :: proc(pGroup: ^sound_group, absoluteGlobalTimeInFrames: u64) --- + sound_group_set_start_time_in_milliseconds :: proc(pGroup: ^sound_group, absoluteGlobalTimeInMilliseconds: u64) --- + sound_group_set_stop_time_in_pcm_frames :: proc(pGroup: ^sound_group, absoluteGlobalTimeInFrames: u64) --- + sound_group_set_stop_time_in_milliseconds :: proc(pGroup: ^sound_group, absoluteGlobalTimeInMilliseconds: u64) --- + sound_group_is_playing :: proc(pGroup: ^sound_group) -> b32 --- + sound_group_get_time_in_pcm_frames :: proc(pGroup: ^sound_group) -> u64 --- +} + + +engine_config :: struct { + pResourceManager: ^resource_manager, /* Can be null in which case a resource manager will be created for you. */ + pContext: ^context_type, + pDevice: ^device, /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */ + pPlaybackDeviceID: ^device_id, /* The ID of the playback device to use with the default listener. */ + pLog: ^log, /* When set to NULL, will use the context's log. */ + listenerCount: u32, /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */ + channels: u32, /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ + sampleRate: u32, /* The sample rate. When set to 0 will use the native channel count of the device. */ + periodSizeInFrames: u32, /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/ + periodSizeInMilliseconds: u32, /* Used if periodSizeInFrames is unset. */ + gainSmoothTimeInFrames: u32, /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ + gainSmoothTimeInMilliseconds: u32, /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ + allocationCallbacks: allocation_callbacks, + noAutoStart: b32, /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ + noDevice: b32, /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ + monoExpansionMode: mono_expansion_mode, /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ + pResourceManagerVFS: ^vfs, /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */ +} + +engine :: struct { + nodeGraph: node_graph, /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ + pResourceManager: ^resource_manager, + pDevice: ^device, /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ + pLog: ^log, + sampleRate: u32, + listenerCount: u32, + listeners: [ENGINE_MAX_LISTENERS]spatializer_listener, + allocationCallbacks: allocation_callbacks, + ownsResourceManager: b8, + ownsDevice: b8, + inlinedSoundLock: spinlock, /* For synchronizing access so the inlined sound list. */ + pInlinedSoundHead: ^sound_inlined, /* The first inlined sound. Inlined sounds are tracked in a linked list. */ + inlinedSoundCount: u32, /*atomic*/ /* The total number of allocated inlined sound objects. Used for debugging. */ + gainSmoothTimeInFrames: u32, /* The number of frames to interpolate the gain of spatialized sounds across. */ + monoExpansionMode: mono_expansion_mode, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + engine_config_init :: proc() -> engine_config --- + + engine_init :: proc(pConfig: ^engine_config, pEngine: ^engine) -> result --- + engine_uninit :: proc(pEngine: ^engine) --- + engine_read_pcm_frames :: proc(pEngine: ^engine, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + engine_get_node_graph :: proc(pEngine: ^engine) -> ^node_graph --- + engine_get_resource_manager :: proc(pEngine: ^engine) -> ^resource_manager --- + engine_get_device :: proc(pEngine: ^engine) -> ^device --- + engine_get_log :: proc(pEngine: ^engine) -> ^log --- + engine_get_endpoint :: proc(pEngine: ^engine) -> ^node --- + engine_get_time :: proc(pEngine: ^engine) -> u64 --- + engine_set_time :: proc(pEngine: ^engine, globalTime: u64) -> result --- + engine_get_channels :: proc(pEngine: ^engine) -> u32 --- + engine_get_sample_rate :: proc(pEngine: ^engine) -> u32 --- + + engine_start :: proc(pEngine: ^engine) -> result --- + engine_stop :: proc(pEngine: ^engine) -> result --- + engine_set_volume :: proc(pEngine: ^engine, volume: f32) -> result --- + engine_set_gain_db :: proc(pEngine: ^engine, gainDB: f32) -> result --- + + engine_get_listener_count :: proc(pEngine: ^engine) -> u32 --- + engine_find_closest_listener :: proc(pEngine: ^engine, absolutePosX, absolutePosY, absolutePosZ: f32) -> u32 --- + engine_listener_set_position :: proc(pEngine: ^engine, listenerIndex: u32, x, y, z: f32) --- + engine_listener_get_position :: proc(pEngine: ^engine, listenerIndex: u32) -> vec3f --- + engine_listener_set_direction :: proc(pEngine: ^engine, listenerIndex: u32, x, y, z: f32) --- + engine_listener_get_direction :: proc(pEngine: ^engine, listenerIndex: u32) -> vec3f --- + engine_listener_set_velocity :: proc(pEngine: ^engine, listenerIndex: u32, x, y, z: f32) --- + engine_listener_get_velocity :: proc(pEngine: ^engine, listenerIndex: u32) -> vec3f --- + engine_listener_set_cone :: proc(pEngine: ^engine, listenerIndex: u32, innerAngleInRadians, outerAngleInRadians, outerGain: f32) --- + engine_listener_get_cone :: proc(pEngine: ^engine, listenerIndex: u32, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain: ^f32) --- + engine_listener_set_world_up :: proc(pEngine: ^engine, listenerIndex: u32, x, y, z: f32) --- + engine_listener_get_world_up :: proc(pEngine: ^engine, listenerIndex: u32) -> vec3f --- + engine_listener_set_enabled :: proc(pEngine: ^engine, listenerIndex: u32, isEnabled: b32) --- + engine_listener_is_enabled :: proc(pEngine: ^engine, listenerIndex: u32) -> b32 --- + + engine_play_sound_ex :: proc(pEngine: ^engine, pFilePath: cstring, pNode: ^node, nodeInputBusIndex: u32) -> result --- + engine_play_sound :: proc(pEngine: ^engine, pFilePath: cstring, pGroup: ^sound_group) -> result --- /* Fire and forget. */ +} diff --git a/vendor/miniaudio/filtering.odin b/vendor/miniaudio/filtering.odin index 9949f6338..b8175c372 100644 --- a/vendor/miniaudio/filtering.odin +++ b/vendor/miniaudio/filtering.odin @@ -1,5 +1,7 @@ package miniaudio +import c "core:c/libc" + when ODIN_OS == .Windows { foreign import lib "lib/miniaudio.lib" } else when ODIN_OS == .Linux { @@ -19,14 +21,14 @@ biquad_coefficient :: struct #raw_union { } biquad_config :: struct { - format: format, + format: format, channels: u32, - b0: f64, - b1: f64, - b2: f64, - a0: f64, - a1: f64, - a2: f64, + b0: f64, + b1: f64, + b2: f64, + a0: f64, + a1: f64, + a2: f64, } biquad :: struct { @@ -37,17 +39,25 @@ biquad :: struct { b2: biquad_coefficient, a1: biquad_coefficient, a2: biquad_coefficient, - r1: [MAX_CHANNELS]biquad_coefficient, - r2: [MAX_CHANNELS]biquad_coefficient, + pR1: ^biquad_coefficient, + pR2: ^biquad_coefficient, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { biquad_config_init :: proc(format: format, channels: u32, b0, b1, b2, a0, a1, a2: f64) -> biquad_config --- - biquad_init :: proc(pConfig: ^biquad_config, pBQ: ^biquad) -> result --- + biquad_get_heap_size :: proc(pConfig: ^biquad_config, pHeapSizeInBytes: ^c.size_t) -> result --- + biquad_init_preallocated :: proc(pConfig: ^biquad_config, pHeap: rawptr, pBQ: ^biquad) -> result --- + biquad_init :: proc(pConfig: ^biquad_config, pAllocationCallbacks: ^allocation_callbacks, pBQ: ^biquad) -> result --- + biquad_uninit :: proc(pBQ: ^biquad, pAllocationCallbacks: ^allocation_callbacks) --- biquad_reinit :: proc(pConfig: ^biquad_config, pBQ: ^biquad) -> result --- - biquad_process_pcm_frames :: proc(pBQ: ^biquad, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- + biquad_clear_cache :: proc(pBQ: ^biquad) -> result --- + biquad_process_pcm_frames :: proc(pBQ: ^biquad, pFramesOut, pFramesIn: rawptr, frameCount: u64) -> result --- biquad_get_latency :: proc(pBQ: ^biquad) -> u32 --- } @@ -70,7 +80,11 @@ lpf1 :: struct { format: format, channels: u32, a: biquad_coefficient, - r1: [MAX_CHANNELS]biquad_coefficient, + pR1: ^biquad_coefficient, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } lpf2 :: struct { @@ -91,8 +105,12 @@ lpf :: struct { sampleRate: u32, lpf1Count: u32, lpf2Count: u32, - lpf1: [1]lpf1, - lpf2: [MAX_FILTER_ORDER/2]lpf2, + pLPF1: ^lpf1, + pLPF2: ^lpf2, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @@ -101,20 +119,32 @@ foreign lib { lpf1_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64) -> lpf1_config --- lpf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency, q: f64) -> lpf2_config --- - lpf1_init :: proc(pConfig: ^lpf1_config, pLPF: ^lpf1) -> result --- + lpf1_get_heap_size :: proc(pConfig: ^lpf1_config, pHeapSizeInBytes: ^c.size_t) -> result --- + lpf1_init_preallocated :: proc(pConfig: ^lpf1_config, pHeap: rawptr, pLPF: ^lpf1) -> result --- + lpf1_init :: proc(pConfig: ^lpf1_config, pAllocationCallbacks: ^allocation_callbacks, pLPF: ^lpf1) -> result --- + lpf1_uninit :: proc(pLPF: ^lpf1, pAllocationCallbacks: ^allocation_callbacks) --- lpf1_reinit :: proc(pConfig: ^lpf1_config, pLPF: ^lpf1) -> result --- + lpf1_clear_cache :: proc(pLPF: ^lpf1) -> result --- lpf1_process_pcm_frames :: proc(pLPF: ^lpf1, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- lpf1_get_latency :: proc(pLPF: ^lpf1) -> u32 --- - lpf2_init :: proc(pConfig: ^lpf2_config, pLPF: ^lpf2) -> result --- + lpf2_get_heap_size :: proc(pConfig: ^lpf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + lpf2_init_preallocated :: proc(pConfig: ^lpf2_config, pHeap: rawptr, pHPF: ^lpf2) -> result --- + lpf2_init :: proc(pConfig: ^lpf2_config, pAllocationCallbacks: ^allocation_callbacks, pLPF: ^lpf2) -> result --- + lpf2_uninit :: proc(pLPF: ^lpf2, pAllocationCallbacks: ^allocation_callbacks) --- lpf2_reinit :: proc(pConfig: ^lpf2_config, pLPF: ^lpf2) -> result --- + lpf2_clear_cache :: proc(pLPF: ^lpf2) -> result --- lpf2_process_pcm_frames :: proc(pLPF: ^lpf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- lpf2_get_latency :: proc(pLPF: ^lpf2) -> u32 --- lpf_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64, order: u32) -> lpf_config --- - lpf_init :: proc(pConfig: ^lpf_config, pLPF: ^lpf) -> result --- + lpf_get_heap_size :: proc(pConfig: ^lpf_config, pHeapSizeInBytes: ^c.size_t) -> result --- + lpf_init_preallocated :: proc(pConfig: ^lpf_config, pHeap: rawptr, pLPF: ^lpf) -> result --- + lpf_init :: proc(pConfig: ^lpf_config, pAllocationCallbacks: ^allocation_callbacks, pLPF: ^lpf) -> result --- + lpf_uninit :: proc(pLPF: ^lpf, pAllocationCallbacks: ^allocation_callbacks) --- lpf_reinit :: proc(pConfig: ^lpf_config, pLPF: ^lpf) -> result --- + lpf_clear_cache :: proc(pLPF: ^lpf) -> result --- lpf_process_pcm_frames :: proc(pLPF: ^lpf, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- lpf_get_latency :: proc(pLPF: ^lpf) -> u32 --- } @@ -138,7 +168,11 @@ hpf1 :: struct { format: format, channels: u32, a: biquad_coefficient, - r1: [MAX_CHANNELS]biquad_coefficient, + pR1: ^biquad_coefficient, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } hpf2 :: struct { @@ -159,8 +193,12 @@ hpf :: struct { sampleRate: u32, hpf1Count: u32, hpf2Count: u32, - hpf1: [1]hpf1, - hpf2: [MAX_FILTER_ORDER/2]hpf2, + pHPF1: ^hpf1, + pHPF2: ^hpf2, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @@ -169,19 +207,28 @@ foreign lib { hpf1_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64) -> hpf1_config --- hpf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency, q: f64) -> hpf2_config --- - hpf1_init :: proc(pConfig: ^hpf1_config, pHPF: ^hpf1) -> result --- + hpf1_get_heap_size :: proc(pConfig: ^hpf1_config, pHeapSizeInBytes: ^c.size_t) -> result --- + hpf1_init_preallocated :: proc(pConfig: ^hpf1_config, pHeap: rawptr, pLPF: ^hpf1) -> result --- + hpf1_init :: proc(pConfig: ^hpf1_config, pAllocationCallbacks: ^allocation_callbacks, pHPF: ^hpf1) -> result --- + hpf1_uninit :: proc(pHPF: ^hpf1, pAllocationCallbacks: ^allocation_callbacks) --- hpf1_reinit :: proc(pConfig: ^hpf1_config, pHPF: ^hpf1) -> result --- hpf1_process_pcm_frames :: proc(pHPF: ^hpf1, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- hpf1_get_latency :: proc(pHPF: ^hpf1) -> u32 --- - hpf2_init :: proc(pConfig: ^hpf2_config, pHPF: ^hpf2) -> result --- + hpf2_get_heap_size :: proc(pConfig: ^hpf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + hpf2_init_preallocated :: proc(pConfig: ^hpf2_config, pHeap: rawptr, pHPF: ^hpf2) -> result --- + hpf2_init :: proc(pConfig: ^hpf2_config, pAllocationCallbacks: ^allocation_callbacks, pHPF: ^hpf2) -> result --- + hpf2_uninit :: proc(pHPF: ^hpf2, pAllocationCallbacks: ^allocation_callbacks) --- hpf2_reinit :: proc(pConfig: ^hpf2_config, pHPF: ^hpf2) -> result --- hpf2_process_pcm_frames :: proc(pHPF: ^hpf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- hpf2_get_latency :: proc(pHPF: ^hpf2) -> u32 --- hpf_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64, order: u32) -> hpf_config --- - hpf_init :: proc(pConfig: ^hpf_config, pHPF: ^hpf) -> result --- + hpf_get_heap_size :: proc(pConfig: ^hpf_config, pHeapSizeInBytes: ^c.size_t) -> result --- + hpf_init_preallocated :: proc(pConfig: ^hpf_config, pHeap: rawptr, pLPF: ^hpf) -> result --- + hpf_init :: proc(pConfig: ^hpf_config, pAllocationCallbacks: ^allocation_callbacks, pHPF: ^hpf) -> result --- + hpf_uninit :: proc(pHPF: ^hpf, pAllocationCallbacks: ^allocation_callbacks) --- hpf_reinit :: proc(pConfig: ^hpf_config, pHPF: ^hpf) -> result --- hpf_process_pcm_frames :: proc(pHPF: ^hpf, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- hpf_get_latency :: proc(pHPF: ^hpf) -> u32 --- @@ -217,21 +264,31 @@ bpf :: struct { format: format, channels: u32, bpf2Count: u32, - bpf2: [MAX_FILTER_ORDER/2]bpf2, + pBPF2: ^bpf2, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") foreign lib { bpf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64, q: f64) -> bpf2_config --- - bpf2_init :: proc(pConfig: ^bpf2_config, pBPF: ^bpf2) -> result --- + bpf2_get_heap_size :: proc(pConfig: ^bpf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + bpf2_init_preallocated :: proc(pConfig: ^bpf2_config, pHeap: rawptr, pBPF: ^bpf2) -> result --- + bpf2_init :: proc(pConfig: ^bpf2_config, pAllocationCallbacks: ^allocation_callbacks, pBPF: ^bpf2) -> result --- + bpf2_uninit :: proc(pBPF: ^bpf2, pAllocationCallbacks: ^allocation_callbacks) --- bpf2_reinit :: proc(pConfig: ^bpf2_config, pBPF: ^bpf2) -> result --- bpf2_process_pcm_frames :: proc(pBPF: ^bpf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- bpf2_get_latency :: proc(pBPF: ^bpf2) -> u32 --- bpf_config_init :: proc(format: format, channels: u32, sampleRate: u32, cutoffFrequency: f64, order: u32) -> bpf_config --- - bpf_init :: proc(pConfig: ^bpf_config, pBPF: ^bpf) -> result --- + bpf_get_heap_size :: proc(pConfig: ^bpf_config, pHeapSizeInBytes: ^c.size_t) -> result --- + bpf_init_preallocated :: proc(pConfig: ^bpf_config, pHeap: rawptr, pBPF: ^bpf) -> result --- + bpf_init :: proc(pConfig: ^bpf_config, pAllocationCallbacks: ^allocation_callbacks, pBPF: ^bpf) -> result --- + bpf_uninit :: proc(pBPF: ^bpf, pAllocationCallbacks: ^allocation_callbacks) --- bpf_reinit :: proc(pConfig: ^bpf_config, pBPF: ^bpf) -> result --- bpf_process_pcm_frames :: proc(pBPF: ^bpf, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- bpf_get_latency :: proc(pBPF: ^bpf) -> u32 --- @@ -260,7 +317,10 @@ notch2 :: struct { foreign lib { notch2_config_init :: proc(format: format, channels: u32, sampleRate: u32, q: f64, frequency: f64) -> notch2_config --- - notch2_init :: proc(pConfig: ^notch2_config, pFilter: ^notch2) -> result --- + notch2_get_heap_size :: proc(pConfig: ^notch2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + notch2_init_preallocated :: proc(pConfig: ^notch2_config, pHeap: rawptr, pFilter: ^notch2) -> result --- + notch2_init :: proc(pConfig: ^notch2_config, pAllocationCallbacks: ^allocation_callbacks, pFilter: ^notch2) -> result --- + notch2_uninit :: proc(pFilter: ^notch2, pAllocationCallbacks: ^allocation_callbacks) --- notch2_reinit :: proc(pConfig: ^notch2_config, pFilter: ^notch2) -> result --- notch2_process_pcm_frames :: proc(pFilter: ^notch2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- notch2_get_latency :: proc(pFilter: ^notch2) -> u32 --- @@ -290,7 +350,10 @@ peak2 :: struct { foreign lib { peak2_config_init :: proc(format: format, channels: u32, sampleRate: u32, gainDB, q, frequency: f64) -> peak2_config --- - peak2_init :: proc(pConfig: ^peak2_config, pFilter: ^peak2) -> result --- + peak2_get_heap_size :: proc(pConfig: ^peak2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + peak2_init_preallocated :: proc(pConfig: ^peak2_config, pHeap: rawptr, pFilter: ^peak2) -> result --- + peak2_init :: proc(pConfig: ^peak2_config, pAllocationCallbacks: ^allocation_callbacks, pFilter: ^peak2) -> result --- + peak2_uninit :: proc(pFilter: ^peak2, pAllocationCallbacks: ^allocation_callbacks) --- peak2_reinit :: proc(pConfig: ^peak2_config, pFilter: ^peak2) -> result --- peak2_process_pcm_frames :: proc(pFilter: ^peak2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- peak2_get_latency :: proc(pFilter: ^peak2) -> u32 --- @@ -320,7 +383,10 @@ loshelf2 :: struct { foreign lib { loshelf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, gainDB, shelfSlope, frequency: f64) -> loshelf2_config --- - loshelf2_init :: proc(pConfig: ^loshelf2_config, pFilter: ^loshelf2) -> result --- + loshelf2_get_heap_size :: proc(pConfig: ^loshelf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + loshelf2_init_preallocated :: proc(pConfig: ^loshelf2_config, pHeap: rawptr, pFilter: ^loshelf2) -> result --- + loshelf2_init :: proc(pConfig: ^loshelf2_config, pAllocationCallbacks: ^allocation_callbacks, pFilter: ^loshelf2) -> result --- + loshelf2_uninit :: proc(pFilter: ^loshelf2, pAllocationCallbacks: ^allocation_callbacks) --- loshelf2_reinit :: proc(pConfig: ^loshelf2_config, pFilter: ^loshelf2) -> result --- loshelf2_process_pcm_frames :: proc(pFilter: ^loshelf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- loshelf2_get_latency :: proc(pFilter: ^loshelf2) -> u32 --- @@ -350,7 +416,10 @@ hishelf2 :: struct { foreign lib { hishelf2_config_init :: proc(format: format, channels: u32, sampleRate: u32, gainDB, shelfSlope, frequency: f64) -> hishelf2_config --- - hishelf2_init :: proc(pConfig: ^hishelf2_config, pFilter: ^hishelf2) -> result --- + hishelf2_get_heap_size :: proc(pConfig: ^hishelf2_config, pHeapSizeInBytes: ^c.size_t) -> result --- + hishelf2_init_preallocated :: proc(pConfig: ^hishelf2_config, pHeap: rawptr, pFilter: ^hishelf2) -> result --- + hishelf2_init :: proc(pConfig: ^hishelf2_config, pAllocationCallbacks: ^allocation_callbacks, pFilter: ^hishelf2) -> result --- + hishelf2_uninit :: proc(pFilter: ^hishelf2, pAllocationCallbacks: ^allocation_callbacks) --- hishelf2_reinit :: proc(pConfig: ^hishelf2_config, pFilter: ^hishelf2) -> result --- hishelf2_process_pcm_frames :: proc(pFilter: ^hishelf2, pFramesOut: rawptr, pFramesIn: rawptr, frameCount: u64) -> result --- hishelf2_get_latency :: proc(pFilter: ^hishelf2) -> u32 --- diff --git a/vendor/miniaudio/generation.odin b/vendor/miniaudio/generation.odin index 97b7d453c..305090c7d 100644 --- a/vendor/miniaudio/generation.odin +++ b/vendor/miniaudio/generation.odin @@ -56,14 +56,18 @@ noise :: struct { lcg: lcg, state: struct #raw_union { pink: struct { - bin: [MAX_CHANNELS][16]f64, - accumulation: [MAX_CHANNELS]f64, - counter: [MAX_CHANNELS]u32, + bin: ^[^]f64, + accumulation: [^]f64, + counter: [^]u32, }, brownian: struct { - accumulation: [MAX_CHANNELS]f64, + accumulation: [^]f64, }, }, + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, } @(default_calling_convention="c", link_prefix="ma_") @@ -72,7 +76,7 @@ foreign lib { waveform_init :: proc(pConfig: ^waveform_config, pWaveform: ^waveform) -> result --- waveform_uninit :: proc(pWaveform: ^waveform) --- - waveform_read_pcm_frames :: proc(pWaveform: ^waveform, pFramesOut: rawptr, frameCount: u64) -> u64 --- + waveform_read_pcm_frames :: proc(pWaveform: ^waveform, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- waveform_seek_to_pcm_frame :: proc(pWaveform: ^waveform, frameIndex: u64) -> result --- waveform_set_amplitude :: proc(pWaveform: ^waveform, amplitude: f64) -> result --- waveform_set_frequency :: proc(pWaveform: ^waveform, frequency: f64) -> result --- @@ -81,10 +85,12 @@ foreign lib { noise_config_init :: proc(format: format, channels: u32, type: noise_type, seed: i32, amplitude: f64) -> noise_config --- - noise_init :: proc(pConfig: ^noise_config, pNoise: ^noise) -> result --- - noise_uninit :: proc(pNoise: ^noise) --- - noise_read_pcm_frames :: proc(pNoise: ^noise, pFramesOut: rawptr, frameCount: u64) -> u64 --- - noise_set_amplitude :: proc(pNoise: ^noise, amplitude: f64) -> result --- - noise_set_seed :: proc(pNoise: ^noise, seed: i32) -> result --- - noise_set_type :: proc(pNoise: ^noise, type: noise_type) -> result --- + noise_get_heap_size :: proc(pConfig: ^noise_config, pHeapSizeInBytes: ^c.size_t) -> result --- + noise_init_preallocated :: proc(pConfig: ^noise_config, pHeap: rawptr, pNoise: ^noise) -> result --- + noise_init :: proc(pConfig: ^noise_config, pAllocationCallbacks: ^allocation_callbacks, pNoise: ^noise) -> result --- + noise_uninit :: proc(pNoise: ^noise, pAllocationCallbacks: ^allocation_callbacks) --- + noise_read_pcm_frames :: proc(pNoise: ^noise, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + noise_set_amplitude :: proc(pNoise: ^noise, amplitude: f64) -> result --- + noise_set_seed :: proc(pNoise: ^noise, seed: i32) -> result --- + noise_set_type :: proc(pNoise: ^noise, type: noise_type) -> result --- } diff --git a/vendor/miniaudio/job_queue.odin b/vendor/miniaudio/job_queue.odin new file mode 100644 index 000000000..99899fdbd --- /dev/null +++ b/vendor/miniaudio/job_queue.odin @@ -0,0 +1,239 @@ +package miniaudio + +import c "core:c/libc" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/* +Slot Allocator +-------------- +The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used +as the insertion point for an object. + +Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. + +The slot index is stored in the low 32 bits. The reference counter is stored in the high 32 bits: + + +-----------------+-----------------+ + | 32 Bits | 32 Bits | + +-----------------+-----------------+ + | Reference Count | Slot Index | + +-----------------+-----------------+ +*/ +slot_allocator_config :: struct { + capacity: u32, /* The number of slots to make available. */ +} + +slot_allocator_group :: struct { + bitfield: u32, /*atomic*/ /* Must be used atomically because the allocation and freeing routines need to make copies of this which must never be optimized away by the compiler. */ +} + +slot_allocator :: struct { + pGroups: [^]slot_allocator_group, /* Slots are grouped in chunks of 32. */ + pSlots: [^]u32, /* 32 bits for reference counting for ABA mitigation. */ + count: u32, /* Allocation count. */ + capacity: u32, + + /* Memory management. */ + _ownsHeap: b32, + _pHeap: rawptr, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + slot_allocator_config_init :: proc(capacity: u32) -> slot_allocator_config --- + + slot_allocator_get_heap_size :: proc(pConfig: ^slot_allocator_config, pHeapSizeInBytes: ^c.size_t) -> result --- + slot_allocator_init_preallocated :: proc(pConfig: ^slot_allocator_config, pHeap: rawptr, pAllocator: ^slot_allocator) -> result --- + slot_allocator_init :: proc(pConfig: ^slot_allocator_config, pAllocationCallbacks: ^allocation_callbacks, pAllocator: ^slot_allocator) -> result --- + slot_allocator_uninit :: proc(pAllocator: ^slot_allocator, pAllocationCallbacks: ^allocation_callbacks) --- + slot_allocator_alloc :: proc(pAllocator: ^slot_allocator, pSlot: ^u64) -> result --- + slot_allocator_free :: proc(pAllocator: ^slot_allocator, slot: u64) -> result --- +} + +/* +Callback for processing a job. Each job type will have their own processing callback which will be +called by ma_job_process(). +*/ +job_proc :: proc "c" (pJob: ^job) + +/* When a job type is added here an callback needs to be added go "g_jobVTable" in the implementation section. */ +job_type :: enum c.int { + /* Miscellaneous. */ + QUIT = 0, + CUSTOM, + + /* Resource Manager. */ + RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE, + RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE, + RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE, + RESOURCE_MANAGER_LOAD_DATA_BUFFER, + RESOURCE_MANAGER_FREE_DATA_BUFFER, + RESOURCE_MANAGER_LOAD_DATA_STREAM, + RESOURCE_MANAGER_FREE_DATA_STREAM, + RESOURCE_MANAGER_PAGE_DATA_STREAM, + RESOURCE_MANAGER_SEEK_DATA_STREAM, + + /* Device. */ + DEVICE_AAUDIO_REROUTE, + + /* Count. Must always be last. */ + COUNT, +} + +job :: struct { + toc: struct #raw_union { /* 8 bytes. We encode the job code into the slot allocation data to save space. */ + breakup: struct { + code: u16, /* Job type. */ + slot: u16, /* Index into a ma_slot_allocator. */ + refcount: u32, + }, + allocation: u64, + }, + next: u64, /*atomic*/ /* refcount + slot for the next item. Does not include the job code. */ + order: u32, /* Execution order. Used to create a data dependency and ensure a job is executed in order. Usage is contextual depending on the job type. */ + + data: struct #raw_union { + /* Miscellaneous. */ + custom: struct { + proc_: job_proc, + data0: uintptr, + data1: uintptr, + }, + + /* Resource Manager */ + resourceManager: struct #raw_union { + loadDataBufferNode: struct { + pResourceManager: rawptr /*ma_resource_manager**/, + pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, + pFilePath: cstring, + pFilePathW: [^]c.wchar_t, + flags: u32, /* Resource manager data source flags that were used when initializing the data buffer. */ + pInitNotification: ^async_notification, /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE when decoding. */ + pInitFence: ^fence, /* Released when initialization of the decoder is complete. */ + pDoneFence: ^fence, /* Released if initialization of the decoder fails. Passed through to PAGE_DATA_BUFFER_NODE untouched if init is successful. */ + }, + freeDataBufferNode: struct { + pResourceManager: rawptr /*ma_resource_manager**/, + pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, + pDoneNotification: ^async_notification, + pDoneFence: ^fence, + }, + pageDataBufferNode: struct { + pResourceManager: rawptr /*ma_resource_manager**/, + pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, + pDecoder: rawptr /*ma_decoder**/, + pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. */ + pDoneFence: ^fence, /* Passed through from LOAD_DATA_BUFFER_NODE and released when the data buffer completes decoding or an error occurs. */ + }, + + loadDataBuffer: struct { + pDataBuffer: rawptr /*ma_resource_manager_data_buffer**/, + pInitNotification: ^async_notification, /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. */ + pInitFence: ^fence, /* Released when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + pDoneFence: ^fence, /* Released when the data buffer has been fully decoded. */ + rangeBegInPCMFrames: u64, + rangeEndInPCMFrames: u64, + loopPointBegInPCMFrames: u64, + loopPointEndInPCMFrames: u64, + isLooping: u32, + }, + freeDataBuffer: struct { + pDataBuffer: rawptr /*ma_resource_manager_data_buffer**/, + pDoneNotification: ^async_notification, + pDoneFence: ^fence, + }, + + loadDataStream: struct { + pDataStream: rawptr /*ma_resource_manager_data_stream**/, + pFilePath: cstring, /* Allocated when the job is posted, freed by the job thread after loading. */ + pFilePathW: [^]c.wchar_t, /* ^ As above ^. Only used if pFilePath is NULL. */ + initialSeekPoint: u64, + pInitNotification: ^async_notification, /* Signalled after the first two pages have been decoded and frames can be read from the stream. */ + pInitFence: ^fence, + }, + freeDataStream: struct { + pDataStream: rawptr /*ma_resource_manager_data_stream**/, + pDoneNotification: ^async_notification, + pDoneFence: ^fence, + }, + pageDataStream: struct { + pDataStream: rawptr /*ma_resource_manager_data_stream**/, + pageIndex: u32, /* The index of the page to decode into. */ + }, + seekDataStream: struct { + pDataStream: rawptr /*ma_resource_manager_data_stream**/, + frameIndex: u64, + }, + }, + + /* Device. */ + device: struct #raw_union { + aaudio: struct #raw_union { + reroute: struct { + pDevice: rawptr /*ma_device**/, + deviceType: u32 /*ma_device_type*/, + }, + }, + }, + }, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + job_init :: proc(code: u16) -> job --- + job_process :: proc(pJob: ^job) -> result --- +} + + +/* +When set, ma_job_queue_next() will not wait and no semaphore will be signaled in +ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if nothing is available. + +This flag should always be used for platforms that do not support multithreading. +*/ +job_queue_flags :: enum c.int { + NON_BLOCKING = 0x00000001, +} + +job_queue_config :: struct { + flags: u32, + capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. */ +} + +USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE :: false + +job_queue :: struct { + flags: u32, /* Flags passed in at initialization time. */ + capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */ + head: u64, /*atomic*/ /* The first item in the list. Required for removing from the top of the list. */ + tail: u64, /*atomic*/ /* The last item in the list. Required for appending to the end of the list. */ + sem: (struct {} when NO_THREADING else semaphore), /* Only used when MA_JOB_QUEUE_FLAG_NON_BLOCKING is unset. */ + allocator: slot_allocator, + pJobs: [^]job, + lock: (struct {} when USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE else spinlock), + + /* Memory management. */ + _pHeap: rawptr, + _ownsHeap: b32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + job_queue_config_init :: proc(flags, capacity: u32) -> job_queue_config --- + + job_queue_get_heap_size :: proc(pConfig: ^job_queue_config, pHeapSizeInBytes: ^c.size_t) -> result --- + job_queue_init_preallocated :: proc(pConfig: ^job_queue_config, pHeap: rawptr, pQueue: ^job_queue) -> result --- + job_queue_init :: proc(pConfig: ^job_queue_config, pAllocationCallbacks: ^allocation_callbacks, pQueue: ^job_queue) -> result --- + job_queue_uninit :: proc(pQueue: ^job_queue, pAllocationCallbacks: ^allocation_callbacks) --- + job_queue_post :: proc(pQueue: ^job_queue, pJob: ^job) -> result --- + job_queue_next :: proc(pQueue: ^job_queue, pJob: ^job) -> result --- /* Returns MA_CANCELLED if the next job is a quit job. */ +} diff --git a/vendor/miniaudio/lib/miniaudio.lib b/vendor/miniaudio/lib/miniaudio.lib index 3d7c8327f6b3432b3c9fc8054061d0b9dc5c07c8..400cb9608cb9090e57467eb2fc0beb292eb2f139 100644 GIT binary patch literal 2767136 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%0AGJr$_2h{ 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πp`DD)%875Ofd>q)r2!$O_4Tpb0aGENE+`Gq`>L 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[IF2f{_X zp&~(Wky@xo09<4xRKy!D@*FDS0~b+dg_!IG7m0+5IKoBxpdwCik%LeX2e`;PsE9jU zM1>7vvIkrw0xIGL7ioivxWGkrK}B5QB5$A~&TtV4c8JM#aFH;mh&^0n5>&(%E^-1Y zavGj8UqD4p!9_SYASR!Li)cVamcY%ifQn3qi?~5Wrolxbpdzc`A{kJTWpI%ysK`>d z$V8~fD!9lhsK^Sq$RVi6a=6G1sK`pV$SbHw4qSwT6XLIIxQGf=BnvKL2NlVLi$p_3 zGT6;3D6kBGqsaJ}!taD&Zo! zP?2W1h#OR-2`&-`6{&!WR6s@Q;Ub+-kzBaQ9H>Y>Tx1PYq!cc)7b;Q=7dZnJDS?aJ zg^HBJMLt1A^57!u+z@}2!9^sYB8_knL#RjtT*L<|QUDi;fr=c5i)2AXPQgX0p(1DD zBE3+N({PblP?0lmk@ZlKlW>tkP>~aGkt$@iQK(24T*L?} zG8rxs02OJ2i)2GZX2M0LLq)pbBFms6eQ=SjP?3JP$Z@F19Jt6^sK{KnhyX9d&9mVm z+E9^Ma1m#y$a=U)I8>wuE|LosnE)4QhKlTgi_C_K%!i9Chl(77i)?|4^uk3>LPaLR zMeadG_QFNpLq&GNML78&{@M)}5rv8zf{PeKMfSl(5}_go;392Mk$G^DB~X!raFG*G zkruefC#Xm}Tttu`;+_t$NNQ10KJMO3>B#Zixd}@CKf?&&TupdxeOBB!7tv*9Aopd!g&yFj|WLPhq&O=cB#n~(ED8&7)tCQy+*V3Ev{)S`G(6Oe}) z4ueHNOkb#)y>O8nsK`OMNF`KcJ6I$oKRzudA5u`PguAB&swNfg=2=jYbhyYCs7M-I zi`3e;|1hyUIW?o^4-(BEpq@f}ia1l+YNH$!=6e^Me7jc4$@~s{abKp@7 zRs}K#si1}G%1zBpE`Z;f0aBBjnwy(n1}&h$XL{$R<`$;M zn20gVg>PA*u>*I{XPAgF)GCH=a1mIDeSnFW!+h}xCIZvM@DV0r2(!zR8ETg)%%>hO z5kr`^5SWM=Oji(01a1yD8@f4MFcG*p0x%JHisFZfn8AF(2NN-Z*(CxOfrX(sOa$&` zF_;KEoMqr5FkeW+L|_68iZBs)d?~|3OksBE!9)x#pib0=i@;ns85XK=_e_F`!2Lc0 zCIa{SbeIU-@6%u+aKF!ni@@A74<-V)Yc5O#?)SxT5tvsO!9-vJ49j66aKEpFiNO87 z2_|9)OH%7$B5)T*z*2%SGVs=dltY%jG%=LgDpEWg&V_iiUUl<0w!V) z6M>uT3>SeFQcf@txC?*5Le&`N=D#ozQ<$6oz(n9CGr(O4bMt?g8o0^LFcG-PZ7>nI z$*nLExXGO`5hIw%9dHp?XmqhaLjzVAGt7XA7{O|XDHtNDFmvE(J_RNM&nek35lfgk zX)qCESlZYJ^EA(2TkqE&@}t5iSB#GY2jLGiWwU1fIeVz(tIq zk-7;c0?#`$U?MQ97_P!x2#aKfTQCuL$$t|jVh(jO!%LV5y!N~Y6M+Q=!!wu&EL$+V zfQi8Ex(pM6=agJnI)vBVIWQ4;om&JKf#*TE2rSJPz(n9BTOmvY?)O?)J~f7A=0>;( z%%=@75qL`IfQgvGQe6{F1RfW47$WsB5xDIieFbH9Yh`>bPrGz0YzThI}FcD+0#~2tG^k5=}1|SD9 zFfj1KMBs731s8$oVup#Bfj!T_z#xSoA`BCOmp=k<5tv=-7$OQV5xARWVIqd$AY))) zcmOXYV9mH22oY#A?iO4GmJaX2M2ujC%W;^9Ijlr#g@vIRI0_jU7&gQ5mm$ojvtjAb z2qrQeE&?-WIa~x5VoPBn@H{vbCIYjH;R8$r-tzni6ET4)iiL#+JSD`yMBpYT!9?I~ zfFKN!V7Lg(g^4f`Q3vdu|vxZb5N+3Ad7(OWqSsQ8gRJ*4j*?E5s>Zv$RgnSG#o_)4Fde#TV#u1cV4EzCeei zBSb)M1|2dC7XjCC70eKyf=f}5+6wD0P z$Z9~r%;1bH0#45$_k_aS1GXI`QV8<}SQkjd5f&P75et|ac!=4-MBr+yU?T9=y9ZnZ z6sI7QyY_QA9x2dov@O3<@7VWD!G{=RxiP+YXQS z7ce)2-2?LJZny|6cCNxjK<)vlISCg5*{;LD!0;I6Q*dfggo=Re0{7_Ekww7y3uMkZ zSQwgu6)`X{?1zbfTRR{%M`0pHu*Uimm^=z{+sA$TFA+e9m_>EbqWYHo!#Sy}ykx z5palsjNSwjfzL#&hl!YjlL7++!)llaI8;Hpm@wST3=@I(cYeZjfkOkNiw7nG%Ww<~ zFkSH4;Rj3&+!tSAB5*fE)Lin2Ru~S zVIuJH1ap`R;VlPExCqQ-b(jczBv1n`0!kYoyR=~Oz*;C5|>r5|{Ia1BfZR(&z7gNeY3d4|<+5m?A? zhl#*Leg{kh9?s0LP=%+2@9;Vo>;(n}hEP~&z~dquCW06%fQgubN(qq8WSEGN1vFSw zVIsyB(Cm~36ETPBDujtx!curHOavaz`EU`aF$_^K5qJzn!$jcqMFfV3D_jH?r!E*G zZg3H}=V9#xGgy$ZV2H57MPUA7!VqDGiNMF=*kK~z5(yNBQt;S;+0_e+L3nN60~3L_ znr6U6%wW;n2NN*_cil;W);ID43Wn$5qMzSfr%KxlK4HC2wcs5 zmiNN#e5tsfQhofrr>)m^9<1i7p zt`jg3c;4}X)gYEIAyD4{+_r%w1O^|N8h9A`!bD)27*t`tuz;0^YA_La`K$pGf$Lfg zE4ARd*1$yIy4Jx&;JVUbw!=qdGhia{8YBxQ0#84SV79|`EryA}V{i#f1l(c-rzY-j*2HJM-{@OBh4OayKY3rqxVP8TeE z;O2D0MBwK1z(nBY^uk17#XLhdEJeZlu>CL*c$;l9Oaz|lRAJ^A!Lp?WOaz`<)L|lq zuwp_NCSqy$eUt_n88HgC5Z`41l}Stf{DPYF9vRy2)yklfgvJ} zAtH$(A_W(L8EpU;fkjUeEXTp~UoW48Y9+$5$+h z8jz!tQA9wtXQGIJTHTES`OgR70^}buVMKVoKsR57#QFpASW}ZLPfy-0!1K$2C@jq$qahP zB4E2fT0!Q3b%6qv!3tRoxL$TZ5dnp&8;S@hRDDoHKrtAEA_B4}0!0KAs&ObHAYC4y z5myfA_%lcogB(-@>_U(x2GD5OV^~=S_9>{0f{TFt1uC`SB4B@k6v9Qo{<1+f8QjZu zLJ{2fR{c5hFwd>>hB-3N(iZQUehIIr;*M8jus8Ba47X z9si+-fMQt`Y8TiX@VJB&vIxi+aO(XCYgK|n6(sZ>CIVOU872ZQ>p+*?zk!K>R~Fr2 zWMBY^fak=)&9M#0B4!}>fR363sR6qO+_M86)`t)QSq3`b3oZh#b3uoDz(v5d-W?`n zyFlSQ2U!H%ascIJxGr#x1Lb9e2*@tb%r#sDTwA$A-3)dixS8pTA_B527)1n>#z1q~ z2y;Lt$0Mr&*WIZoBA^_XjVuBlUj@x>!_5KrFUpbCfZGYRC?X*D%s>$VnY;u=1Qeej z{2Z3!zU7)C70EGx#1f0S_3K1e8f7v0M z1NJ*;oFAbEl)Jr9)PO=BH1dy719Eg2iW-okV^Bmuj!r@m0l6guMFiw%(9Q_B$>5X; z+Dw5E0lB#fSr<61HXw_D$8A7|im!l$D#&2)+%ZT596sRC0F{{t5s=BA3=9ki5s=?O zK1GOt{0?fxAw)oa2h{}#5s=?OJ3pi3~HevL_lr^U8D#X0r&COFfcF_ z!)9|pni#-7fwc-inixRo2c!lZ&LC; z=^~4m7#J}yl<+Vx9DuDUH3BVIkLG}gfK4_6O@)@AidZr*bfAcU8e8*FMT{62wxEg_ zGcfE%6)|C8xQHrZ%D@0B=RocO+YauVgZ%au*7^c@f&o;vfJDG*!0{!^z`y_(0VPB5 z+6=e|IKDKI)qrCMG=B3RU?SkY9!P`@rVFm- z8B7gajkKlpIL%14HR%QTQ_KFYzg&*i@MYxCwXhIIu zA^?ei{cZ^=X+gbFkOPTnjQ8>}Eqy^D6^c4S0=fE3$|os8QRC zEMfxE#lQ-255yeMV(aCoYD^gzKqG|+yFi1~pjCn(5pejJfTq?!V~`*busPro=`*T1 z#taN0$aWclIx5^u5Zl4#7=fx!WmFL}28I}95ff0ypaR7lP+~1ZRb$A&Fabpks2eZ= zRgD1y18CeG?q+bAxe7%MDB?jYl;CQNKs(g-A*%tGT2GNhj6f41Z;?ezKtq9`y@LpI zKobw3^>-iw5}T@0uBxE zoE_*|6SxR?d<=9c20{cBs-Th$Ap&Z%fv!S8h=59d&{6*g5m5F9U5YUm78l@f2CYW| ziGXbfWo7Wl30wp`o&d5BE&{GSL8ik+z$*kmEqu5LcuEsg6TwBm{sN8dz(v4gr=S=_ zh=9TebT|ZD1XQpwfYJ|01RP@Ex#Je_4cgFMY~Wf3vva2Aoq0kww5E54su{qzfDu;Bll@WHq4TkO9{C!l0v7@2 zFHpZ5E&@)OjtmS8a1n3{2lcz*BH)w>YBj+{z_ASKX~IRo;SB0i!$rVp6*P_n5&?$~ zIK)6BfglmEFTiOPbR-c-1gr*J6M;r#2HKq(W{)p^fKnz%1nfd^T!2z0Tm&2!pp*$0 z0mlU>Wx_?k;S6#&Tm&2!Aa}z>zI35wLr}aRD0Z zhKqpX0+cV{BH*|H0xklM3sAm*i-7YEC||%uz;OY}7jO}9T!8WgTm&2! zpnL%r0mlU>Uw}lwAqI{MP`&_(fPDdu3sAlQiGbCB;{uc~Kq6o@;J5(g3%Ce8Uw}j) zx!T=Wmxd$Y|2onL1y@NzN zn4ql!a9n^yyf8$PU?SkW15%TWA(9Fg0ky0^YSQ2$Fc+r7ML;P5q-G&Z1e`KKB1>T+ z;5rv1G8HBQZm)twD&Zn9bDH5IpzaL^D+5Cn6SS2Aj%Cp7BuE4jgCJcynILK)BA_zL zgaugylnbsQix`1M7(lke%>m~xkjQ+P?I2@70t}#?nP8LQvAhzd2AozwidMr!3_(i- zK(m*iUKu#m!Dm=NB4BgCv9p_rfdL@`Dn(^kkVQcG6tvd?uEq#FjKzSY2GkJ;S}w7bXJNWepbr<#&)SJD3P~R1G9j4HJQ%`OpXxfv4x0FcEl)(!dbO zfQi6up9~X$k2QJ2L_k9hAj=tiVIpuf{xA`^8b1t?01Oe(-Zn_cgHj8~=peWnm`EsG z1e97pYQo?mFg2hZaj51*z}3KXg~LR^eLavlpq=~>U7$DxiGW52P(@;4x|D)qwWnfz5%(dk#zuxQ_->lnoPstI36lz{{Tkm>E6MKx3oe(144C!qk9k zYLLm{a1oF>pdNGtY+etX!a=K9Kq6psz$q${fq?-o0@B0)nzMt8fWvtfvKr9vD#$8? zE?9IiM8a%``zsJ80&Z1;#x6iB;lMRD*e=kXVvq>fJz%>)EqsIsDE*W%FfbrQK(*&G zWD$7DW(k`s0gu;#oM;6Tu>^abfq}sqCIVMu3lo9YxehQ9uunn#Q#&?jiwJBCXk`&d z1ng#T7=qUO!9~FS0`{y*yiT!zi-7zN3d39msC&S6 zfx-|Z0(K8P3=txrFa-5e5F#+6&%#>5@b=+3mmB z5pV#3cE`X(z%B#@#~HW?EL$?dQVTpzSHWi1;3?c0w#owL35I02IiM`(z{0?AixC<= z;2I6oKLv?^!v}2DTSkZ)un0IVKqnZ%)qtHW12qS%#?aWnfZ>BG0|V%c0C4IxG&V3~ zxPmMK?tg)jI~FOAd%Gun4%!4CF!<0maTYUSttaEUytn76EzVofwjcA!z7d zTLMW0yp9O8dUhJD^$uzpGk{jl!bL#FFo0I3fkeRW0s9mbK5!9miURdR;3D9XA0&(r z0Rnj5h9@20nJw;L_q!mt)4}QfcynoJ&Ol_#v z7(i|UmkA)Nz~^0nM8NqAWEBHwTnjD&iV6l$NemYOS!T5dp) zYULqBK;~RQQ3DG4uP7p*pci98HU|{+<|rbdpa=CS5hjED4jLnYi-1c}0Tzfk;4lQY z9Bv_-1I`yeP((nXA;}EU1@;Aa#MKH#1mp|QIh}C3z@w=(C~82y0L|ne)PQ`k4_OVk zF@tYWBTU|@iY zfDC46LlyxgRR+-7Ik*~dP63UkB1Ay3vk+MqcrF_>a*I#{3LnsH1wsVmXi$q9Ap&yr zQ517Pjy{JX0&+BHMirq8@rDMAF~XwcbNa1n4_3A&IGAp&wU=z1@N2&nV}UCM(H z0l67;Spz}@$fZPn~@gsDB+zjeHBSb)MwnEVbabWgb2u|pbM=KA|OYD#!(O=AV=4mJ*AU7{X5dpaw z)aOQ+3<_w_SOP)>LWyq7#Kijf+6WL zW?+ayRb#@y02=E>Qe(=%0ABNtFvpC6VK0g<&?Fh?vc1+XzNfO=6zOc)qIqpe6H zrVI={sOFe4Fswxp0ae}w$Swqj;YAiC+reS@9z_Hch61d}YCvIVh#~?C!ypt9P#A*N zHN#y93Lb_j$ZEi0xD{E%5VVX*g`I(6KdeOr-uY|G1QCITk2wPaXq+3Q1}b8~z)*v# z#*%>nGz)=H11bVQE7Oof3>g^iqv!&6@|ltC0u5Gz)|er5ftu!^-QFM(a4ds;0U9v^ ziGbY$ZtZ~1ctePQawX`rb%Y40B-z1=WEXfj2Nc)F))DE!GK+82-=u*mkA;QcA+6?dJ=R37)T9N#GHWvbP^kqhy??KEi;NP zO9qBG6cJFD9(394}Bx1zC09ply5V0^bWY~i2LeR272GD8!a5dl_2k2}7 zgb1jt1D(Nu5CMg178{Za!R^&<6cJEa2RiKrp$inMXHnFELiHW8h@pj<0mBt!7lO~6 z(dI<5%Lvr;1l=_OH`xeeeHN-3QwD}^R1q@DsG3=WDq_gMa28d>h=Ji9s)#WI13xda zU7%{l09nM)!qkF6#2=ytoDNMuoyKf_hzK~vs3K+z4F1R>h8Cvg3?|4fGzJ|QH%$o19AnV3=`E-tW(*8hQANxd z7`~y3STHb%3!|80$-rQNA_7YEk*Feu3=CDsB8C>GW(*<7E;I$LE?y&wWU?t}kH#rf z5eo)}*Qg?v3=BMC$httb>!OMnGBEg|iWo64OAp3{}LKfq`8TMU4pqg9fUIDFcHis)!i_Ll&xtIRisCs)z*x!y04}Lkm+AhAGG{ zGy^pmd8CnSHv=sekw6gv)feihB8ChMW~d@Y3=FQQBA`{=s3Il|49Tb>rVI>4s3K+z z42`HF<_ruIP(>^l80MpjSTZoILlFVxroE^lh71g6P(_Rw7;dAA7&9=uLKQJ#VEBnD zV#>h4A%h%Zpc+IRRm7ZuK@C;Jf`P#lRm75k!39MGRD%SgiWo95B%z8JF)$ROiWoC6 zG@y!@FfjC^ikLDm%tIA1V_;Z|Dq_ySum@Gdf`Q>Qs)!{6!!2YHLkm-529W?rXn;#L zGf=b1NfsgkF4@dL(`J6CBIXPXv8W;z3=BD_B9;sc)hHsMW>Xich#>>ROjHpg28I== zBE}31+fhYK7#NPBikLDmTtyWzV_)yQ1H(~N5hDhME2tvI3=EG^MNAkNKB0=3GB7a8BZnAh3!eb8h@pk45yKRe z6b0I_SAeX>%+i>Fp$1jNgn^+ORm7BmVHT>083V&gR1tFqh8?IP77Pr>QAI2n7_K3U zn1k*?c#0}w$iVOgRm6yafms2`r{Z6|rPsU{XX5F;KlDh$>>pz@UICV#L6pk1Ar!z+j6kVrXG%$RH93 zF&SJ&nS)wi^N`h;gX)V_s3L|83_DRpj2IYBpo$nXFkD9!F=1eMhALvp!0;7S#EgM~ zMG4{_a0zJ+nlcnd6|rDoP(l^4WMD8v5dqZ~4yYoA3=ICLB1Q}hai}833=FxbA|?zB zHK-z>wmPba83V&CR1tFqhLxxy77PqKP(>^m7>=WefF^XWp^6wXFg!&SF=Al&f+}Ln zz`(4G9AcpEx)7>}DFcHds)!i_g8{0DIRk?|s)z*xgCDAhB?ChYiU_FQ$wn42v@kVb zn1Y<5EI_^WTva3&T7Zt7>P8hYW?)!@Dq_OGa28d>l!4(Ls)!i_1HT%I$>t0U2B;zy z3=DzDB8CMJyQ@exZnfa;3B;vN@nut_`Y) z5d%Xks)#WILmje+p@oSB!xdx~8X6c{FznVwGTG3;(30UMs)&&R!!I-uLk4Lb6kSF} z3^r&Y#tgA&A|?!VXd;U$`gIRlp&vh5ZoCJZ4cE;KeYVz9A5 zR%2{v%n*epV!}|3CSuAk2TjC`VK179Im10P5etU@Xd;#j3YN&WTbLL#l%TlK$kd!6 z!WvnPk*Nhk8JdVC!*o;;GXsVlXd;FTH_=3l7=EFN7&A!Qpx9+*!eE0UVqs#$Fa^bh zpk8#MH4Y&SdS)R&ae|r#Dd{4nusOCSyT}-1BUBpB8ChP(L{_GUZaT^ zGkir8F=6|hFmleGlp_B5p#w{G!YAiZZr`~hN-9`76uIS(L@XxR-%a*F>FQ? zF=p6{CSt;H98JWO;UbENg^3}9NH8SiAtjr!r6GfrJwyajvKd<%F*u=#7&G{wiI^~i zp^2C>B%q0yF=U~Mm@|~1iC8eyp@~>BbfAcs7#J{2LK87$n1d!_#IOua#F$|NnurO* zE;JESh9hVqW(?=hM9dj(pov&8JVFz(WO#!rVrand4Nb(5fx!VeoJ|ak7`V_xj2T4G zL`)du&_qlbG|)uM7>v+F%o%LZL@XHG&_pa50#HSa3>c!&L<|{H&_s+F@=!!9Obi&N zprj}hBMXKTC~82%vKLTAj13rWp@|qWJV6sNVt9upV$ARZO~izO$q^+~jZGPN&_v7_ z#Lz^{85Gb&EEu%VL@XIhP(@4(80^qQ3>iGoM2r}M&_s+GV$eiP7}C&0Oc@H$M9dhf z&_v7`TF^u+82ZpeEE#5?ikKQOEJ718WLSeHV#KfwO~jbt0Gfyi!znZoQ-&*OB4!Nt z&_v7`UZ9CsFnmH2v1Is-B4S}|$siH}iBm`!WnyN^5at9Cfs|1uW@Zd=Xd>ne8E7IF z3`J-nmJBthBIX7RZD=Bf3=_~qj2LF2i5N31K@%}yScfKJ%CG}X#Ejt(nus~W88i_K zhHGddmJAP2MJx;$UZIH?GJHW3F=F_KCSuIM;fxZ77A6crXdj_yFf$Im(fJb86KgDSTKA<6R~7q@J2Dm%z%L(O~jBv4o$>} zK^INLn85~3#Du{cO~jNT3Qfd}Arno+oS_0u#Dbw6O~jI68mfr70mD)>5krP8Xd*@o zhtWig87`rTm@qs<6ES7@fF@$b@E=XYoPo~=B@E3i7-Z2zEE#lAMJx;$tkFaa8NAR$ zj2I%(M2s0S&_qla%F#qj8QRc9%owJkh*%h#GK7Re+zct_%`A-={-CG<4N0^3LexM? zeltrG20k_8JSVmO2*V$5&` zO~i!Z8k&eH!vi!CGlo}aBIXQV&_pa4{-KFjGH^iG6+q(D+{l1I2u;M0K?Y64h(Qfa z#F)VVS;W%7f`K7Ioq^#Xd~FYCK|ko;Gmr?xF3{HK+bj?@P!SUbhCiqxrVI?StSD;C z7#Kje8X}ov&cFb=;}J>3f`Op{RhK0L!$M>cOVEjA7050G@0_~Ij${|;U@8XC>A7%| z!S@P@a3HAx-zQ~?Dq_gM5QZvZ#K2IFDq_sQ0J^gsZZi1phMmZ2EJ0^p%|Ldc5y<*S zTu8QqHtR4jaHEJAGB7BjiWo64IH8IdGcY8hikL7kw4jQZGB7Mf6)|IAIEE}@X<*90 zumi<~pbcQpc#+&=1UfE(g%3r zFf2wDF=t>nf+}Lc!0;4V#1eGApM(a)J>c5G6f`?2Ee;U@=Uh`u69xuLR1s4KhA31K zGX{ogR1tFqhB>Gr77Pq~QAI2n815m9n1N;||09c78W=J#SfIENw5``p3fYCAEow0+ zA|?h547I2th71hzP(_Rw81|!z7&9Ba2vo%9aXb z7lJB$h5}_IU7!k|p$}EWl!0MAs)!i_!v$0ka|VWws3H~&3_>a>CR;Kv7$b{-Dtv|z zWDyHcaXbUrh31w93=H0?NamP>&KrzF6)|F9$U+q{W?(2o6)|C8XhIb+Wnkz-6#>l; zqKcR^Ff2nAv0z}>gen3W=SCK>0HuUes3L|84A)Raj2IXmp^6woX3gOBg$1Z&`-Q5; zl!1Xw4JkA%KsAUEs)#uQgB+@e1p|W?s)!{6gBgm5p#cMf6RHSk{TQl<5d%XMs)#WI zLmH}x2?IkBs)#8ALmjG!83RKXs)#uQ!!%S83kHTos3M^CQ79rt1`G_lP(=(G7>*%} zfOF*obq0n*h*pX@0|V%mSC9y}WCO33|IGqX0~N7kV31)&7BMqmV6a6MF=SwfLlrS% zV5mnGF=k*`fGT3bz;F;*1e_~Dmq~!!11@{Pr>*{DN46bQgUE28h?p=i*rJMDq_LFa1d3*l7Zn7vIsa=Dri7#2fNT1lrjamAtGSAj6v-L zeN+)M28IAs5pxEHLR1k828KzfB9;scn^8nSDf0@dh#>>RS7Z?jP{Ymv*@Z@+l2()t z$%RIslGYei#E^j@1XaX{fuR&t#F&9$8mb6r^$4nnDFed|R1q@mS_;Z=07V3}E8z;V2&iGj@DfD? z6oLOxL_m8Ygk+ISwgA=L0-6vJa7khgI{Q#=u~RDq_yS5QHjX!N5?AEMfs_Zx|rE&WQNdQ_0@0_PM| z5mN?+?WiJV3=HQ`Ma&r(o}!9aFfjZ<6|rPs5LQL93*6e#LKQJ&U~ohgF=AkdKov1& zV8}-mF=1e6LlrS)V3><4V#dI*1y#hHf#Ec&hy|o&1+ND!KzrYQqNoAYDFSN9z5wlg zQ%4m6t!PCRF=k*0MHMk&V8}rgF=b$AMil|IDp5tu85lOAidZl(oIn+^WMH_DA_8jd zd_xs6WMJThwr#+9#{#r6UjRm6mWAqZK-5VT98g^hs$bh{5YSAutG{bhuR zfK4_8t+SG5LJ_fGV6Z_Iv1DKXokj*S2ciq)<~kHLpmkQDbMlbX7%?y$KviSR!0-@7 z#N3#H0d#LCNEg_J;9XijSdiQU-lZkRiYx-!PXxMQ5~K_49?(K3@JT-i5zv03N)%n7 z{Y0}+L_qtAcB6=Z%ELRzB8H&40Cd{}==2J(3qf1W7`zx67(gOm+l@fwA?VC7gb2uX z(0O185s>Z0D7rwlH=&4tY@diC0@|Io09nM?0JLu#MFh0N5ws&7ZWnkT{aF+>pb!Jy z6@^d(a`S5xH6Sv44|E)2y;MgR!30-a7jrh=9U*9g1BbH-mPqBGiD~ zd;m0Q1TqJl4#Bx#GKv~d_<&BMN2mdX&r%dM;P62a0for`pg4Vtq6_5aZzv)lH~&Ww0l67; zCnv%jkedZrkV4)ZQAQB~xmgEA1mtE@6cJE3+o6bn-0Y4b0&;TziU`Qf zktiY{Hz%QpfZUvoA_8)A39<;N*}_nZA_8)A8?p#^-)IIK1H(30Ul@GO+jJ&~2smwk z&!O6aA_B7g3W^BG_AkgH;9MZWjARa|S;t_4A_5Ar5EKzmh=Fcm1i1%nyCJAC+JWps zP}Rh699b7=10OhTz;&5}!jOdp$#&2dItCRK5s)ujP((n!NI@0>$9oHk2q+9ecca1W z0^d*%y4wPDdIdOFg3BKZCI$wO2-p{(20jDmOi+XfDBeM*S0Y3}ZU&ubix2_D`&JZl zKyJQ@A_8*rS7Z@z$cr)~*=`8h7a+g}5dpi<9OR2sWHq2=-3(1ABA~bco!Ja`4>;rx zp{N1*;t`4n$QMj35WB#B2Zy{8iU`OTPRJt0po&n3lY!wWEFBty4)Oq<8VDBwH5?e& z86j%GCWDkP97hoWg~kjfBsHKRVFpk>1(^fZ1sbMhn8Avq1~etbaF-of1QhZ@97rPI zk`{E<8QdJu=3oZUiDb#J)7U_pgTW_~!9_sX9eg4gNCa#<$mw^0fNTflID`lUR?Ap-IS zD96D?;5iN<0Q&96|)-Q&5gWh=5E6x4N#6lh=5!O%5iWJc#eaM zfJgP8b2Bic!_MzD1kE9evN14Zf<^QyQW8rNL8qNEFcfeyFffCru)*AuvXYhgoUC9r z149JpQXvMY8V0=rgswucE*@~b&%m%~rQR)st}Li7uo?zryNbZNIH0Cy?>H>$0k^9f zstc?J#jawoE|^{O>b7MdbWMTk0;@r>s|2hIX4m2sH^2D9?OFrX1y+M%S1DK*%r0&J z#9)u7l_2G%9RzyLZVi=oHzpK~nSu8&Y%U^OUqm4kJ`>{@($0XIUIkSHTK zl)!3G?5cq40x3{B*T2IHZkG{M7g!C7U6pWM%nS?+?d8Qu2wl-oU0^jRc2$9Oi9lmn zp-iR&p{p6H3#H@1lv8xHJ z3zQB)vCR8zLm)!e5vVS(8Wg*l!Mb2}l^i$Tj?nc2stc?J#jX~xE|^^p1orhKbn$?S zJ_ZH`uo@J*TEV(tcHQGjDMRQofa(IPL9we1t_u{5PE~J`z2RXP0@VdpgJM@ZTo))b z9tWp;B6O8Pb%E8O*wq2n1@o8v6Dt>luBlL6U^OUqb%J%l{3R}|E`!ju9jXhg2F0!} zur8QgmzYduA#~k@>H@1lu?uu=5i=vqu48izVi3B1Lv?}GpxD&|w+mFV)YkuxiGhcq zEa*BX1_lPO8Wg*F!Mb2}MLw2$gV1FI)dg0AVpktn7bt&$?9wb*el8krR}@qiSPhC@ z{a{@%yBvRcPDbdehUx;VL9uHBSQo6^xUls_IYQTLs4lP?6uTyZb;0a98P9$mp=&Qx z7g!C7U6a7NV0Nu&ZJvYBbswq=tOmud$zWYDyB-SvOGfBolmvwk0|QtMid|E{xK46rVkzt#%KX(4p2hUx;VL9uHlSQpGLGf4+cgs!tt zU0^jRcFh9o0^QRF3gwQt7V$`UI(!e+1y+M%*KDvZm|e?uRInp-@koJ8VqgHPL9uHN zSQjWSgH$x}JkyVY+ob{31y+M%*IckJm|gc?6>1@L`9gJp)u7ll53CCmU!d}E-j&jf zc(`4KP+ed(D0a;U>w?*JXWm{mgsy(5F0dLDyB2_Tf#M5f*Ogs5HF0pe)L3M%EpxCtntPA8XkX?Z+5pxi_jze{U z)u7n5608em*KXa8PY7LKpt`_nQ0!U-)&)u%AiDzoJ+(yWQUo3P4Z5icqz1*V)gTdg z{l#!TN(`aP52_2S2E{JW*?i25FuSxQ3*R7gRYP@w)u7n57R4_2Wxlr&x>i7Sfz_bc zwGONcIUOd!)5cY(F0dLDyViqs!NO3o{N_4@E=Ew-4>XepQiEdG29O9iG(h<)D0k(O z0JvQ`P+ed(D0Xdx>jH(mWUTI4gf4%mF0dLDyEcJ!!O{lr%=BD@u0p6Tuo@J*HiLD+ z{B=a&>r#ZSsZd>DH7Is%0qcUw?*3`6Dj~q3bSG7g!C7 zUE9FAKz4z`@M~YnlOT8)GJ*~zW?*0dt3k1AJ6IRYt_^-C_aJl`Ky`uDpxCtotP7Nv zL3XWNw=OymZdV*s7g!C7T|2?LV0QJ)4CF)TYJ} zt3k1AH&_?UF4cRnI}p0AKy`uDpxCtstPA8XP>Q{@BE=Nm$o8B`Zo4T@d+z`9^|#pkuWLg>nX>H@1lv1>nA7tF4FzEUQHu31oBU^OUq z9RTZsl^Z8bxEK+-&Omj6)u7mQ5UdMkm*MBWc!aKhP+ed(D0UqJ>jK#YN*i`A7MY>& z_%Z-ppUuF)09J!y*I}?Om|e$iUNS@I3We$dt3k2r2v`@Wy#b0ZFU0^jR zb{z%lg4v}aanc>3Yd%yLSPhC@$H2Nk{sP&hW!&12&~+553#aVA37HK;DI z8Wg)g=g%VgdM$G{7bA3WC_zF4tOmudb6~qb{sN`Lihw)I!r@_P1=R&sgJRctur8Rt zK;vu(UGY#|U^OUqT>$F>*#!#2Khd8<5xSb8y1;5s?79fn1+y#Ta-I}I*Gi}^uo@J* zE`fEy!mwz{!;1)A=b^g5YEbOD4Aup+>*Gtin+RQBp}N3oQ0%$_)&*+=T(msO|CSXt zE-DQgwq;;o0INZ<>nd0m%&v>}AAcZp=|gpa)u7mQ4Xg_mhEgh0GZDI+pt`_nQ0%%6 z)&;Zc(Q-9!gsyO?F0dLDyKaDW!R*>ra`G-hR}NGcSPhC@H^I7Kb`|XwJ&MrP2-O8v zgJRb$ur5%1fzsjkpremI!^3bIR2Ntcie0zCx?pzIR$4wo=-Lj|1y+M%*B!7fn7`8Q zy01a#x(?L^R)b>KU9c{gUFXd6f)ToYLUn=FpxAW}tP5t>sop6s5W1vQAh{B(2F0%X zU|le~e&~NLLg=!E>H@1lvFia?7bpxt`D^0jym*AJc&IM08Wg)8f_1^{dMVd49igic zstc?J#jZziU7+}CkYj!K4IW>Mp}N3oQ0#gP*9CIn*3_Tn2wlgZy1;5s?0N#$1q;K6 zaZf7|x?Vtafz_bc^%SfN=C6fP-f{?CJgSh82dhD`>ls)V%r0L(Gh2i%1E?;r8Wg*p zgLT2|N>Y8-fY22J)dg0AV%H0>E>L`d!tjPT>x2*RFsy*;0;@r>>m^th%r1sHnI#Ba zJy2a>H7Ity0_%dcqgXos%|hs!57h-$gJRcfur8Qg*Y2s^Md;cB)dg0AV%Hn6E?5}$ zeYsPN&~*~33#8EHIVn(zLbdBNR2Ntcie2wf z>^f|d`WRsshZ-bpfYqSb^#QC46ki~_-c0e9Md*@+>H@1lvFjsP7tCLtGpAia=rV%p z0;@r>>l0WPD84|hk*%Nj>JvO|xI=Y;)u7n*8LSItSMee9rwCm!P+ed(D0Y1T>jI5^ zf&8T((^P@bRRGlmR)b>KSFkRaUB8}gnTF8S2Gs>tgJRb=ur62_&euqYLFk$T)dg0A zV%K-DE|^`-i~7S6x;8;|fz_bc^#iO6<}Ygtr+S306Hr}XH7Iud1nYv?^=tBtB?w*j zpt`_nQ0)2z)`i_)U!c0cYEbO@4b}y-%g(dg6=4^bIw!?D_-N1#55oZsR!e z5uP>_pt`_nQ0)2()&;W*G{=F^WeU{=R)b>KKd>&4zd&VFV)iFBPi9!##uKUwtOmud z|6pA(yS|)`P)6v=h3W#UL9vU03pT$DN{5p~?k+~?ngG=WR)b;}BUl&AuFuC;c_DOd zh3W#UL9vSotPA8XkX;gr{Id|cZb5Z{)u7nL4Aup+>yOT+a)hq`P+ed(D0Z=cb%Dx5 zP#89D64UX3hoK7S;wlCP2Cy0wyI8@xV0N{~wlgAhSwnS!)u7nL2G#|#3*@gkKYw~7 zbOl0nfz_bc#SYd5vuk08hzmkjCR7(#4T@bHU|p~<%-()s2SQgPR2Ntcid~#wT`;?* zUDD`8=$Z-D1y+M%7Z+F;%wGm8wfGUbHbQlQ)u7nL4b}y-t084?F+$f#s4lP?6uWr9 zy0H7}AygMw4T@d7U|le~H@1lv5OC^3znBbeTNWu+7JXS@L*tI0INZ< z3v`bNqOZ3`NZ}?zmljkPSPhC@0${sfVVKT&dIv(66I2&i4T@cYU|leO-Mtx;gU}TN z)dg0AVwVtD7tF5HkrICpy2_xsz-mzJ5(evn*;RGpR1-qi1gI{s8Wg)kz`C&8wF;^W ztOms{QLrwUT|48L<|6Dm0@VdpgJPE$SQjj9IQ%G2KO9HM7RBq_Gdz%Eo(}ob}3U>wu2Cy0wyCmVdKqUaE4S>*P4%G!#gJPEy zSeFKLorCRR`5J_-Xs9l*8Wg*v!Mb4n67yW1htSms)dg0AVwVh97pz`BrLo!`p=&u* z7g!C7U9wKQc_qsR^q01Ah3#y^2nmR&PH&ho`4T@blU|leOG3{O;h0wJS zstc?J#V%d2E|^`*pF77ObZv*~0;@r>OAo9IyT8stb%E8O*rgBF1+z=1tPRP9kDw?*J_hw8ELYEy>7g!C7T_#{%FuOo?B|=viR2Ntcie08)T`;>qbtOVq z9#j`t4T@c6U|rblYJ=(mt3k2L9IOjw7pSg8*fkHT3#w?(@sw)w?&Ovp7)u7mA1=j^CH&o9?s`A6r#xtlcuo@J*tl_#qxw0w7c_l*E zU#Kpy8Wg*1;JQF|%{bL*&j+_lSPxQ0fz_bcWee8@vg=pWB4>myGpH`G8Wg+iz`9hR z^|GGbx&H`V5l~%VH7Iu3gLT2eP(Ur92BE74stc?J#V!Z1E?C<5n#tRP(6s=n3#*A2wgm& z#m@{33}7`VcDaCc!NTy@ft8sET?SBHU^OUqxq@}U?D`}pCydY)0@VdpgJPE(SQpG+ zVjI5xLg=c1>H@1lvCAE-3uf0ZwIzQLy5>N2fz_bcq0Eyb-$YL3M%EpxEUF)&)u%AiFZ8UrUI??fMPX1y+M%mp51!%wLWd_I^O< zk~M(j6tEf;yL`a9VDpu)*qtvUblE|5fz_bcI4{a0bS;PK0;@r>D*&tuWEUt5vs~B3h{40~6jT>j z4T@cXU|le~K<#;iuKQ43U^OUq1%Y*e;tLeZqOzM(5xPD@b%E8O*cA-c1+(i=Y+5Wr z7boaKZ3YGguo@J*LcqFUcJ1^{R7B{Khw1{WL9r_otP5t>0tSC|gf3I4F0dLDyTZV_ zKw${-7pUEf(B%!)1y+M%S2$P~%r3E*wE+lSiBMf&H7IsPfOR3aH)P=PRSwk!R)bBE>L>|p{oz73#w?(@YHuKP?Sbk7t3k0V z2CNHa7pT2~&~*i>3#H@1lu`2;mOwZ3kT`X?WU5f$9RQL9r_ttP5rrL&%Hc2wl}sU0^jRcBO!I!OFwc&0MN~kWd8Wg+Iz`8*G0_Cqa`}Wr%bRC510;@r>D;=y0W|z78 z?w?*JSv{8*p(_ol3#IZm2G>8Wg*V zz`8*5Um#uO^1gNmT^FFbz-mzJDhBI<`AgIz{whM(bEqz`8Wg)qz`9`ZRdp>s4x#HG zR2Ntcie05(T`;?{`48Mj=n^%7q(iV86uZj6x?u73a}h@lLYEFy7g!C7UFBe1FuN{o zSS5qdw?*} zDw^{lLYE0t7g!C7T@7Geu=t9OD4vVZ6%N$}R)bH@1lv8xrV3uYH+{1Bmw(+rf#7#P56Q0!_0>jLFvkX@kh!%1+vw4l1cYEbNI2kU~_ z1sXp@=(30E0;@r>s{^bHWEZH60*xOcbOl3ofz_bc)d|)GvkNqSh|rY@)dg0AVpkVf z7bpxtc7etZ5xVN3y1;5s?CJ*Vg4qQcKSb!73e^QxgJM??SQpG+pz%Y5uGLUoU^OUq z^@4T5>;jD+B6J;u>H@1lv8xZP3%kFrLv?}GpxD(9)&;W*G=7M%>n&6lSPhC@6TrG) z?G4ae+YETxU^a)OD6kq7yC#Bl!R!LH=MlQ3pt`_nQ0$rn)&&bgPKOt3DP zU7+?nLe~wbF0dLDyJo?4fyxcgSju#G+V}v~1y+M%*KD{hP_6`xA0l+ITR>72SPhC@ zbKtr_c7etZr@`%#h3W#UL9uHtTo=eL(D)%jmjhH6SPhC@^T4`b^%rRT5TPpxstc?J z#jg2aU9d0&jUOU(wLx`()u7n50IUm^HbCQt2wkh7y1;5s>{f0;@r>Yb9J4C>?^v4-vXvKy`uDpxCtvtP7MjKz4z~ z59h({Vzq*VJXj5iU8}*mVEzJ)A0l+ALv?}GpxCtrtP9p(28|ygba_B^fz_bcwHB-k zW|t{vNjpMUHdGf_4T@dsz`9`Z1sXp@=<0*&0;@r>Ydu&O%&xcFcnT4^HbQlQ)u7n5 z0jvvT7bpxtKZm=$x zU7+>`Lf0{w?(@YHuLyQiJLOt3k2r09Y3+ZGhSv2wiqiU0^jRb{z!kg4qRX zZy^cJ0 z1uG9hU0^jRcAWt0g5@vJ_~CMR82*Op0;@r>>m*ng%r4OQAwrjg zEhI&O)u7mQ3aks{FHn4e#t&D*?J|Mt0;@r>>oiyw%r4OQAwri2R2Ntcid|>Gx?pJ@ zG=7NC6%Ew|R)b>KS+FjcU7+zpgsuXpF0dLDyUu}if&2xEFVOfQLRTwP7g!C7UFX5N zV0MAV4-vX%L3M%EpxAW*t_u`~pz*^^@Gx8t)dg0AV%J5uE|3dBvkNqSh|nbu)dg0AV%IgWE?9hl#t#v?%%Hl!YEbOD4%P*;3p9R+(B%u& z1y+M%*A1{Pm|dXpLxiprs4lP?6uWMMb;0ZcjUOU(RYP@w)u7mQ3#jzX9SPhC@_rSVfcCo}v=tby~wg>r@fdQ-r#jg8cT`;>ghE6|;&}9eJ z1y+M%*8{LFSbUk)$TlE!B|>$9)u7n*5UdMk*OmuCc?eytP+ed(D0YGFt3#Zzz^HL* z0z%hHs4lP?6uTay*cG?W;Q>O|S*R|s8Wg*pz;%Jj4NyNN0=^FRBUBey4T@b);krO2 z0H~jW(8ccn33;#@6uX{*b%F9S$gbW{kM3x=T{=)*U^OUqJqPQ8g`xPF>uV9Z{GhtP zYEbNY0oDbw3*^FC`>iG-bmc>Jfz_bc^%ATLW|v9OBzJ_aNl;y2H7Ity0_%dc0k-VV zj7R9&3e^QxgJRcfur8QgD^34$A#~k<>H@1lvFi<37c3p#*m3k9Lf3DoF0dLDyWWCz z!R%tO+f`mL+4T@b~!Mb2}f%=sQ zT^dkbU^OUqeFN))g(0Y4iO^*O)dg0AV%K-DE|^`QekDRz5L6df4T@bqz`9^|f%=sQ zT^UeaU^OUq{RHcR*#+uXB6QV3b%E8O*!2sn3lxT+ybS7BB6LlG>H@1lvFkTj7tAhD zzY?Kq6;u~k4T@cVz`9`VUeGvp0zAGBKy`uDpxE^ntP5rrs9%ZDbsee;tOmude_&m( zFa-505xU+&b%E8O*!3T*3uYInUy0Dg>I_LyU^OUqfo?oS?BxXYD-pV6p}N3oQ0!vl z2JISy*#+uXB6OKTb%E8O*u@0a1@jlGUA|CVU^OUqF@tr%>;m;G5q70Qb%E8O*u?_Y z1xp*CekDRzEmRj+4T@c?U|le~K>bRDuBlL6U^OUqv4M4g>;mOwP`@%Bo;KD(b%E8O z*u@Uk1+&XPd-G?6t|L%gU^OUqae#Hf>XdC!LU9OPx1hSfYEbOr1nYv?#m)c90-@^@ zR2Ntcid|e_T_Asf^4H?VCte6$TrQ9l1y+M%7dKcJ%r4eRZod$^l%Tr6YEbOr0qX+U z1q#DI9dGBR!o$!Cstc?J#V%g3E|^_kKiZihbooJbfz_bc#Rt~~vg?DHR9PO}u0*IV zuo@J*_~E)hp#ka>A#{~Pb%E8O*d+kg1xxemUT!Qx=<0#$0;@r>3v}lnVy~H9u)ryV zuEkJYU^OUq38C0^R%6LjgsxpsU0^jRb_s)Z!NTyOo?0wI*F~rpxT%SPhC@VqjgcFl5@9WQNcs<_bxNU^OUq ziGy{)>~ z*H)-5uo@J*WWl;%c17Nr{S=|=22>YV4T@cIU|q1iX7R~$w;*)=hUx;VL9t67tP5rr zZ{TNEgf2NZNGyZZpxC7V)&+~Ntp;ae5V{;m2DjmTf7vWHs`x>BIJz-mzJ zQbMr{)c->0YKQ6qt3k0#8LSKDufJutS0Hq)hUx;VL9t5(t_xH~f%;#w;cI)&Lv?}G zpxC7f*9FRzp#B#^*L$cguo@J*)WEtx`3qzhc%BGu7oR&M{18og82*7|C$Ha zWdzj)R)b=f23QxY{#yQFb^}6JC{!0%4T@cwU|le~M0&Zy5xT0Ny1;5s?9u}3f~5^m zKLw#{K2#T24T@dbU|le~I>d`L5xR~*b%E8O*rfy313v_ENBF+EZC42>; zOAo3GtOms{1F&7NFa(|Jh|uK*)dg0AVwWLU7tCK9a-F8Wg*X!Mb2}iS~BbBXspab%E8O*kuCN1xtt7`xFcjx|Tt8fz_bcWeU~> zv&;KI*LQ@jeNbIsH7ItOfpx*c@Mrq{HiWKgP+ed(D0Z2Hb;0Zsp8HeJ%Op=&l&7g!C7UG`vIFuOo~B80B3P+ed(D0Vr3bz!&bJX9B04T@ckU|le~ zKz$;FT`!@!z-mzJasumur43M@2%(GF3z9a#YEbNQ2J3>^1?m$abjd<>fz_bc%LA+n%M+{%X4fm5vJ!-@ zZBSicH7Is@!F7Sc5Yz^+WrytnzW~(*R)b=fH(VFUg`hS7Lf3n!F0dLDyL`a9K`Duo@J*KsTc!#>_!&0EDhtP+ed(D0T&+*yWkXB!kek52_2S z2F0!*ur62|V8SofxAyQbd<4}6R)bIf)p-Tm-3#w?+EC3QFjp=%vf7g!C7T`^!?urLI*0T8;5LUn=Fpx6}) z)&;Z6W!i!V2wiuey1;5s?1}^Hg4u-{U!S46z-mzJiU;e0*|mo4LnFd2E?-EB0;@r> zD*>zvxeee9PxFdUU0^jRb|r#!!R-=KxQWnZ3DpHwgJM?_SQjj9fZ6~ET>(&CU^OUq zC4+Ur>;km`5V|s`DRag4qRX10ZxYL3M%EpxBiP)&;W*)CNH4nhn(jR)b)>B>W&L3akdju57R_m|dVY0791mR2Ntc zid{KiU9h~YB6#g1LYF&K7g!C7UAbUgFuP_ySZ09Gl>pTRR)bYJP+ed(D0UUVb%DYV)CTwkpQB$5)dg0AVpkzt7s!R6 zHUL7`S*R|s8Wg*Vz`8)`5M-BatWwTjxLxm{y1;5s>?#K9g82*720-W%@rT4RSPhC@ zC172!b9;)4T4E8pETOuq4%-7~uVsMNnN}H7Is9f_1^{0`*}Ly7oeKfz_bc)dbcB z3qw%-h0t{!stc?J#ja+sE|^`Q`U|1!GgKE?4T@bYU|le~K=l_w7k?lmZGhFF*wqTw z1+xoOe<5^fLv?}GpxD(0)`i_Jcc?C~8Wg+Q!Mb2}f$A@WUCB^gU^OUqb%1rj(gvvh zLg=c8>H@1lv8xlT3uYIn{zB-Q4b=r!gJM?~SQp4HP+tBQT)*@`JTGsD>H@1lv8x-b z3uYIn{zB-w1l0vrgJM??SQo53)VlhB8KLV9R2Ntcie0^6T`;@il+Au4ba4bhVi~Lk z#jZZEE|9-K`3qEkA#|xgb%E8O*wqi#1+&X#f_f=JmjhH6SPhC@6X3c)VF;?fn&Dv> z1JwmqgJRc2xGs3xR~=LrSPhC@lfb$_=@4WWsQzk$+ch7m3#w?7>sQyCedIi-5R)b>KRIo0XU7-34p-V6r5?^37D0WQ)>w?7> zsQyCevViIWt3k1AI#?IXE>Qi2&=n8W1y+M%*9@>OkX@iK1l3=y@GxwH>H@1lv1=w+ z7tAhD{e{pqAF2zi2F0#fU|pd20>v_@{zB;54%G!#gJRcgur8Qgp!y4;>mpPaSPhC@ zbHKV_c7f_Igs#_6U0^jRcFhIrg4qSCzYw}uLm=@5R)b>KJg_cM7=ru-s=pAre$5${^7g!C7T?@gwV0MA(FNCf=x?pyJ>Mw+@J5XI;lza2)jf=A!!4w2F0!w zU|q1Z0jj?cx(uMYz-mzJS_#$#vkO#zA$0jbb%E8O*tH6*3uG54FN5l@4tUzgfa(IP zL9uH!SQpGLQ2m9_)eO}IR)b>K8n7-{c?hb%5W40jX94MLYPR2Ntcid~z)x?t&0O+WQALYD zU^OUq?F8$B*`+P*FOSf53#tpO2F0#jU|p~w?+k6Hso2(4`F31y+M%*IuwLm|dDm<+TW1Hc(w)H7IuN1M9+W zS142$SPhC@`@y!bS;4D0;@r>>kwEMENuvGiLajmPa8X-y1;5s>^cnA1+y!q;LjU`uDeiOU^OUq z9Rcftg<nKhRH3@SYEbMt2G#`&!;V?2LlL^X zpt`_nQ0zJm)&;XmNaghKb+9g&U7+?M z!Y+TPF0dLDyKaDW!O|h9eTdMN3DpHwgJRcBur8Qgp!Oj`S2I)>SPhC@x4^nUYXU)e zxi=_HCk>u9=0bIW)u7mQ8>|avSBI+N1ca_bP+ed(D0bZe>w=|2!}<>MeeiL%XHZ>W zH7Iu71?z&@l^A_S9ifXm8WPK3H7Iu71M33C7sy|NMk3*^aDVATb%E8O*mWPQ3uc#a zXXbWNLf1d2F0dLDyPkk`!Qu;4Zfu0x zr4$1Rd9WH3yPkq|!R!K+8wg#VP+ed(D0V#q>jK3WsO$xm8wg$bP+ed(D0V#u>w?(@ zDmM_irbBgs)u7n*0;~&W7pUAo=-Lm}1y+M%*GsT2m|dW91EK3VR2Ntcie0b3y0F{D z9}5XXuo@J*UW0YP>;jb=2)oRoy1;5s?0N&%1xkmYFa(tw2wm||U0^jRcD)7bg4qQs zHxRnop}N3oQ0#gK)&)z4dfxvd=D^d&dZ;e28Wg+UgLT2|%6iDwgV1#Ystc?J#jX!v zU9d2GuEMnlp^G675{6(kD0Y1W>w?*p_J(ggLYEd)7g!C7U7x_ZU}4zC9cYTs6#~@- zR)b>KXRt1qU5>R2lo7h>pt`_nQ0)2w)&*L#1S&UjZ?|*LhKJz_s4lP?6uZ8Hb;0Zs zX{w!$&~*f=3#>nB(j%wJ1bgpVL}SweMz)u7n*3#SPhC@f5Ez7 zcCEA&5JKoW0M!LngJRb|ur3qm`SXWQACf@mdI{A9R)b>Kf3Pl?T~WnLCn9u-BtSwQ ztOms{1|HB(E(54tTXb`|5W4K4y1;5s>;hegj#x{)Ncqfugsv>8F0dLDyO_Xs!TgoR zd`1w=|u*URp!5xV9=b%E8O*u@Ff1+$B7SJ5tnu9Hw*U^NVSW%)TJNjacI&cFrMr37X( zFjU`aEr#fdFU!x#O@Ro5)u8C&2J3=^6$3;6m#l)#;GBZ23#RR+AiH*9=mM)j(ItkW3uM=13|(L~D7wT^bb;*JfuRem21SDa77SfrH7L5IP;}{*o@VaC&;?e5qDvaA3zok?{@RYA3#zPy(w#u}cxG3sw`I*}Zx`LRS`47g!C7 zT}ohGFuPQCss{12mg4GTuf{R`wbVWgRfz_bcr3Tgox2yKG1wz+!s4lP?6uZ>Hx?pyl zOn7t*q3a4%7g!C7T^e9rFuVS1EdGShC71&77g!BSiqZt@g2*y39J{$86`~8dP64Yy zaiJDi7pxBpI;-j`d>p_JY8O}yiVL;Dx?nB@omGX#9)u7m=2i66%3v@2lO}Jfupt`_nQ0&qN>w?(@ zIu{F}OEwh}wqP|Vb{T+m!BQ{iTr7kxTc|Fu8Wg(>!Mb2}NxN>eKH@1lvC9ao z3*;|QP63^Zh0xUq)dg0AVwW*k7tF4jnB)$GuEkJYU^OUqnZR{{;*0NdHtS1x7#@Y{ z0;@r>%M`8)YEbMl1M7l?p)8Am3_=%I8YJYwYEbMl2kV0QOMXhX z8bX&IR2Ntcid`09U7%7Fk+yhL3M%E zpx9*#)&;Zc>%QsI2wiOHkdOzfL9xpYtP5t>=ej%d5xTUXy1;5s?6L>zg4w0ln|}nM z%MYpxtOms{2e2+!7;X%C9D&eP1l0vrgJPE>SQpGL8JV(q2wl^ly1;5s>~aF@g8A$C z?Kejex^_Wzfz_bcw=ZNpz+>&@chM;0SS4q8Wg)+ z!Mb2}DRdWJN9Zzy>H@1lvC9pt3)!w`aJ%B6y1;5s>~aU|g4wk-^=COkR|`}ZSPhC@ z9$;O_cD;bxwF0UOtOms{Pp~eSU7+;<2wj(jI@ikX_rgXYl91?K%k61y+M%mp@n+%&y&C@|zL5ZbNl})u7lF z0M-S|U-?gFlp%C|f$9RQL9r_ktP5t>)vsEt2wnVHkT3+RL9r_ctPA8XP{@ZTcyJ+f zX+d>?)u7lF4Aup+ON{lLEkc(oR2Ntcid`XKT`+$g-r{Y9(3Jqy1y+M%S14E)%&sM} zUmhWJRYP@w)u7lF2G)h$U(=wvz-mzJ3J2?g*%e)?=!URsBUBey4T@b6a9yCZVZQHZ zei1xvoPp{Bt3k0V60Qpr^4t^bP9Su>fa(IPL9r_etP43W=fUk_&W6M?SPhC@(O_LL ze~CX+K8Mhy4b=r!gJM?D;};36owz?mG~fZZG`Fqt3k0V0jvuahKIi{)UAb| zVR8kk3#oHVVu2wiWWy1;5s?8*S^0{IIR%b+$kLKlB7 zBn-i7Q0&SC>w?*(kk7OVq00!W3#SK{*FmT*uo@J*^1!-ac7f&) z5xSm1b%E8O*p&~~1+xn@hltR{od*eduo@J*3c$Ls+hqXN1y+M%S0Pvz%r4LzBEqgv zs4lP?6uXMRx?pJoG>3@LRRz@rR)biKzSMDb4RO< zi<{tS;|Nq2SPhC@rC?n!yHcV!`VqRmKy`uDpx9Lg)&&d0)w`905xSJ~LH08+fYqSb zRSwn#v&(vS&whli5U4J&8Wg)Kz`9^z*s&qr456zPstc?J#jZ-QE|^^b+jYbdy0$@e zfz_bcRRz`s^B4Q;4@kP6Lv?}Gpx9Ln)&;X`zx2Xq2)iT-AfW+PgJM?=SQmDGc|vu8 z)u7l_3)Tg*>+u}hc!XV5P+ed(D0bC>b-~Jw#Vc-pse`AD)lgkvH7It~gLT2|is+qS zi_mosstc?J#jXahE@Zpv;dTiWLP7(q2F0#Mur6e~5V~BTy1;5s>}mq*g4wk(W$!f} z`1-?gs4lP?6uX+ix?pxWu3H#_&@~sT3#U7+>`LRTeJ7g!C7 zT@%2%K>h-SA*j89&@~gP3#FuOqQ4TP?pP+ed(D0WSP>jH%#Xq|%!JPdC_ zb%E8O*fklh3*^GEq_Up~UB95Zz-mzJngZ4Z3qz*JtnUb2QYDc10;@r>Ybsb5%wJD_ z9=VCoWdqd(R)b>KG_WpM81Cv&bU^5egX#jSL9uH(SQpH$<!ozS8R2Ntcid{3ox?px)bbK3w&~+ZF3#Yc^OH%r47i0cQ}pG)o~N4_1R>*Br1eSQy%_-t`orD;%l|tOmudxnNx|yXNn= zkVoihh3W#UL9uHdSQo791+{yX;bFKLstc?J#jg2aT`;@WYXMjn zEFGTOyz?(Y7fTr=G{9<5>{Yc*IGvRw#W zPoTQMYEbN21J(tz3$(Yw5I*K0QUM7=uo@J*)`E4x?3$+)w;rKO7pe=a2F0#*U|p~@ z-@bnGI)pA)s4lP?6uZ`gb;0baTNWON&=n8W1y+M%*9NdISh-PjfKv>is}iaUtOmud zjbL3cyPh3X+>FpQ8LA7c2F0#TU|q;zXbum^1uAJP2Jspt`_nQ0&?U)&&bgP}z&nB~%GX z8(=jkc5Mgig4tES$KyIemkv}HSPhC@JHWbNVaUt$_nsa+4Beo*z-mzJ+6mSLvr9Iu zzZjt_9;yqh2F0#jU|k@8f!Z6JU3IE?;dYfnb%E8O*tHw13uc#fM@lF{*9538uo@J* z_JDQ4`~}KiTyVQqKy`uDpxCt+tP5rr=&WFbu0v2=U^OUq?E~wArHwD{0U8Khx1qYg zYEbOj57q^+WA z#uW%%I#69;H7Ir+0_%c>At-Gy!^6-Wstc?J#je9(T`;>|UOxQ=p(_!p3#>m*ng%q~e~!%T#(A5dLj zH7ItS0_%dMLrIRuRtQ~!)sVCSR)b>KX|OJsT@1_zY7x40pt`_nQ0zJb)&&YfP&)j2 zGVKCFmpfD!SPhC@XTiE)cC~v<>qqEHg6aaRL9y!`SQp4HP}(>>)ozM9JPd20y1;5s z>^cwD1+#0?(>V!7;8YEbOD2-XF&Yp%$Xxd>e+ zpt`_nQ0%${)&=qxC=BcPPg^2%J%Z{2t3k2rGFTVPt_QuBrz3Rzf$9RQL9y!!SQp4& zpfLP@M>aqK9)@BykhB3-gJRcJur8QgJ6BmpAaof(b%E8O*mVu83zp_VWv?vUE>EZ~ zuo@J*u7h>K?DBVXpMcPn4AlizgJRbWur83lKw%hmq-!EVR}EAbSPhC@H^I7KcCA`{ z(Gj6*I#d@}4T@d2z`9`ZwRp9FCPLRHs4lP?6uWMNb;0ZkJNj@3Lf2WSF0dLDyY7H> zf!f%h_-avj^#1{T|JMtsF0dLDyY7N@!R*@og;^Y-i?J3G%V0GqcHIN(0@(!$!~bV0 z;}Nv8yOT*KDXRuo@J* zo`7}1(qX}-nadEmwnBA*)u7n*6s!wom(}H&st8@@p}N3oQ0#gJ)&=v|nVCXs5V~GM zb%E8O*!3K&3uc$4pIKXRt1qU90K^QxLiwp}N3oQ0)2w z)&;9mP9{yvy9G}hF;HD#H7Is{1?z&@)m~nlgwRz6)dg0AV%ImYE?8dvr5^Pgp{pON z3##tOmude_&lOyJod6t3v2Xf$9RQ zL9y#USQpH$XJ!^62wnA1U0^jRb}{gR_J6_RYv=L+PK2&mP+ed(D0VS|b;0bK;_E1c z(6tq+3#7OD%Z2E{Hmur64+p}sQr6+)LW zR2Ntcie2nrT`;>8<3D{s=<ivz3+7KV&9O5q4y$xvNjH7Is*f_1^{niagL z4xy_Wstc?J#V#(eE>JlS3NQKeN2aat@x#ebU0^jRc5#Ds!R*?7?|TVC*EXmwuo@J* zc)+?q@dawLU4OrD4MNuqs4lP?6uWrAx?pzAaMpA~==uZI1y+M%7av#`EDXa<7Mw-s zQfLDCm4N}Q2E{IZur8QgNsE?SA#}Mwb%E8O*d+kg1)AdknS1=HYwc$E8qN%;F0dLD zy9B|yV0LvMTl*5Bs}rgVtOms{A+Roxzd-GcxyRSA&W6uruY~FXt3k0#7_19s*Rkd2 zCLwg4gX#jSL9t5&t_x&W&BJ-~ro-*}1l0vrgJPE`To)*oi>KXJLgx?uha_H;8s=(2$70;@r> zOAf3HR&H?EGPHk$+Z71a1y+M%mpoV(%&w?*hph-*c~D(oH7Ir|fOWzA1xg#S@VO9`wCRz}VIVfz@NYb#V2SPhC@%3xhEyW-~6 z`XO|kf$9RQL9t5(tP5tB>56yD5xO2jb%E8O*rf{A1+y#u^o2--u3u1HU^OUqseyH2 zw@a`Uk~Y9w?)8J+qPnVV4F}7g!C7T^e9rpfU=Sdh@o42q1LXLv?}GpxC7e z)&;Z6@W7S52wh=NU0^jRc4>iiA*YQPc-qK?>H@1lu}d4Q3uc$)wHPUct_G+suo@J* zbile`VW?E36oAk*391XM2E{I2ur8Qg)`evT2wlsdy1;5s?9v14g4xxl^ic$%YZp`( zSPhC@`e0o!yL>M$PC@882h{~ugJPEfSQmD?9zk`1)u7mA2-XF&OIo1iJHoDSP+ed( zD0Ufvb;05bG;f{+k1wt^NIC?oL9xpitP5t>8jCBt5W3`{y1;5s>@orCg83_5+5R>{ zmoZcqSPhC@reIw#ySQ6E`5|<9LUn=Fpx9*w)&;Zcbn%<{2wm||U0^jRcA0~9!R+$X zUcC^Zs}!mWtOms{3$QNic6CE_fz_bcWeL^=v&%HvT@GQ_LZ~jV8Wg*%z`9_0+2K+% zGeXyPs4lP?6uYd!x?pw{8}uQC{8^|juo{#dpf+G#OrVOAfq^0Ed&!M>aN0oL0SZ=w z;zC=nE?9~Jjob!-b%Bg|47Cfa2E~PTU|ld5GH88CM(Fwn)dg0AVwXKw7cAr@*IOnb zbV;;BQZHByid_z1T`;=>Lk>Pc=rV!o0;@r>%Mq*#7V>WnEWU-%6#&%*R)b=f6Id6_ zu9AjTQV3l+P+ed(C?W3*)&(l9L7{eSuf8xu7jnph)u8Bd0qbG`X<=YsIFPciD-0a+ z$hyF4Q2gZz)&+|%PzewW)&(-A1L{Js8WexIfpx+B1zN#{(6t1r3#w@JJ+ofAqAaqTK>H@1lu`3*`3uafsJd5=RU7Mh~z-mzJ ziU8{Zr43M=J+I0vMCdvT)dg0AVpk+s7tF2)GwvNn=z0Ow1y+M%R}@$m%wH3~roBPv zV(tWmD+2>q4T@dSU|le~!X-V|Aauz=b%E8O*cAiTh23A4P+ed(D0anyb;0aOV|C9WbhSZsfz_bcl>pWS zvkO#WGl5etC@;@}>H@1lu`3a*3uYIn&4$pm6{-uY2F0!5>H@1lu`30v3uYIn&4$p$*ab-&U^OUqrGj
;kpf5W1wHy1;5s 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

#y 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�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ɕ>Npv=Yx4>M5X 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 G&j0`%QN;ED diff --git a/vendor/miniaudio/logging.odin b/vendor/miniaudio/logging.odin index 0c14a10c2..b03778079 100644 --- a/vendor/miniaudio/logging.odin +++ b/vendor/miniaudio/logging.odin @@ -12,6 +12,36 @@ when ODIN_OS == .Windows { MAX_LOG_CALLBACKS :: 4 + +/* +The callback for handling log messages. + + +Parameters +---------- +pUserData (in) + The user data pointer that was passed into ma_log_register_callback(). + +logLevel (in) + The log level. This can be one of the following: + + +----------------------+ + | Log Level | + +----------------------+ + | MA_LOG_LEVEL_DEBUG | + | MA_LOG_LEVEL_INFO | + | MA_LOG_LEVEL_WARNING | + | MA_LOG_LEVEL_ERROR | + +----------------------+ + +pMessage (in) + The log message. + + +Remarks +------- +Do not modify the state of the device from inside the callback. +*/ log_callback_proc :: proc "c" (pUserData: rawptr, level: u32, pMessage: cstring) log_callback :: struct { diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin new file mode 100644 index 000000000..ac47d43d8 --- /dev/null +++ b/vendor/miniaudio/node_graph.odin @@ -0,0 +1,469 @@ +package miniaudio + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/************************************************************************************************************************************************************ + +Node Graph + +************************************************************************************************************************************************************/ + +/* Must never exceed 254. */ +MAX_NODE_BUS_COUNT :: 254 + +/* Used internally by miniaudio for memory management. Must never exceed MA_MAX_NODE_BUS_COUNT. */ +MAX_NODE_LOCAL_BUS_COUNT :: 2 + +/* Use this when the bus count is determined by the node instance rather than the vtable. */ +NODE_BUS_COUNT_UNKNOWN :: 255 + +node :: struct {} + +/* Node flags. */ +node_flags :: enum c.int { + PASSTHROUGH = 0x00000001, + CONTINUOUS_PROCESSING = 0x00000002, + ALLOW_NULL_INPUT = 0x00000004, + DIFFERENT_PROCESSING_RATES = 0x00000008, + SILENT_OUTPUT = 0x00000010, +} + +/* The playback state of a node. Either started or stopped. */ +node_state :: enum c.int { + started = 0, + stopped = 1, +} + +node_vtable :: struct { + /* + Extended processing callback. This callback is used for effects that process input and output + at different rates (i.e. they perform resampling). This is similar to the simple version, only + they take two seperate frame counts: one for input, and one for output. + + On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas + `pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`. + + On output, set `pFrameCountOut` to the number of PCM frames that were actually output and set + `pFrameCountIn` to the number of input frames that were consumed. + */ + onProcess: proc "c" (pNode: ^node, ppFramesIn: ^[^]f32, pFrameCountIn: ^u32, ppFramesOut: ^[^]f32, pFrameCountOut: ^u32), + + /* + A callback for retrieving the number of a input frames that are required to output the + specified number of output frames. You would only want to implement this when the node performs + resampling. This is optional, even for nodes that perform resampling, but it does offer a + small reduction in latency as it allows miniaudio to calculate the exact number of input frames + to read at a time instead of having to estimate. + */ + onGetRequiredInputFrameCount: proc "c" (pNode: ^node, outputFrameCount: u32, pInputFrameCount: ^u32) -> result, + + /* + The number of input buses. This is how many sub-buffers will be contained in the `ppFramesIn` + parameters of the callbacks above. + */ + inputBusCount: u8, + + /* + The number of output buses. This is how many sub-buffers will be contained in the `ppFramesOut` + parameters of the callbacks above. + */ + outputBusCount: u8, + + /* + Flags describing characteristics of the node. This is currently just a placeholder for some + ideas for later on. + */ + flags: u32, +} + +node_config :: struct { + vtable: ^node_vtable, /* Should never be null. Initialization of the node will fail if so. */ + initialState: node_state, /* Defaults to ma_node_state_started. */ + inputBusCount: u32, /* Only used if the vtable specifies an input bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise must be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + outputBusCount: u32, /* Only used if the vtable specifies an output bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + pInputChannels: ^u32, /* The number of elements are determined by the input bus count as determined by the vtable, or `inputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ + pOutputChannels: ^u32, /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ +} + +/* +A node has multiple output buses. An output bus is attached to an input bus as an item in a linked +list. Think of the input bus as a linked list, with the output bus being an item in that list. +*/ +node_output_bus :: struct { + /* Immutable. */ + pNode: ^node, /* The node that owns this output bus. The input node. Will be null for dummy head and tail nodes. */ + outputBusIndex: u8, /* The index of the output bus on pNode that this output bus represents. */ + channels: u8, /* The number of channels in the audio stream for this bus. */ + + /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ + inputNodeInputBusIndex: u8, /*atomic*/ /* The index of the input bus on the input. Required for detaching. */ + flags: u32, /*atomic*/ /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ + refCount: u32, /*atomic*/ /* Reference count for some thread-safety when detaching. */ + isAttached: b32, /*atomic*/ /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ + lock: spinlock, /*atomic*/ /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + volume: f32, /*atomic*/ /* Linear. */ + pNext: ^node_output_bus, /*atomic*/ /* If null, it's the tail node or detached. */ + pPrev: ^node_output_bus, /*atomic*/ /* If null, it's the head node or detached. */ + pInputNode: ^node, /*atomic*/ /* The node that this output bus is attached to. Required for detaching. */ +} + +/* +A node has multiple input buses. The output buses of a node are connecting to the input busses of +another. An input bus is essentially just a linked list of output buses. +*/ +node_input_bus :: struct { + /* Mutable via multiple threads. */ + head: node_output_bus, /* Dummy head node for simplifying some lock-free thread-safety stuff. */ + nextCounter: u32, /*atomic*/ /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */ + lock: spinlock, /*atomic*/ /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + + /* Set once at startup. */ + channels: u8, /* The number of channels in the audio stream for this bus. */ +} + + +node_base :: struct { + /* These variables are set once at startup. */ + pNodeGraph: ^node_graph, /* The graph this node belongs to. */ + vtable: ^node_vtable, + pCachedData: [^]f32, /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ + cachedDataCapInFramesPerBus: u16, /* The capacity of the input data cache in frames, per bus. */ + + /* These variables are read and written only from the audio thread. */ + cachedFrameCountOut: u16, + cachedFrameCountIn: u16, + consumedFrameCountIn: u16, + + /* These variables are read and written between different threads. */ + state: node_state, /*atomic*/ /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ + stateTimes: [2]u64, /*atomic*/ /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ + localTime: u64, /*atomic*/ /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ + inputBusCount: u32, + outputBusCount: u32, + pInputBuses: [^]node_input_bus, + pOutputBuses: [^]node_output_bus, + + /* Memory management. */ + _inputBuses: [MAX_NODE_LOCAL_BUS_COUNT]node_input_bus, + _outputBuses: [MAX_NODE_LOCAL_BUS_COUNT]node_output_bus, + _pHeap: rawptr, /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */ + _ownsHeap: b32, /* If set to true, the node owns the heap allocation and _pHeap will be freed in ma_node_uninit(). */ +}; + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + node_config_init :: proc() -> node_config --- + + node_get_heap_size :: proc(pNodeGraph: ^node_graph, pConfig: ^node_config, pHeapSizeInBytes: ^c.size_t) -> result --- + node_init_preallocated :: proc(pNodeGraph: ^node_graph, pConfig: ^node_config, pHeap: rawptr, pNode: ^node) -> result --- + node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^node) -> result --- + node_uninit :: proc(pNode: ^node, pAllocationCallbacks: ^allocation_callbacks) --- + node_get_node_graph :: proc(pNode: ^node) -> ^node_graph --- + node_get_input_bus_count :: proc(pNode: ^node) -> u32 --- + node_get_output_bus_count :: proc(pNode: ^node) -> u32 --- + node_get_input_channels :: proc(pNode: ^node, inputBusIndex: u32) -> u32 --- + node_get_output_channels :: proc(pNode: ^node, outputBusIndex: u32) -> u32 --- + node_attach_output_bus :: proc(pNode: ^node, outputBusIndex: u32, pOtherNode: ^node, otherNodeInputBusIndex: u32) -> result --- + node_detach_output_bus :: proc(pNode: ^node, outputBusIndex: u32) -> result --- + node_detach_all_output_buses :: proc(pNode: ^node) -> result --- + node_set_output_bus_volume :: proc(pNode: ^node, outputBusIndex: u32, volume: f32) -> result --- + node_get_output_bus_volume :: proc(pNode: ^node, outputBusIndex: u32) -> f32 --- + node_set_state :: proc(pNode: ^node, state: node_state) -> result --- + node_get_state :: proc(pNode: ^node) -> node_state --- + node_set_state_time :: proc(pNode: ^node, state: node_state, globalTime: u64) -> result --- + node_get_state_time :: proc(pNode: ^node, state: node_state) -> u64 --- + node_get_state_by_time :: proc(pNode: ^node, globalTime: u64) -> node_state --- + node_get_state_by_time_range :: proc(pNode: ^node, globalTimeBeg: u64, globalTimeEnd: u64) -> node_state --- + node_get_time :: proc(pNode: ^node) -> u64 --- + node_set_time :: proc(pNode: ^node, localTime: u64) -> result --- +} + +node_graph_config :: struct { + channels: u32, + nodeCacheCapInFrames: u16, +} + +node_graph :: struct { + /* Immutable. */ + base: node_base, /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ + endpoint: node_base, /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ + nodeCacheCapInFrames: u16, + + /* Read and written by multiple threads. */ + isReading: b32, /*atomic*/ +}; + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + node_graph_config_init :: proc(channels: u32) -> node_graph_config --- + + node_graph_init :: proc(pConfig: ^node_graph_config, pAllocationCallbacks: ^allocation_callbacks, pNodeGraph: ^node_graph) -> result --- + node_graph_uninit :: proc(pNodeGraph: ^node_graph, pAllocationCallbacks: ^allocation_callbacks) --- + node_graph_get_endpoint :: proc(pNodeGraph: ^node_graph) -> ^node --- + node_graph_read_pcm_frames :: proc(pNodeGraph: ^node_graph, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + node_graph_get_channels :: proc(pNodeGraph: ^node_graph) -> u32 --- + node_graph_get_time :: proc(pNodeGraph: ^node_graph) -> u64 --- + node_graph_set_time :: proc(pNodeGraph: ^node_graph, globalTime: u64) -> result --- +} + + + +/* Data source node. 0 input buses, 1 output bus. Used for reading from a data source. */ +data_source_node_config :: struct { + nodeConfig: node_config, + pDataSource: ^data_source, +} + +data_source_node :: struct { + base: node_base, + pDataSource: ^data_source, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + data_source_node_config_init :: proc(pDataSource: ^data_source) -> data_source_node_config --- + + data_source_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^data_source_node_config, pAllocationCallbacks: ^allocation_callbacks, pDataSourceNode: ^data_source_node) -> result --- + data_source_node_uninit :: proc(pDataSourceNode: ^data_source_node, pAllocationCallbacks: ^allocation_callbacks) --- + data_source_node_set_looping :: proc(pDataSourceNode: ^data_source_node, isLooping: b32) -> result --- + data_source_node_is_looping :: proc(pDataSourceNode: ^data_source_node) -> b32 --- +} + + +/* Splitter Node. 1 input, 2 outputs. Used for splitting/copying a stream so it can be as input into two separate output nodes. */ +splitter_node_config :: struct { + nodeConfig: node_config, + channels: u32, +} + +splitter_node :: struct { + base: node_base, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + splitter_node_config_init :: proc(channels: u32) -> splitter_node_config --- + + splitter_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^splitter_node_config, pAllocationCallbacks: ^allocation_callbacks, pSplitterNode: ^splitter_node) -> result --- + splitter_node_uninit :: proc(pSplitterNode: ^splitter_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Biquad Node +*/ +biquad_node_config :: struct { + nodeConfig: node_config, + biquad: biquad_config, +} + +biquad_node :: struct { + baseNode: node_base, + biquad: biquad, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + biquad_node_config_init :: proc(channels: u32, b0, b1, b2, a0, a1, a2: f32) -> biquad_node_config --- + + biquad_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^biquad_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^biquad_node) -> result --- + biquad_node_reinit :: proc(pConfig: ^biquad_config, pNode: ^biquad_node) -> result --- + biquad_node_uninit :: proc(pNode: ^biquad_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Low Pass Filter Node +*/ +lpf_node_config :: struct { + nodeConfig: node_config, + lpf: lpf_config, +} + +lpf_node :: struct { + baseNode: node_base, + lpf: lpf, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + lpf_node_config_init :: proc(channels, sampleRate: u32, cutoffFrequency: f64, order: u32) -> lpf_node_config --- + + lpf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^lpf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^lpf_node) -> result --- + lpf_node_reinit :: proc(pConfig: ^lpf_config, pNode: ^lpf_node) -> result --- + lpf_node_uninit :: proc(pNode: ^lpf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +High Pass Filter Node +*/ +hpf_node_config :: struct { + nodeConfig: node_config, + hpf: hpf_config, +} + +hpf_node :: struct { + baseNode: node_base, + hpf: hpf, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + hpf_node_config_init :: proc(channels, sampleRate: u32, cutoffFrequency: f64, order: u32) -> hpf_node_config --- + + hpf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^hpf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^hpf_node) -> result --- + hpf_node_reinit :: proc(pConfig: ^hpf_config, pNode: ^hpf_node) -> result --- + hpf_node_uninit :: proc(pNode: ^hpf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Band Pass Filter Node +*/ +bpf_node_config :: struct { + nodeConfig: node_config, + bpf: bpf_config, +} + +bpf_node :: struct { + baseNode: node_base, + bpf: bpf, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + bpf_node_config_init :: proc(channels, sampleRate: u32, cutoffFrequency: f64, order: u32) -> bpf_node_config --- + + bpf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^bpf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^bpf_node) -> result --- + bpf_node_reinit :: proc(pConfig: ^bpf_config, pNode: ^bpf_node) -> result --- + bpf_node_uninit :: proc(pNode: ^bpf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Notching Filter Node +*/ +notch_node_config :: struct { + nodeConfig: node_config, + notch: notch_config, +} + +notch_node :: struct { + baseNode: node_base, + notch: notch2, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + notch_node_config_init :: proc(channels, sampleRate: u32, q, frequency: f64) -> notch_node_config --- + + notch_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^notch_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^notch_node) -> result --- + notch_node_reinit :: proc(pConfig: ^notch_config, pNode: ^notch_node) -> result --- + notch_node_uninit :: proc(pNode: ^notch_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Peaking Filter Node +*/ +peak_node_config :: struct { + nodeConfig: node_config, + peak: peak_config, +} + +peak_node :: struct { + baseNode: node_base, + peak: peak2, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + peak_node_config_init :: proc(channels, sampleRate: u32, gainDB, q, frequency: f64) -> peak_node_config --- + + peak_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^peak_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^peak_node) -> result --- + peak_node_reinit :: proc(pConfig: ^peak_config, pNode: ^peak_node) -> result --- + peak_node_uninit :: proc(pNode: ^peak_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Low Shelf Filter Node +*/ +loshelf_node_config :: struct { + nodeConfig: node_config, + loshelf: loshelf_config, +} + +loshelf_node :: struct { + baseNode: node_base, + loshelf: loshelf2, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + loshelf_node_config_init :: proc(channels, sampleRate: u32, gainDB, q, frequency: f64) -> loshelf_node_config --- + + loshelf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^loshelf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^loshelf_node) -> result --- + loshelf_node_reinit :: proc(pConfig: ^loshelf_config, pNode: ^loshelf_node) -> result --- + loshelf_node_uninit :: proc(pNode: ^loshelf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +High Shelf Filter Node +*/ +hishelf_node_config :: struct { + nodeConfig: node_config, + hishelf: hishelf_config, +} + +hishelf_node :: struct { + baseNode: node_base, + hishelf: hishelf2, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + hishelf_node_config_init :: proc(channels, sampleRate: u32, gainDB, q, frequency: f64) -> hishelf_node_config --- + + hishelf_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^hishelf_node_config, pAllocationCallbacks: ^allocation_callbacks, pNode: ^hishelf_node) -> result --- + hishelf_node_reinit :: proc(pConfig: ^hishelf_config, pNode: ^hishelf_node) -> result --- + hishelf_node_uninit :: proc(pNode: ^hishelf_node, pAllocationCallbacks: ^allocation_callbacks) --- +} + + +/* +Delay Filter Node +*/ +delay_node_config :: struct { + nodeConfig: node_config, + delay: delay_config, +} + +delay_node :: struct { + baseNode: node_base, + delay: delay, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + delay_node_config_init :: proc(channels, sampleRate, delayInFrames: u32, decay: f32) -> delay_node_config --- + + delay_node_init :: proc(pNodeGraph: ^node_graph, pConfig: ^delay_node_config, pAllocationCallbacks: ^allocation_callbacks, pDelayNode: ^delay_node) -> result --- + delay_node_uninit :: proc(pDelayNode: ^delay_node, pAllocationCallbacks: ^allocation_callbacks) --- + delay_node_set_wet :: proc(pDelayNode: ^delay_node, value: f32) --- + delay_node_get_wet :: proc(pDelayNode: ^delay_node) -> f32 --- + delay_node_set_dry :: proc(pDelayNode: ^delay_node, value: f32) --- + delay_node_get_dry :: proc(pDelayNode: ^delay_node) -> f32 --- + delay_node_set_decay :: proc(pDelayNode: ^delay_node, value: f32) --- + delay_node_get_decay :: proc(pDelayNode: ^delay_node) -> f32 --- +} diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin new file mode 100644 index 000000000..c4d722342 --- /dev/null +++ b/vendor/miniaudio/resource_manager.odin @@ -0,0 +1,288 @@ +package miniaudio + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +/************************************************************************************************************************************************************ + +Resource Manager + +************************************************************************************************************************************************************/ + +resource_manager_data_source_flags :: enum c.int { + STREAM = 0x00000001, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ + DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ + ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ + WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ + UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ +} + +/* +Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional. +*/ +resource_manager_pipeline_stage_notification :: struct { + pNotification: ^async_notification, + pFence: ^fence, +} + +resource_manager_pipeline_notifications :: struct { + init: resource_manager_pipeline_stage_notification, /* Initialization of the decoder. */ + done: resource_manager_pipeline_stage_notification, /* Decoding fully completed. */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + resource_manager_pipeline_notifications_init :: proc() -> resource_manager_pipeline_notifications --- +} + + +/* BEGIN BACKWARDS COMPATIBILITY */ +/* TODO: Remove this block in version 0.12. */ +resource_manager_job :: job +resource_manager_job_init :: job_init +JOB_TYPE_RESOURCE_MANAGER_QUEUE_FLAG_NON_BLOCKING :: job_queue_flags.NON_BLOCKING +resource_manager_job_queue_config :: job_queue_config +resource_manager_job_queue_config_init :: job_queue_config_init +resource_manager_job_queue :: job_queue +resource_manager_job_queue_get_heap_size :: job_queue_get_heap_size +resource_manager_job_queue_init_preallocated :: job_queue_init_preallocated +resource_manager_job_queue_init :: job_queue_init +resource_manager_job_queue_uninit :: job_queue_uninit +resource_manager_job_queue_post :: job_queue_post +resource_manager_job_queue_next :: job_queue_next +/* END BACKWARDS COMPATIBILITY */ + + + +/* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ +RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT :: 64 + +resource_manager_flags :: enum c.int { + /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ + NON_BLOCKING = 0x00000001, + + /* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */ + NO_THREADING = 0x00000002, +} + +resource_manager_data_source_config :: struct { + pFilePath: cstring, + pFilePathW: [^]c.wchar_t, + pNotifications: ^resource_manager_pipeline_notifications, + initialSeekPointInPCMFrames: u64, + rangeBegInPCMFrames: u64, + rangeEndInPCMFrames: u64, + loopPointBegInPCMFrames: u64, + loopPointEndInPCMFrames: u64, + isLooping: b32, + flags: u32, +} + +resource_manager_data_supply_type :: enum c.int { + unknown = 0, /* Used for determining whether or the data supply has been initialized. */ + encoded, /* Data supply is an encoded buffer. Connector is ma_decoder. */ + decoded, /* Data supply is a decoded buffer. Connector is ma_audio_buffer. */ + decoded_paged, /* Data supply is a linked list of decoded buffers. Connector is ma_paged_audio_buffer. */ +} + +resource_manager_data_supply :: struct { + type: resource_manager_data_supply_type, /*atomic*/ /* Read and written from different threads so needs to be accessed atomically. */ + backend: struct #raw_union { + encoded: struct { + pData: rawptr, + sizeInBytes: c.size_t, + }, + decoded: struct { + pData: rawptr, + totalFrameCount: u64, + decodedFrameCount: u64, + format: format, + channels: u32, + sampleRate: u32, + }, + decodedPaged: struct { + data: paged_audio_buffer_data, + decodedFrameCount: u64, + sampleRate: u32, + }, + }, +} + +resource_manager_data_buffer_node :: struct { + hashedName32: u32, /* The hashed name. This is the key. */ + refCount: u32, + result: result, /*atomic*/ /* Result from asynchronous loading. When loading set to MA_BUSY. When fully loaded set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. */ + executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ + executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + isDataOwnedByResourceManager: b32, /* Set to true when the underlying data buffer was allocated the resource manager. Set to false if it is owned by the application (via ma_resource_manager_register_*()). */ + data: resource_manager_data_supply, + pParent: ^resource_manager_data_buffer_node, + pChildLo: ^resource_manager_data_buffer_node, + pChildHi: ^resource_manager_data_buffer_node, +} + +resource_manager_data_buffer :: struct { + ds: data_source_base, /* Base data source. A data buffer is a data source. */ + pResourceManager: ^resource_manager, /* A pointer to the resource manager that owns this buffer. */ + pNode: ^resource_manager_data_buffer_node, /* The data node. This is reference counted and is what supplies the data. */ + flags: u32, /* The flags that were passed used to initialize the buffer. */ + executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ + executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + seekTargetInPCMFrames: u64, /* Only updated by the public API. Never written nor read from the job thread. */ + seekToCursorOnNextRead: b32, /* On the next read we need to seek to the frame cursor. */ + result: result, /*atomic*/ /* Keeps track of a result of decoding. Set to MA_BUSY while the buffer is still loading. Set to MA_SUCCESS when loading is finished successfully. Otherwise set to some other code. */ + isLooping: b32, /*atomic*/ /* Can be read and written by different threads at the same time. Must be used atomically. */ + isConnectorInitialized: b32, /* Used for asynchronous loading to ensure we don't try to initialize the connector multiple times while waiting for the node to fully load. */ + connector: struct #raw_union { + decoder: decoder, /* Supply type is ma_resource_manager_data_supply_type_encoded */ + buffer: audio_buffer, /* Supply type is ma_resource_manager_data_supply_type_decoded */ + pagedBuffer: paged_audio_buffer, /* Supply type is ma_resource_manager_data_supply_type_decoded_paged */ + }, /* Connects this object to the node's data supply. */ +} + +resource_manager_data_stream :: struct { + ds: data_source_base, /* Base data source. A data stream is a data source. */ + pResourceManager: ^resource_manager, /* A pointer to the resource manager that owns this data stream. */ + flags: u32, /* The flags that were passed used to initialize the stream. */ + decoder: decoder, /* Used for filling pages with data. This is only ever accessed by the job thread. The public API should never touch this. */ + isDecoderInitialized: b32, /* Required for determining whether or not the decoder should be uninitialized in MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM. */ + totalLengthInPCMFrames: u64, /* This is calculated when first loaded by the MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM. */ + relativeCursor: u32, /* The playback cursor, relative to the current page. Only ever accessed by the public API. Never accessed by the job thread. */ + absoluteCursor: u64, /*atomic*/ /* The playback cursor, in absolute position starting from the start of the file. */ + currentPageIndex: u32, /* Toggles between 0 and 1. Index 0 is the first half of pPageData. Index 1 is the second half. Only ever accessed by the public API. Never accessed by the job thread. */ + executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ + executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + + /* Written by the public API, read by the job thread. */ + isLooping: b32, /*atomic*/ /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */ + + /* Written by the job thread, read by the public API. */ + pPageData: rawptr, /* Buffer containing the decoded data of each page. Allocated once at initialization time. */ + pageFrameCount: [2]u32, /*atomic*/ /* The number of valid PCM frames in each page. Used to determine the last valid frame. */ + + /* Written and read by both the public API and the job thread. These must be atomic. */ + result: result, /*atomic*/ /* Result from asynchronous loading. When loading set to MA_BUSY. When initialized set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. If an error occurs when loading, set to an error code. */ + isDecoderAtEnd: b32, /*atomic*/ /* Whether or not the decoder has reached the end. */ + isPageValid: [2]b32, /*atomic*/ /* Booleans to indicate whether or not a page is valid. Set to false by the public API, set to true by the job thread. Set to false as the pages are consumed, true when they are filled. */ + seekCounter: b32, /*atomic*/ /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */ +} + +resource_manager_data_source :: struct { + backend: struct #raw_union { + buffer: resource_manager_data_buffer, + stream: resource_manager_data_stream, + }, /* Must be the first item because we need the first item to be the data source callbacks for the buffer or stream. */ + + flags: u32, /* The flags that were passed in to ma_resource_manager_data_source_init(). */ + executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ + executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ +} + +resource_manager_config :: struct { + allocationCallbacks: allocation_callbacks, + pLog: ^log, + decodedFormat: format, /* The decoded format to use. Set to ma_format_unknown (default) to use the file's native format. */ + decodedChannels: u32, /* The decoded channel count to use. Set to 0 (default) to use the file's native channel count. */ + decodedSampleRate: u32, /* the decoded sample rate to use. Set to 0 (default) to use the file's native sample rate. */ + jobThreadCount: u32, /* Set to 0 if you want to self-manage your job threads. Defaults to 1. */ + jobQueueCapacity: u32, /* The maximum number of jobs that can fit in the queue at a time. Defaults to MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY. Cannot be zero. */ + flags: u32, + pVFS: ^vfs, /* Can be NULL in which case defaults will be used. */ + ppCustomDecodingBackendVTables: ^[^]decoding_backend_vtable, + customDecodingBackendCount: u32, + pCustomDecodingBackendUserData: rawptr, +} + +resource_manager :: struct { + config: resource_manager_config, + pRootDataBufferNode: ^resource_manager_data_buffer_node, /* The root buffer in the binary tree. */ + dataBufferBSTLock: (struct {} when NO_THREADING else mutex), /* For synchronizing access to the data buffer binary tree. */ + jobThreads: (struct {} when NO_THREADING else [RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT]thread), /* The threads for executing jobs. */ + jobQueue: job_queue, /* Multi-consumer, multi-producer job queue for managing jobs for asynchronous decoding and streaming. */ + defaultVFS: default_vfs, /* Only used if a custom VFS is not specified. */ + log: log, /* Only used if no log was specified in the config. */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + resource_manager_data_source_config_init :: proc() -> resource_manager_data_source_config --- + resource_manager_config_init :: proc() -> resource_manager_config --- + + /* Init. */ + resource_manager_init :: proc(pConfig: ^resource_manager_config, pResourceManager: ^resource_manager) -> result --- + resource_manager_uninit :: proc(pResourceManager: ^resource_manager) --- + resource_manager_get_log :: proc(pResourceManager: ^resource_manager) -> ^log ---; + + /* Registration. */ + resource_manager_register_file :: proc(pResourceManager: ^resource_manager, pFilePath: cstring, flags: u32) -> result --- + resource_manager_register_file_w :: proc(pResourceManager: ^resource_manager, pFilePath: [^]c.wchar_t, flags: u32) -> result --- + resource_manager_register_decoded_data :: proc(pResourceManager: ^resource_manager, pName: cstring, pData: rawptr, frameCount: u64, format: format, channels: u32, sampleRate: u32) -> result --- /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ + resource_manager_register_decoded_data_w :: proc(pResourceManager: ^resource_manager, pName: [^]c.wchar_t, pData: rawptr, frameCount: u64, format: format, channels: u32, sampleRate: u32) -> result --- + resource_manager_register_encoded_data :: proc(pResourceManager: ^resource_manager, pName: cstring, pData: rawptr, sizeInBytes: c.size_t) -> result --- /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ + resource_manager_register_encoded_data_w :: proc(pResourceManager: ^resource_manager, pName: [^]c.wchar_t, pData: rawptr, sizeInBytes: c.size_t) -> result --- + resource_manager_unregister_file :: proc(pResourceManager: ^resource_manager, pFilePath: cstring) -> result --- + resource_manager_unregister_file_w :: proc(pResourceManager: ^resource_manager, pFilePath: [^]c.wchar_t) -> result --- + resource_manager_unregister_data :: proc(pResourceManager: ^resource_manager, pName: cstring) -> result --- + resource_manager_unregister_data_w :: proc(pResourceManager: ^resource_manager, pName: [^]c.wchar_t) -> result --- + + /* Data Buffers. */ + resource_manager_data_buffer_init_ex :: proc(pResourceManager: ^resource_manager, pConfig: ^resource_manager_data_source_config, pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_init :: proc(pResourceManager: ^resource_manager, pFilePath: cstring, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_init_w :: proc(pResourceManager: ^resource_manager, pFilePath: [^]c.wchar_t, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_init_copy :: proc(pResourceManager: ^resource_manager, pExistingDataBuffer, pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_uninit :: proc(pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_read_pcm_frames :: proc(pDataBuffer: ^resource_manager_data_buffer, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + resource_manager_data_buffer_seek_to_pcm_frame :: proc(pDataBuffer: ^resource_manager_data_buffer, frameIndex: u64) -> result --- + resource_manager_data_buffer_get_data_format :: proc(pDataBuffer: ^resource_manager_data_buffer, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + resource_manager_data_buffer_get_cursor_in_pcm_frames :: proc(pDataBuffer: ^resource_manager_data_buffer, pCursor: ^u64) -> result --- + resource_manager_data_buffer_get_length_in_pcm_frames :: proc(pDataBuffer: ^resource_manager_data_buffer, pLength: ^u64) -> result --- + resource_manager_data_buffer_result :: proc(pDataBuffer: ^resource_manager_data_buffer) -> result --- + resource_manager_data_buffer_set_looping :: proc(pDataBuffer: ^resource_manager_data_buffer, isLooping: b32) -> result --- + resource_manager_data_buffer_is_looping :: proc(pDataBuffer: ^resource_manager_data_buffer) -> b32 --- + resource_manager_data_buffer_get_available_frames :: proc(pDataBuffer: ^resource_manager_data_buffer, pAvailableFrames: ^u64) -> result --- + + /* Data Streams. */ + resource_manager_data_stream_init_ex :: proc(pResourceManager: ^resource_manager, pConfig: ^resource_manager_data_source_config, pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_init :: proc(pResourceManager: ^resource_manager, pFilePath: cstring, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_init_w :: proc(pResourceManager: ^resource_manager, pFilePath: [^]c.wchar_t, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_uninit :: proc(pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_read_pcm_frames :: proc(pDataStream: ^resource_manager_data_stream, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + resource_manager_data_stream_seek_to_pcm_frame :: proc(pDataStream: ^resource_manager_data_stream, frameIndex: u64) -> result --- + resource_manager_data_stream_get_data_format :: proc(pDataStream: ^resource_manager_data_stream, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + resource_manager_data_stream_get_cursor_in_pcm_frames :: proc(pDataStream: ^resource_manager_data_stream, pCursor: ^u64) -> result --- + resource_manager_data_stream_get_length_in_pcm_frames :: proc(pDataStream: ^resource_manager_data_stream, pLength: ^u64) -> result --- + resource_manager_data_stream_result :: proc(pDataStream: ^resource_manager_data_stream) -> result --- + resource_manager_data_stream_set_looping :: proc(pDataStream: ^resource_manager_data_stream, isLooping: b32) -> result --- + resource_manager_data_stream_is_looping :: proc(pDataStream: ^resource_manager_data_stream) -> b32 --- + resource_manager_data_stream_get_available_frames :: proc(pDataStream: ^resource_manager_data_stream, pAvailableFrames: ^u64) -> result --- + + /* Data Sources. */ + resource_manager_data_source_init_ex :: proc(pResourceManager: ^resource_manager, pConfig: ^resource_manager_data_source_config, pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_init :: proc(pResourceManager: ^resource_manager, pName: cstring, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_init_w :: proc(pResourceManager: ^resource_manager, pName: [^]c.wchar_t, flags: u32, pNotifications: ^resource_manager_pipeline_notifications, pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_init_copy :: proc(pResourceManager: ^resource_manager, pExistingDataSource, pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_uninit :: proc(pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_read_pcm_frames :: proc(pDataSource: ^resource_manager_data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- + resource_manager_data_source_seek_to_pcm_frame :: proc(pDataSource: ^resource_manager_data_source, frameIndex: u64) -> result --- + resource_manager_data_source_get_data_format :: proc(pDataSource: ^resource_manager_data_source, pFormat: ^format, pChannels, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + resource_manager_data_source_get_cursor_in_pcm_frames :: proc(pDataSource: ^resource_manager_data_source, pCursor: ^u64) -> result --- + resource_manager_data_source_get_length_in_pcm_frames :: proc(pDataSource: ^resource_manager_data_source, pLength: ^u64) -> result --- + resource_manager_data_source_result :: proc(pDataSource: ^resource_manager_data_source) -> result --- + resource_manager_data_source_set_looping :: proc(pDataSource: ^resource_manager_data_source, isLooping: b32) -> result --- + resource_manager_data_source_is_looping :: proc(pDataSource: ^resource_manager_data_source) -> b32 --- + resource_manager_data_source_get_available_frames :: proc(pDataSource: ^resource_manager_data_source, pAvailableFrames: ^u64) -> result --- + + /* Job management. */ + resource_manager_post_job :: proc(pResourceManager: ^resource_manager, pJob: ^job) -> result --- + resource_manager_post_job_quit :: proc(pResourceManager: ^resource_manager) -> result --- /* Helper for posting a quit job. */ + resource_manager_next_job :: proc(pResourceManager: ^resource_manager, pJob: ^job) -> result --- + resource_manager_process_job :: proc(pResourceManager: ^resource_manager, pJob: ^job) -> result --- /* DEPRECATED. Use ma_job_process(). Will be removed in version 0.12. */ + resource_manager_process_next_job :: proc(pResourceManager: ^resource_manager) -> result --- /* Returns MA_CANCELLED if a MA_JOB_TYPE_QUIT job is found. In non-blocking mode, returns MA_NO_DATA_AVAILABLE if no jobs are available. */ +} diff --git a/vendor/miniaudio/src/miniaudio.h b/vendor/miniaudio/src/miniaudio.h index 5793f20d6..f774f0d5f 100644 --- a/vendor/miniaudio/src/miniaudio.h +++ b/vendor/miniaudio/src/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.10.42 - 2021-08-22 +miniaudio - v0.11.9 - 2022-04-20 David Reid - mackron@gmail.com @@ -12,7 +12,8 @@ GitHub: https://github.com/mackron/miniaudio /* 1. Introduction =============== -miniaudio is a single file library for audio playback and capture. To use it, do the following in one .c file: +miniaudio is a single file library for audio playback and capture. To use it, do the following in +one .c file: ```c #define MINIAUDIO_IMPLEMENTATION @@ -21,16 +22,44 @@ miniaudio is a single file library for audio playback and capture. To use it, do You can do `#include "miniaudio.h"` in other parts of the program just like any other header. -miniaudio uses the concept of a "device" as the abstraction for physical devices. The idea is that you choose a physical device to emit or capture audio from, -and then move data to/from the device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a callback which you specify when -initializing the device. +miniaudio includes both low level and high level APIs. The low level API is good for those who want +to do all of their mixing themselves and only require a light weight interface to the underlying +audio device. The high level API is good for those who have complex mixing and effect requirements. -When initializing the device you first need to configure it. The device configuration allows you to specify things like the format of the data delivered via -the callback, the size of the internal buffer and the ID of the device you want to emit or capture audio from. +In miniaudio, objects are transparent structures. Unlike many other libraries, there are no handles +to opaque objects which means you need to allocate memory for objects yourself. In the examples +presented in this documentation you will often see objects declared on the stack. You need to be +careful when translating these examples to your own code so that you don't accidentally declare +your objects on the stack and then cause them to become invalid once the function returns. In +addition, you must ensure the memory address of your objects remain the same throughout their +lifetime. You therefore cannot be making copies of your objects. -Once you have the device configuration set up you can initialize the device. When initializing a device you need to allocate memory for the device object -beforehand. This gives the application complete control over how the memory is allocated. In the example below we initialize a playback device on the stack, -but you could allocate it on the heap if that suits your situation better. +A config/init pattern is used throughout the entire library. The idea is that you set up a config +object and pass that into the initialization routine. The advantage to this system is that the +config object can be initialized with logical defaults and new properties added to it without +breaking the API. The config object can be allocated on the stack and does not need to be +maintained after initialization of the corresponding object. + + +1.1. Low Level API +------------------ +The low level API gives you access to the raw audio data of an audio device. It supports playback, +capture, full-duplex and loopback (WASAPI only). You can enumerate over devices to determine which +physical device(s) you want to connect to. + +The low level API uses the concept of a "device" as the abstraction for physical devices. The idea +is that you choose a physical device to emit or capture audio from, and then move data to/from the +device when miniaudio tells you to. Data is delivered to and from devices asynchronously via a +callback which you specify when initializing the device. + +When initializing the device you first need to configure it. The device configuration allows you to +specify things like the format of the data delivered via the callback, the size of the internal +buffer and the ID of the device you want to emit or capture audio from. + +Once you have the device configuration set up you can initialize the device. When initializing a +device you need to allocate memory for the device object beforehand. This gives the application +complete control over how the memory is allocated. In the example below we initialize a playback +device on the stack, but you could allocate it on the heap if that suits your situation better. ```c void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) @@ -63,20 +92,27 @@ but you could allocate it on the heap if that suits your situation better. } ``` -In the example above, `data_callback()` is where audio data is written and read from the device. The idea is in playback mode you cause sound to be emitted -from the speakers by writing audio data to the output buffer (`pOutput` in the example). In capture mode you read data from the input buffer (`pInput`) to -extract sound captured by the microphone. The `frameCount` parameter tells you how many frames can be written to the output buffer and read from the input -buffer. A "frame" is one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2 samples: one for the left, one for the right. -The channel count is defined by the device config. The size in bytes of an individual sample is defined by the sample format which is also specified in the -device config. Multi-channel audio data is always interleaved, which means the samples for each frame are stored next to each other in memory. For example, in -a stereo stream the first pair of samples will be the left and right samples for the first frame, the second pair of samples will be the left and right samples -for the second frame, etc. +In the example above, `data_callback()` is where audio data is written and read from the device. +The idea is in playback mode you cause sound to be emitted from the speakers by writing audio data +to the output buffer (`pOutput` in the example). In capture mode you read data from the input +buffer (`pInput`) to extract sound captured by the microphone. The `frameCount` parameter tells you +how many frames can be written to the output buffer and read from the input buffer. A "frame" is +one sample for each channel. For example, in a stereo stream (2 channels), one frame is 2 +samples: one for the left, one for the right. The channel count is defined by the device config. +The size in bytes of an individual sample is defined by the sample format which is also specified +in the device config. Multi-channel audio data is always interleaved, which means the samples for +each frame are stored next to each other in memory. For example, in a stereo stream the first pair +of samples will be the left and right samples for the first frame, the second pair of samples will +be the left and right samples for the second frame, etc. -The configuration of the device is defined by the `ma_device_config` structure. The config object is always initialized with `ma_device_config_init()`. It's -important to always initialize the config with this function as it initializes it with logical defaults and ensures your program doesn't break when new members -are added to the `ma_device_config` structure. The example above uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` -takes a single parameter, which is whether or not the device is a playback, capture, duplex or loopback device (loopback devices are not supported on all -backends). The `config.playback.format` member sets the sample format which can be one of the following (all formats are native-endian): +The configuration of the device is defined by the `ma_device_config` structure. The config object +is always initialized with `ma_device_config_init()`. It's important to always initialize the +config with this function as it initializes it with logical defaults and ensures your program +doesn't break when new members are added to the `ma_device_config` structure. The example above +uses a fairly simple and standard device configuration. The call to `ma_device_config_init()` takes +a single parameter, which is whether or not the device is a playback, capture, duplex or loopback +device (loopback devices are not supported on all backends). The `config.playback.format` member +sets the sample format which can be one of the following (all formats are native-endian): +---------------+----------------------------------------+---------------------------+ | Symbol | Description | Range | @@ -88,22 +124,30 @@ backends). The `config.playback.format` member sets the sample format which can | ma_format_u8 | 8-bit unsigned integer | [0, 255] | +---------------+----------------------------------------+---------------------------+ -The `config.playback.channels` member sets the number of channels to use with the device. The channel count cannot exceed MA_MAX_CHANNELS. The -`config.sampleRate` member sets the sample rate (which must be the same for both playback and capture in full-duplex configurations). This is usually set to -44100 or 48000, but can be set to anything. It's recommended to keep this between 8000 and 384000, however. +The `config.playback.channels` member sets the number of channels to use with the device. The +channel count cannot exceed MA_MAX_CHANNELS. The `config.sampleRate` member sets the sample rate +(which must be the same for both playback and capture in full-duplex configurations). This is +usually set to 44100 or 48000, but can be set to anything. It's recommended to keep this between +8000 and 384000, however. -Note that leaving the format, channel count and/or sample rate at their default values will result in the internal device's native configuration being used -which is useful if you want to avoid the overhead of miniaudio's automatic data conversion. +Note that leaving the format, channel count and/or sample rate at their default values will result +in the internal device's native configuration being used which is useful if you want to avoid the +overhead of miniaudio's automatic data conversion. -In addition to the sample format, channel count and sample rate, the data callback and user data pointer are also set via the config. The user data pointer is -not passed into the callback as a parameter, but is instead set to the `pUserData` member of `ma_device` which you can access directly since all miniaudio -structures are transparent. +In addition to the sample format, channel count and sample rate, the data callback and user data +pointer are also set via the config. The user data pointer is not passed into the callback as a +parameter, but is instead set to the `pUserData` member of `ma_device` which you can access +directly since all miniaudio structures are transparent. -Initializing the device is done with `ma_device_init()`. This will return a result code telling you what went wrong, if anything. On success it will return -`MA_SUCCESS`. After initialization is complete the device will be in a stopped state. To start it, use `ma_device_start()`. Uninitializing the device will stop -it, which is what the example above does, but you can also stop the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again. -Note that it's important to never stop or start the device from inside the callback. This will result in a deadlock. Instead you set a variable or signal an -event indicating that the device needs to stop and handle it in a different thread. The following APIs must never be called inside the callback: +Initializing the device is done with `ma_device_init()`. This will return a result code telling you +what went wrong, if anything. On success it will return `MA_SUCCESS`. After initialization is +complete the device will be in a stopped state. To start it, use `ma_device_start()`. +Uninitializing the device will stop it, which is what the example above does, but you can also stop +the device with `ma_device_stop()`. To resume the device simply call `ma_device_start()` again. +Note that it's important to never stop or start the device from inside the callback. This will +result in a deadlock. Instead you set a variable or signal an event indicating that the device +needs to stop and handle it in a different thread. The following APIs must never be called inside +the callback: ```c ma_device_init() @@ -113,12 +157,14 @@ event indicating that the device needs to stop and handle it in a different thre ma_device_stop() ``` -You must never try uninitializing and reinitializing a device inside the callback. You must also never try to stop and start it from inside the callback. There -are a few other things you shouldn't do in the callback depending on your requirements, however this isn't so much a thread-safety thing, but rather a -real-time processing thing which is beyond the scope of this introduction. +You must never try uninitializing and reinitializing a device inside the callback. You must also +never try to stop and start it from inside the callback. There are a few other things you shouldn't +do in the callback depending on your requirements, however this isn't so much a thread-safety +thing, but rather a real-time processing thing which is beyond the scope of this introduction. -The example above demonstrates the initialization of a playback device, but it works exactly the same for capture. All you need to do is change the device type -from `ma_device_type_playback` to `ma_device_type_capture` when setting up the config, like so: +The example above demonstrates the initialization of a playback device, but it works exactly the +same for capture. All you need to do is change the device type from `ma_device_type_playback` to +`ma_device_type_capture` when setting up the config, like so: ```c ma_device_config config = ma_device_config_init(ma_device_type_capture); @@ -126,8 +172,9 @@ from `ma_device_type_playback` to `ma_device_type_capture` when setting up the c config.capture.channels = MY_CHANNEL_COUNT; ``` -In the data callback you just read from the input buffer (`pInput` in the example above) and leave the output buffer alone (it will be set to NULL when the -device type is set to `ma_device_type_capture`). +In the data callback you just read from the input buffer (`pInput` in the example above) and leave +the output buffer alone (it will be set to NULL when the device type is set to +`ma_device_type_capture`). These are the available device types and how you should handle the buffers in the callback: @@ -140,23 +187,29 @@ These are the available device types and how you should handle the buffers in th | ma_device_type_loopback | Read from input buffer, leave output buffer untouched. | +-------------------------+--------------------------------------------------------+ -You will notice in the example above that the sample format and channel count is specified separately for playback and capture. This is to support different -data formats between the playback and capture devices in a full-duplex system. An example may be that you want to capture audio data as a monaural stream (one -channel), but output sound to a stereo speaker system. Note that if you use different formats between playback and capture in a full-duplex configuration you -will need to convert the data yourself. There are functions available to help you do this which will be explained later. +You will notice in the example above that the sample format and channel count is specified +separately for playback and capture. This is to support different data formats between the playback +and capture devices in a full-duplex system. An example may be that you want to capture audio data +as a monaural stream (one channel), but output sound to a stereo speaker system. Note that if you +use different formats between playback and capture in a full-duplex configuration you will need to +convert the data yourself. There are functions available to help you do this which will be +explained later. -The example above did not specify a physical device to connect to which means it will use the operating system's default device. If you have multiple physical -devices connected and you want to use a specific one you will need to specify the device ID in the configuration, like so: +The example above did not specify a physical device to connect to which means it will use the +operating system's default device. If you have multiple physical devices connected and you want to +use a specific one you will need to specify the device ID in the configuration, like so: ```c config.playback.pDeviceID = pMyPlaybackDeviceID; // Only if requesting a playback or duplex device. config.capture.pDeviceID = pMyCaptureDeviceID; // Only if requesting a capture, duplex or loopback device. ``` -To retrieve the device ID you will need to perform device enumeration, however this requires the use of a new concept called the "context". Conceptually -speaking the context sits above the device. There is one context to many devices. The purpose of the context is to represent the backend at a more global level -and to perform operations outside the scope of an individual device. Mainly it is used for performing run-time linking against backend libraries, initializing -backends and enumerating devices. The example below shows how to enumerate devices. +To retrieve the device ID you will need to perform device enumeration, however this requires the +use of a new concept called the "context". Conceptually speaking the context sits above the device. +There is one context to many devices. The purpose of the context is to represent the backend at a +more global level and to perform operations outside the scope of an individual device. Mainly it is +used for performing run-time linking against backend libraries, initializing backends and +enumerating devices. The example below shows how to enumerate devices. ```c ma_context context; @@ -197,44 +250,236 @@ backends and enumerating devices. The example below shows how to enumerate devic ma_context_uninit(&context); ``` -The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`. The first parameter is a pointer to a list of `ma_backend` -values which are used to override the default backend priorities. When this is NULL, as in this example, miniaudio's default priorities are used. The second -parameter is the number of backends listed in the array pointed to by the first parameter. The third parameter is a pointer to a `ma_context_config` object -which can be NULL, in which case defaults are used. The context configuration is used for setting the logging callback, custom memory allocation callbacks, -user-defined data and some backend-specific configurations. +The first thing we do in this example is initialize a `ma_context` object with `ma_context_init()`. +The first parameter is a pointer to a list of `ma_backend` values which are used to override the +default backend priorities. When this is NULL, as in this example, miniaudio's default priorities +are used. The second parameter is the number of backends listed in the array pointed to by the +first parameter. The third parameter is a pointer to a `ma_context_config` object which can be +NULL, in which case defaults are used. The context configuration is used for setting the logging +callback, custom memory allocation callbacks, user-defined data and some backend-specific +configurations. -Once the context has been initialized you can enumerate devices. In the example above we use the simpler `ma_context_get_devices()`, however you can also use a -callback for handling devices by using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer to a pointer that will, -upon output, be set to a pointer to a buffer containing a list of `ma_device_info` structures. You also provide a pointer to an unsigned integer that will -receive the number of items in the returned buffer. Do not free the returned buffers as their memory is managed internally by miniaudio. +Once the context has been initialized you can enumerate devices. In the example above we use the +simpler `ma_context_get_devices()`, however you can also use a callback for handling devices by +using `ma_context_enumerate_devices()`. When using `ma_context_get_devices()` you provide a pointer +to a pointer that will, upon output, be set to a pointer to a buffer containing a list of +`ma_device_info` structures. You also provide a pointer to an unsigned integer that will receive +the number of items in the returned buffer. Do not free the returned buffers as their memory is +managed internally by miniaudio. -The `ma_device_info` structure contains an `id` member which is the ID you pass to the device config. It also contains the name of the device which is useful -for presenting a list of devices to the user via the UI. +The `ma_device_info` structure contains an `id` member which is the ID you pass to the device +config. It also contains the name of the device which is useful for presenting a list of devices +to the user via the UI. -When creating your own context you will want to pass it to `ma_device_init()` when initializing the device. Passing in NULL, like we do in the first example, -will result in miniaudio creating the context for you, which you don't want to do since you've already created a context. Note that internally the context is -only tracked by it's pointer which means you must not change the location of the `ma_context` object. If this is an issue, consider using `malloc()` to -allocate memory for the context. +When creating your own context you will want to pass it to `ma_device_init()` when initializing the +device. Passing in NULL, like we do in the first example, will result in miniaudio creating the +context for you, which you don't want to do since you've already created a context. Note that +internally the context is only tracked by it's pointer which means you must not change the location +of the `ma_context` object. If this is an issue, consider using `malloc()` to allocate memory for +the context. + + +1.2. High Level API +------------------- +The high level API consists of three main parts: + + * Resource management for loading and streaming sounds. + * A node graph for advanced mixing and effect processing. + * A high level "engine" that wraps around the resource manager and node graph. + +The resource manager (`ma_resource_manager`) is used for loading sounds. It supports loading sounds +fully into memory and also streaming. It will also deal with reference counting for you which +avoids the same sound being loaded multiple times. + +The node graph is used for mixing and effect processing. The idea is that you connect a number of +nodes into the graph by connecting each node's outputs to another node's inputs. Each node can +implement it's own effect. By chaining nodes together, advanced mixing and effect processing can +be achieved. + +The engine encapsulates both the resource manager and the node graph to create a simple, easy to +use high level API. The resource manager and node graph APIs are covered in more later sections of +this manual. + +The code below shows how you can initialize an engine using it's default configuration. + + ```c + ma_result result; + ma_engine engine; + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +This creates an engine instance which will initialize a device internally which you can access with +`ma_engine_get_device()`. It will also initialize a resource manager for you which can be accessed +with `ma_engine_get_resource_manager()`. The engine itself is a node graph (`ma_node_graph`) which +means you can pass a pointer to the engine object into any of the `ma_node_graph` APIs (with a +cast). Alternatively, you can use `ma_engine_get_node_graph()` instead of a cast. + +Note that all objects in miniaudio, including the `ma_engine` object in the example above, are +transparent structures. There are no handles to opaque structures in miniaudio which means you need +to be mindful of how you declare them. In the example above we are declaring it on the stack, but +this will result in the struct being invalidated once the function encapsulating it returns. If +allocating the engine on the heap is more appropriate, you can easily do so with a standard call +to `malloc()` or whatever heap allocation routine you like: + + ```c + ma_engine* pEngine = malloc(sizeof(*pEngine)); + ``` + +The `ma_engine` API uses the same config/init pattern used all throughout miniaudio. To configure +an engine, you can fill out a `ma_engine_config` object and pass it into the first parameter of +`ma_engine_init()`: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &myCustomResourceManager; // <-- Initialized as some earlier stage. + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; + } + ``` + +This creates an engine instance using a custom config. In this particular example it's showing how +you can specify a custom resource manager rather than having the engine initialize one internally. +This is particularly useful if you want to have multiple engine's share the same resource manager. + +The engine must be uninitialized with `ma_engine_uninit()` when it's no longer needed. + +By default the engine will be started, but nothing will be playing because no sounds have been +initialized. The easiest but least flexible way of playing a sound is like so: + + ```c + ma_engine_play_sound(&engine, "my_sound.wav", NULL); + ``` + +This plays what miniaudio calls an "inline" sound. It plays the sound once, and then puts the +internal sound up for recycling. The last parameter is used to specify which sound group the sound +should be associated with which will be explained later. This particular way of playing a sound is +simple, but lacks flexibility and features. A more flexible way of playing a sound is to first +initialize a sound: + + ```c + ma_result result; + ma_sound sound; + + result = ma_sound_init_from_file(&engine, "my_sound.wav", 0, NULL, NULL, &sound); + if (result != MA_SUCCESS) { + return result; + } + + ma_sound_start(&sound); + ``` + +This returns a `ma_sound` object which represents a single instance of the specified sound file. If +you want to play the same file multiple times simultaneously, you need to create one sound for each +instance. + +Sounds should be uninitialized with `ma_sound_uninit()`. + +Sounds are not started by default. Start a sound with `ma_sound_start()` and stop it with +`ma_sound_stop()`. When a sound is stopped, it is not rewound to the start. Use +`ma_sound_seek_to_pcm_frames(&sound, 0)` to seek back to the start of a sound. By default, starting +and stopping sounds happens immediately, but sometimes it might be convenient to schedule the sound +the be started and/or stopped at a specific time. This can be done with the following functions: + + ```c + ma_sound_set_start_time_in_pcm_frames() + ma_sound_set_start_time_in_milliseconds() + ma_sound_set_stop_time_in_pcm_frames() + ma_sound_set_stop_time_in_milliseconds() + ``` + +The start/stop time needs to be specified based on the absolute timer which is controlled by the +engine. The current global time time in PCM frames can be retrieved with `ma_engine_get_time()`. +The engine's global time can be changed with `ma_engine_set_time()` for synchronization purposes if +required. Note that scheduling a start time still requires an explicit call to `ma_sound_start()` +before anything will play: + + ```c + ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2); + ma_sound_start(&sound); + ``` + +The third parameter of `ma_sound_init_from_file()` is a set of flags that control how the sound be +loaded and a few options on which features should be enabled for that sound. By default, the sound +is synchronously loaded fully into memory straight from the file system without any kind of +decoding. If you want to decode the sound before storing it in memory, you need to specify the +`MA_SOUND_FLAG_DECODE` flag. This is useful if you want to incur the cost of decoding at an earlier +stage, such as a loading stage. Without this option, decoding will happen dynamically at mixing +time which might be too expensive on the audio thread. + +If you want to load the sound asynchronously, you can specify the `MA_SOUND_FLAG_ASYNC` flag. This +will result in `ma_sound_init_from_file()` returning quickly, but the sound will not start playing +until the sound has had some audio decoded. + +The fourth parameter is a pointer to sound group. A sound group is used as a mechanism to organise +sounds into groups which have their own effect processing and volume control. An example is a game +which might have separate groups for sfx, voice and music. Each of these groups have their own +independent volume control. Use `ma_sound_group_init()` or `ma_sound_group_init_ex()` to initialize +a sound group. + +Sounds and sound groups are nodes in the engine's node graph and can be plugged into any `ma_node` +API. This makes it possible to connect sounds and sound groups to effect nodes to produce complex +effect chains. + +A sound can have it's volume changed with `ma_sound_set_volume()`. If you prefer decibel volume +control you can use `ma_volume_db_to_linear()` to convert from decibel representation to linear. + +Panning and pitching is supported with `ma_sound_set_pan()` and `ma_sound_set_pitch()`. If you know +a sound will never have it's pitch changed with `ma_sound_set_pitch()` or via the doppler effect, +you can specify the `MA_SOUND_FLAG_NO_PITCH` flag when initializing the sound for an optimization. + +By default, sounds and sound groups have spatialization enabled. If you don't ever want to +spatialize your sounds, initialize the sound with the `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. The +spatialization model is fairly simple and is roughly on feature parity with OpenAL. HRTF and +environmental occlusion are not currently supported, but planned for the future. The supported +features include: + + * Sound and listener positioning and orientation with cones + * Attenuation models: none, inverse, linear and exponential + * Doppler effect + +Sounds can be faded in and out with `ma_sound_set_fade_in_pcm_frames()`. + +To check if a sound is currently playing, you can use `ma_sound_is_playing()`. To check if a sound +is at the end, use `ma_sound_at_end()`. Looping of a sound can be controlled with +`ma_sound_set_looping()`. Use `ma_sound_is_looping()` to check whether or not the sound is looping. 2. Building =========== -miniaudio should work cleanly out of the box without the need to download or install any dependencies. See below for platform-specific details. +miniaudio should work cleanly out of the box without the need to download or install any +dependencies. See below for platform-specific details. 2.1. Windows ------------ -The Windows build should compile cleanly on all popular compilers without the need to configure any include paths nor link to any libraries. +The Windows build should compile cleanly on all popular compilers without the need to configure any +include paths nor link to any libraries. + +The UWP build may require linking to mmdevapi.lib if you get errors about an unresolved external +symbol for `ActivateAudioInterfaceAsync()`. + 2.2. macOS and iOS ------------------ -The macOS build should compile cleanly without the need to download any dependencies nor link to any libraries or frameworks. The iOS build needs to be -compiled as Objective-C and will need to link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling through the command line -requires linking to `-lpthread` and `-lm`. +The macOS build should compile cleanly without the need to download any dependencies nor link to +any libraries or frameworks. The iOS build needs to be compiled as Objective-C and will need to +link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling +through the command line requires linking to `-lpthread` and `-lm`. -Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's notarization process. To fix this there are two options. The -first is to use the `MA_NO_RUNTIME_LINKING` option, like so: +Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's +notarization process. To fix this there are two options. The first is to use the +`MA_NO_RUNTIME_LINKING` option, like so: ```c #ifdef __APPLE__ @@ -244,8 +489,9 @@ first is to use the `MA_NO_RUNTIME_LINKING` option, like so: #include "miniaudio.h" ``` -This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. Alternatively, if you would rather keep using runtime -linking you can add the following to your entitlements.xcent file: +This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. +Alternatively, if you would rather keep using runtime linking you can add the following to your +entitlements.xcent file: ``` com.apple.security.cs.allow-dyld-environment-variables @@ -254,26 +500,37 @@ linking you can add the following to your entitlements.xcent file: ``` +See this discussion for more info: https://github.com/mackron/miniaudio/issues/203. + 2.3. Linux ---------- -The Linux build only requires linking to `-ldl`, `-lpthread` and `-lm`. You do not need any development packages. +The Linux build only requires linking to `-ldl`, `-lpthread` and `-lm`. You do not need any +development packages. You may need to link with `-latomic` if you're compiling for 32-bit ARM. + 2.4. BSD -------- -The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio(4), OpenBSD uses sndio and FreeBSD uses OSS. +The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio(4), OpenBSD uses +sndio and FreeBSD uses OSS. You may need to link with `-latomic` if you're compiling for 32-bit +ARM. + 2.5. Android ------------ -AAudio is the highest priority backend on Android. This should work out of the box without needing any kind of compiler configuration. Support for AAudio -starts with Android 8 which means older versions will fall back to OpenSL|ES which requires API level 16+. +AAudio is the highest priority backend on Android. This should work out of the box without needing +any kind of compiler configuration. Support for AAudio starts with Android 8 which means older +versions will fall back to OpenSL|ES which requires API level 16+. + +There have been reports that the OpenSL|ES backend fails to initialize on some Android based +devices due to `dlopen()` failing to open "libOpenSLES.so". If this happens on your platform +you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. -There have been reports that the OpenSL|ES backend fails to initialize on some Android based devices due to `dlopen()` failing to open "libOpenSLES.so". If -this happens on your platform you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. 2.6. Emscripten --------------- -The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. You cannot use -std=c* compiler flags, nor -ansi. +The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. +You cannot use `-std=c*` compiler flags, nor `-ansi`. 2.7. Build Options @@ -366,28 +623,26 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea +----------------------------------+--------------------------------------------------------------------+ | MA_NO_MP3 | Disables the built-in MP3 decoder. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_DEVICE_IO | Disables playback and recording. This will disable ma_context and | - | | ma_device APIs. This is useful if you only want to use miniaudio's | - | | data conversion and/or decoding APIs. | + | MA_NO_DEVICE_IO | Disables playback and recording. This will disable `ma_context` | + | | and `ma_device` APIs. This is useful if you only want to use | + | | miniaudio's data conversion and/or decoding APIs. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_THREADING | Disables the ma_thread, ma_mutex, ma_semaphore and ma_event APIs. | - | | This option is useful if you only need to use miniaudio for data | - | | conversion, decoding and/or encoding. Some families of APIs | - | | require threading which means the following options must also be | - | | set: | + | MA_NO_THREADING | Disables the `ma_thread`, `ma_mutex`, `ma_semaphore` and | + | | `ma_event` APIs. This option is useful if you only need to use | + | | miniaudio for data conversion, decoding and/or encoding. Some | + | | families of APIsrequire threading which means the following | + | | options must also be set: | | | | | | ``` | | | MA_NO_DEVICE_IO | | | ``` | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_GENERATION | Disables generation APIs such a ma_waveform and ma_noise. | + | MA_NO_GENERATION | Disables generation APIs such a `ma_waveform` and `ma_noise`. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_SSE2 | Disables SSE2 optimizations. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_AVX2 | Disables AVX2 optimizations. | +----------------------------------+--------------------------------------------------------------------+ - | MA_NO_AVX512 | Disables AVX-512 optimizations. | - +----------------------------------+--------------------------------------------------------------------+ | MA_NO_NEON | Disables NEON optimizations. | +----------------------------------+--------------------------------------------------------------------+ | MA_NO_RUNTIME_LINKING | Disables runtime linking. This is useful for passing Apple's | @@ -399,47 +654,47 @@ The Emscripten build emits Web Audio JavaScript directly and should compile clea | | You may need to enable this if your target platform does not allow | | | runtime linking via `dlopen()`. | +----------------------------------+--------------------------------------------------------------------+ - | MA_DEBUG_OUTPUT | Enable processing of MA_LOG_LEVEL_DEBUG messages and `printf()` | - | | output. | + | MA_DEBUG_OUTPUT | Enable `printf()` output of debug logs (`MA_LOG_LEVEL_DEBUG`). | +----------------------------------+--------------------------------------------------------------------+ | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to | | | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | +----------------------------------+--------------------------------------------------------------------+ | MA_API | Controls how public APIs should be decorated. Default is `extern`. | +----------------------------------+--------------------------------------------------------------------+ - | MA_DLL | If set, configures MA_API to either import or export APIs | - | | depending on whether or not the implementation is being defined. | - | | If defining the implementation, MA_API will be configured to | - | | export. Otherwise it will be configured to import. This has no | - | | effect if MA_API is defined externally. | - +----------------------------------+--------------------------------------------------------------------+ 3. Definitions ============== -This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity in the use of terms throughout the audio space, so this -section is intended to clarify how miniaudio uses each term. +This section defines common terms used throughout miniaudio. Unfortunately there is often ambiguity +in the use of terms throughout the audio space, so this section is intended to clarify how miniaudio +uses each term. 3.1. Sample ----------- -A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit floating point number. +A sample is a single unit of audio data. If the sample format is f32, then one sample is one 32-bit +floating point number. 3.2. Frame / PCM Frame ---------------------- -A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 samples, a mono frame is 1 sample, a 5.1 surround sound frame -is 6 samples, etc. The terms "frame" and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. If ever miniaudio -needs to refer to a compressed frame, such as a FLAC frame, it will always clarify what it's referring to with something like "FLAC frame". +A frame is a group of samples equal to the number of channels. For a stereo stream a frame is 2 +samples, a mono frame is 1 sample, a 5.1 surround sound frame is 6 samples, etc. The terms "frame" +and "PCM frame" are the same thing in miniaudio. Note that this is different to a compressed frame. +If ever miniaudio needs to refer to a compressed frame, such as a FLAC frame, it will always +clarify what it's referring to with something like "FLAC frame". 3.3. Channel ------------ -A stream of monaural audio that is emitted from an individual speaker in a speaker system, or received from an individual microphone in a microphone system. A -stereo stream has two channels (a left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio systems refer to a channel as -a complex audio stream that's mixed with other channels to produce the final mix - this is completely different to miniaudio's use of the term "channel" and -should not be confused. +A stream of monaural audio that is emitted from an individual speaker in a speaker system, or +received from an individual microphone in a microphone system. A stereo stream has two channels (a +left channel, and a right channel), a 5.1 surround sound system has 6 channels, etc. Some audio +systems refer to a channel as a complex audio stream that's mixed with other channels to produce +the final mix - this is completely different to miniaudio's use of the term "channel" and should +not be confused. 3.4. Sample Rate ---------------- -The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number of PCM frames that are processed per second. +The sample rate in miniaudio is always expressed in Hz, such as 44100, 48000, etc. It's the number +of PCM frames that are processed per second. 3.5. Formats ------------ @@ -459,10 +714,1685 @@ All formats are native-endian. -4. Decoding +4. Data Sources +=============== +The data source abstraction in miniaudio is used for retrieving audio data from some source. A few +examples include `ma_decoder`, `ma_noise` and `ma_waveform`. You will need to be familiar with data +sources in order to make sense of some of the higher level concepts in miniaudio. + +The `ma_data_source` API is a generic interface for reading from a data source. Any object that +implements the data source interface can be plugged into any `ma_data_source` function. + +To read data from a data source: + + ```c + ma_result result; + ma_uint64 framesRead; + + result = ma_data_source_read_pcm_frames(pDataSource, pFramesOut, frameCount, &framesRead, loop); + if (result != MA_SUCCESS) { + return result; // Failed to read data from the data source. + } + ``` + +If you don't need the number of frames that were successfully read you can pass in `NULL` to the +`pFramesRead` parameter. If this returns a value less than the number of frames requested it means +the end of the file has been reached. `MA_AT_END` will be returned only when the number of frames +read is 0. + +When calling any data source function, with the exception of `ma_data_source_init()` and +`ma_data_source_uninit()`, you can pass in any object that implements a data source. For example, +you could plug in a decoder like so: + + ```c + ma_result result; + ma_uint64 framesRead; + ma_decoder decoder; // <-- This would be initialized with `ma_decoder_init_*()`. + + result = ma_data_source_read_pcm_frames(&decoder, pFramesOut, frameCount, &framesRead, loop); + if (result != MA_SUCCESS) { + return result; // Failed to read data from the decoder. + } + ``` + +If you want to seek forward you can pass in `NULL` to the `pFramesOut` parameter. Alternatively you +can use `ma_data_source_seek_pcm_frames()`. + +To seek to a specific PCM frame: + + ```c + result = ma_data_source_seek_to_pcm_frame(pDataSource, frameIndex); + if (result != MA_SUCCESS) { + return result; // Failed to seek to PCM frame. + } + ``` + +You can retrieve the total length of a data source in PCM frames, but note that some data sources +may not have the notion of a length, such as noise and waveforms, and others may just not have a +way of determining the length such as some decoders. To retrieve the length: + + ```c + ma_uint64 length; + + result = ma_data_source_get_length_in_pcm_frames(pDataSource, &length); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve the length. + } + ``` + +Care should be taken when retrieving the length of a data source where the underlying decoder is +pulling data from a data stream with an undefined length, such as internet radio or some kind of +broadcast. If you do this, `ma_data_source_get_length_in_pcm_frames()` may never return. + +The current position of the cursor in PCM frames can also be retrieved: + + ```c + ma_uint64 cursor; + + result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursor); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve the cursor. + } + ``` + +You will often need to know the data format that will be returned after reading. This can be +retrieved like so: + + ```c + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_channel channelMap[MA_MAX_CHANNELS]; + + result = ma_data_source_get_data_format(pDataSource, &format, &channels, &sampleRate, channelMap, MA_MAX_CHANNELS); + if (result != MA_SUCCESS) { + return result; // Failed to retrieve data format. + } + ``` + +If you do not need a specific data format property, just pass in NULL to the respective parameter. + +There may be cases where you want to implement something like a sound bank where you only want to +read data within a certain range of the underlying data. To do this you can use a range: + + ```c + result = ma_data_source_set_range_in_pcm_frames(pDataSource, rangeBegInFrames, rangeEndInFrames); + if (result != MA_SUCCESS) { + return result; // Failed to set the range. + } + ``` + +This is useful if you have a sound bank where many sounds are stored in the same file and you want +the data source to only play one of those sub-sounds. + +Custom loop points can also be used with data sources. By default, data sources will loop after +they reach the end of the data source, but if you need to loop at a specific location, you can do +the following: + + ```c + result = ma_data_set_loop_point_in_pcm_frames(pDataSource, loopBegInFrames, loopEndInFrames); + if (result != MA_SUCCESS) { + return result; // Failed to set the loop point. + } + ``` + +The loop point is relative to the current range. + +It's sometimes useful to chain data sources together so that a seamless transition can be achieved. +To do this, you can use chaining: + + ```c + ma_decoder decoder1; + ma_decoder decoder2; + + // ... initialize decoders with ma_decoder_init_*() ... + + result = ma_data_source_set_next(&decoder1, &decoder2); + if (result != MA_SUCCESS) { + return result; // Failed to set the next data source. + } + + result = ma_data_source_read_pcm_frames(&decoder1, pFramesOut, frameCount, pFramesRead, MA_FALSE); + if (result != MA_SUCCESS) { + return result; // Failed to read from the decoder. + } + ``` + +In the example above we're using decoders. When reading from a chain, you always want to read from +the top level data source in the chain. In the example above, `decoder1` is the top level data +source in the chain. When `decoder1` reaches the end, `decoder2` will start seamlessly without any +gaps. + +Note that the `loop` parameter is set to false in the example above. When this is set to true, only +the current data source will be looped. You can loop the entire chain by linking in a loop like so: + + ```c + ma_data_source_set_next(&decoder1, &decoder2); // decoder1 -> decoder2 + ma_data_source_set_next(&decoder2, &decoder1); // decoder2 -> decoder1 (loop back to the start). + ``` + +Note that setting up chaining is not thread safe, so care needs to be taken if you're dynamically +changing links while the audio thread is in the middle of reading. + +Do not use `ma_decoder_seek_to_pcm_frame()` as a means to reuse a data source to play multiple +instances of the same sound simultaneously. Instead, initialize multiple data sources for each +instance. This can be extremely inefficient depending on the data source and can result in +glitching due to subtle changes to the state of internal filters. + + +4.1. Custom Data Sources +------------------------ +You can implement a custom data source by implementing the functions in `ma_data_source_vtable`. +Your custom object must have `ma_data_source_base` as it's first member: + + ```c + struct my_data_source + { + ma_data_source_base base; + ... + }; + ``` + +In your initialization routine, you need to call `ma_data_source_init()` in order to set up the +base object (`ma_data_source_base`): + + ```c + static ma_result my_data_source_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) + { + // Read data here. Output in the same format returned by my_data_source_get_data_format(). + } + + static ma_result my_data_source_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) + { + // Seek to a specific PCM frame here. Return MA_NOT_IMPLEMENTED if seeking is not supported. + } + + static ma_result my_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) + { + // Return the format of the data here. + } + + static ma_result my_data_source_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) + { + // Retrieve the current position of the cursor here. Return MA_NOT_IMPLEMENTED and set *pCursor to 0 if there is no notion of a cursor. + } + + static ma_result my_data_source_get_length(ma_data_source* pDataSource, ma_uint64* pLength) + { + // Retrieve the length in PCM frames here. Return MA_NOT_IMPLEMENTED and set *pLength to 0 if there is no notion of a length or if the length is unknown. + } + + static g_my_data_source_vtable = + { + my_data_source_read, + my_data_source_seek, + my_data_source_get_data_format, + my_data_source_get_cursor, + my_data_source_get_length + }; + + ma_result my_data_source_init(my_data_source* pMyDataSource) + { + ma_result result; + ma_data_source_config baseConfig; + + baseConfig = ma_data_source_config_init(); + baseConfig.vtable = &g_my_data_source_vtable; + + result = ma_data_source_init(&baseConfig, &pMyDataSource->base); + if (result != MA_SUCCESS) { + return result; + } + + // ... do the initialization of your custom data source here ... + + return MA_SUCCESS; + } + + void my_data_source_uninit(my_data_source* pMyDataSource) + { + // ... do the uninitialization of your custom data source here ... + + // You must uninitialize the base data source. + ma_data_source_uninit(&pMyDataSource->base); + } + ``` + +Note that `ma_data_source_init()` and `ma_data_source_uninit()` are never called directly outside +of the custom data source. It's up to the custom data source itself to call these within their own +init/uninit functions. + + + +5. Engine +========= +The `ma_engine` API is a high level API for managing and mixing sounds and effect processing. The +`ma_engine` object encapsulates a resource manager and a node graph, both of which will be +explained in more detail later. + +Sounds are called `ma_sound` and are created from an engine. Sounds can be associated with a mixing +group called `ma_sound_group` which are also created from the engine. Both `ma_sound` and +`ma_sound_group` objects are nodes within the engine's node graph. + +When the engine is initialized, it will normally create a device internally. If you would rather +manage the device yourself, you can do so and just pass a pointer to it via the engine config when +you initialize the engine. You can also just use the engine without a device, which again can be +configured via the engine config. + +The most basic way to initialize the engine is with a default config, like so: + + ```c + ma_result result; + ma_engine engine; + + result = ma_engine_init(NULL, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +This will result in the engine initializing a playback device using the operating system's default +device. This will be sufficient for many use cases, but if you need more flexibility you'll want to +configure the engine with an engine config: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pPlaybackDevice = &myDevice; + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +In the example above we're passing in a pre-initialized device. Since the caller is the one in +control of the device's data callback, it's their responsibility to manually call +`ma_engine_read_pcm_frames()` from inside their data callback: + + ```c + void playback_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) + { + ma_engine_read_pcm_frames(&g_Engine, pOutput, frameCount, NULL); + } + ``` + +You can also use the engine independent of a device entirely: + + ```c + ma_result result; + ma_engine engine; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.noDevice = MA_TRUE; + engineConfig.channels = 2; // Must be set when not using a device. + engineConfig.sampleRate = 48000; // Must be set when not using a device. + + result = ma_engine_init(&engineConfig, &engine); + if (result != MA_SUCCESS) { + return result; // Failed to initialize the engine. + } + ``` + +Note that when you're not using a device, you must set the channel count and sample rate in the +config or else miniaudio won't know what to use (miniaudio will use the device to determine this +normally). When not using a device, you need to use `ma_engine_read_pcm_frames()` to process audio +data from the engine. This kind of setup is useful if you want to do something like offline +processing. + +When a sound is loaded it goes through a resource manager. By default the engine will initialize a +resource manager internally, but you can also specify a pre-initialized resource manager: + + ```c + ma_result result; + ma_engine engine1; + ma_engine engine2; + ma_engine_config engineConfig; + + engineConfig = ma_engine_config_init(); + engineConfig.pResourceManager = &myResourceManager; + + ma_engine_init(&engineConfig, &engine1); + ma_engine_init(&engineConfig, &engine2); + ``` + +In this example we are initializing two engines, both of which are sharing the same resource +manager. This is especially useful for saving memory when loading the same file across multiple +engines. If you were not to use a shared resource manager, each engine instance would use their own +which would result in any sounds that are used between both engine's being loaded twice. By using +a shared resource manager, it would only be loaded once. Using multiple engine's is useful when you +need to output to multiple playback devices, such as in a local multiplayer game where each player +is using their own set of headphones. + +By default an engine will be in a started state. To make it so the engine is not automatically +started you can configure it as such: + + ```c + engineConfig.noAutoStart = MA_TRUE; + + // The engine will need to be started manually. + ma_engine_start(&engine); + + // Later on the engine can be stopped with ma_engine_stop(). + ma_engine_stop(&engine); + ``` + +The concept of starting or stopping an engine is only relevant when using the engine with a +device. Attempting to start or stop an engine that is not associated with a device will result in +`MA_INVALID_OPERATION`. + +The master volume of the engine can be controlled with `ma_engine_set_volume()` which takes a +linear scale, with 0 resulting in silence and anything above 1 resulting in amplification. If you +prefer decibel based volume control, use `ma_volume_db_to_linear()` to convert from dB to linear. + +When a sound is spatialized, it is done so relative to a listener. An engine can be configured to +have multiple listeners which can be configured via the config: + + ```c + engineConfig.listenerCount = 2; + ``` + +The maximum number of listeners is restricted to `MA_ENGINE_MAX_LISTENERS`. By default, when a +sound is spatialized, it will be done so relative to the closest listener. You can also pin a sound +to a specific listener which will be explained later. Listener's have a position, direction, cone, +and velocity (for doppler effect). A listener is referenced by an index, the meaning of which is up +to the caller (the index is 0 based and cannot go beyond the listener count, minus 1). The +position, direction and velocity are all specified in absolute terms: + + ```c + ma_engine_listener_set_position(&engine, listenerIndex, worldPosX, worldPosY, worldPosZ); + ``` + +The direction of the listener represents it's forward vector. The listener's up vector can also be +specified and defaults to +1 on the Y axis. + + ```c + ma_engine_listener_set_direction(&engine, listenerIndex, forwardX, forwardY, forwardZ); + ma_engine_listener_set_world_up(&engine, listenerIndex, 0, 1, 0); + ``` + +The engine supports directional attenuation. The listener can have a cone the controls how sound is +attenuated based on the listener's direction. When a sound is between the inner and outer cones, it +will be attenuated between 1 and the cone's outer gain: + + ```c + ma_engine_listener_set_cone(&engine, listenerIndex, innerAngleInRadians, outerAngleInRadians, outerGain); + ``` + +When a sound is inside the inner code, no directional attenuation is applied. When the sound is +outside of the outer cone, the attenuation will be set to `outerGain` in the example above. When +the sound is in between the inner and outer cones, the attenuation will be interpolated between 1 +and the outer gain. + +The engine's coordinate system follows the OpenGL coordinate system where positive X points right, +positive Y points up and negative Z points forward. + +The simplest and least flexible way to play a sound is like so: + + ```c + ma_engine_play_sound(&engine, "my_sound.wav", pGroup); + ``` + +This is a "fire and forget" style of function. The engine will manage the `ma_sound` object +internally. When the sound finishes playing, it'll be put up for recycling. For more flexibility +you'll want to initialize a sound object: + + ```c + ma_sound sound; + + result = ma_sound_init_from_file(&engine, "my_sound.wav", flags, pGroup, NULL, &sound); + if (result != MA_SUCCESS) { + return result; // Failed to load sound. + } + ``` + +Sounds need to be uninitialized with `ma_sound_uninit()`. + +The example above loads a sound from a file. If the resource manager has been disabled you will not +be able to use this function and instead you'll need to initialize a sound directly from a data +source: + + ```c + ma_sound sound; + + result = ma_sound_init_from_data_source(&engine, &dataSource, flags, pGroup, &sound); + if (result != MA_SUCCESS) { + return result; + } + ``` + +Each `ma_sound` object represents a single instance of the sound. If you want to play the same +sound multiple times at the same time, you need to initialize a separate `ma_sound` object. + +For the most flexibility when initializing sounds, use `ma_sound_init_ex()`. This uses miniaudio's +standard config/init pattern: + + ```c + ma_sound sound; + ma_sound_config soundConfig; + + soundConfig = ma_sound_config_init(); + soundConfig.pFilePath = NULL; // Set this to load from a file path. + soundConfig.pDataSource = NULL; // Set this to initialize from an existing data source. + soundConfig.pInitialAttachment = &someNodeInTheNodeGraph; + soundConfig.initialAttachmentInputBusIndex = 0; + soundConfig.channelsIn = 1; + soundConfig.channelsOut = 0; // Set to 0 to use the engine's native channel count. + + result = ma_sound_init_ex(&soundConfig, &sound); + if (result != MA_SUCCESS) { + return result; + } + ``` + +In the example above, the sound is being initialized without a file nor a data source. This is +valid, in which case the sound acts as a node in the middle of the node graph. This means you can +connect other sounds to this sound and allow it to act like a sound group. Indeed, this is exactly +what a `ma_sound_group` is. + +When loading a sound, you specify a set of flags that control how the sound is loaded and what +features are enabled for that sound. When no flags are set, the sound will be fully loaded into +memory in exactly the same format as how it's stored on the file system. The resource manager will +allocate a block of memory and then load the file directly into it. When reading audio data, it +will be decoded dynamically on the fly. In order to save processing time on the audio thread, it +might be beneficial to pre-decode the sound. You can do this with the `MA_SOUND_FLAG_DECODE` flag: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE, pGroup, NULL, &sound); + ``` + +By default, sounds will be loaded synchronously, meaning `ma_sound_init_*()` will not return until +the sound has been fully loaded. If this is prohibitive you can instead load sounds asynchronously +by specificying the `MA_SOUND_FLAG_ASYNC` flag: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, NULL, &sound); + ``` + +This will result in `ma_sound_init_*()` returning quickly, but the sound won't yet have been fully +loaded. When you start the sound, it won't output anything until some sound is available. The sound +will start outputting audio before the sound has been fully decoded when the `MA_SOUND_FLAG_DECODE` +is specified. + +If you need to wait for an asynchronously loaded sound to be fully loaded, you can use a fence. A +fence in miniaudio is a simple synchronization mechanism which simply blocks until it's internal +counter hit's zero. You can specify a fence like so: + + ```c + ma_result result; + ma_fence fence; + ma_sound sounds[4]; + + result = ma_fence_init(&fence); + if (result != MA_SUCCES) { + return result; + } + + // Load some sounds asynchronously. + for (int iSound = 0; iSound < 4; iSound += 1) { + ma_sound_init_from_file(&engine, mySoundFilesPaths[iSound], MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC, pGroup, &fence, &sounds[iSound]); + } + + // ... do some other stuff here in the mean time ... + + // Wait for all sounds to finish loading. + ma_fence_wait(&fence); + ``` + +If loading the entire sound into memory is prohibitive, you can also configure the engine to stream +the audio data: + + ```c + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_STREAM, pGroup, NULL, &sound); + ``` + +When streaming sounds, 2 seconds worth of audio data is stored in memory. Although it should work +fine, it's inefficient to use streaming for short sounds. Streaming is useful for things like music +tracks in games. + +When you initialize a sound, if you specify a sound group the sound will be attached to that group +automatically. If you set it to NULL, it will be automatically attached to the engine's endpoint. +If you would instead rather leave the sound unattached by default, you can can specify the +`MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT` flag. This is useful if you want to set up a complex node +graph. + +Sounds are not started by default. To start a sound, use `ma_sound_start()`. Stop a sound with +`ma_sound_stop()`. + +Sounds can have their volume controlled with `ma_sound_set_volume()` in the same way as the +engine's master volume. + +Sounds support stereo panning and pitching. Set the pan with `ma_sound_set_pan()`. Setting the pan +to 0 will result in an unpanned sound. Setting it to -1 will shift everything to the left, whereas ++1 will shift it to the right. The pitch can be controlled with `ma_sound_set_pitch()`. A larger +value will result in a higher pitch. The pitch must be greater than 0. + +The engine supports 3D spatialization of sounds. By default sounds will have spatialization +enabled, but if a sound does not need to be spatialized it's best to disable it. There are two ways +to disable spatialization of a sound: + + ```c + // Disable spatialization at initialization time via a flag: + ma_sound_init_from_file(&engine, "my_sound.wav", MA_SOUND_FLAG_NO_SPATIALIZATION, NULL, NULL, &sound); + + // Dynamically disable or enable spatialization post-initialization: + ma_sound_set_spatialization_enabled(&sound, isSpatializationEnabled); + ``` + +By default sounds will be spatialized based on the closest listener. If a sound should always be +spatialized relative to a specific listener it can be pinned to one: + + ```c + ma_sound_set_pinned_listener_index(&sound, listenerIndex); + ``` + +Like listeners, sounds have a position. By default, the position of a sound is in absolute space, +but it can be changed to be relative to a listener: + + ```c + ma_sound_set_positioning(&sound, ma_positioning_relative); + ``` + +Note that relative positioning of a sound only makes sense if there is either only one listener, or +the sound is pinned to a specific listener. To set the position of a sound: + + ```c + ma_sound_set_position(&sound, posX, posY, posZ); + ``` + +The direction works the same way as a listener and represents the sound's forward direction: + + ```c + ma_sound_set_direction(&sound, forwardX, forwardY, forwardZ); + ``` + +Sound's also have a cone for controlling directional attenuation. This works exactly the same as +listeners: + + ```c + ma_sound_set_cone(&sound, innerAngleInRadians, outerAngleInRadians, outerGain); + ``` + +The velocity of a sound is used for doppler effect and can be set as such: + + ```c + ma_sound_set_velocity(&sound, velocityX, velocityY, velocityZ); + ``` + +The engine supports different attenuation models which can be configured on a per-sound basis. By +default the attenuation model is set to `ma_attenuation_model_inverse` which is the equivalent to +OpenAL's `AL_INVERSE_DISTANCE_CLAMPED`. Configure the attenuation model like so: + + ```c + ma_sound_set_attenuation_model(&sound, ma_attenuation_model_inverse); + ``` + +The supported attenuation models include the following: + + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_none | No distance attenuation. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_inverse | Equivalent to `AL_INVERSE_DISTANCE_CLAMPED`. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_linear | Linear attenuation. | + +----------------------------------+----------------------------------------------+ + | ma_attenuation_model_exponential | Exponential attenuation. | + +----------------------------------+----------------------------------------------+ + +To control how quickly a sound rolls off as it moves away from the listener, you need to configure +the rolloff: + + ```c + ma_sound_set_rolloff(&sound, rolloff); + ``` + +You can control the minimum and maximum gain to apply from spatialization: + + ```c + ma_sound_set_min_gain(&sound, minGain); + ma_sound_set_max_gain(&sound, maxGain); + ``` + +Likewise, in the calculation of attenuation, you can control the minimum and maximum distances for +the attenuation calculation. This is useful if you want to ensure sounds don't drop below a certain +volume after the listener moves further away and to have sounds play a maximum volume when the +listener is within a certain distance: + + ```c + ma_sound_set_min_distance(&sound, minDistance); + ma_sound_set_max_distance(&sound, maxDistance); + ``` + +The engine's spatialization system supports doppler effect. The doppler factor can be configure on +a per-sound basis like so: + + ```c + ma_sound_set_doppler_factor(&sound, dopplerFactor); + ``` + +You can fade sounds in and out with `ma_sound_set_fade_in_pcm_frames()` and +`ma_sound_set_fade_in_milliseconds()`. Set the volume to -1 to use the current volume as the +starting volume: + + ```c + // Fade in over 1 second. + ma_sound_set_fade_in_milliseconds(&sound, 0, 1, 1000); + + // ... sometime later ... + + // Fade out over 1 second, starting from the current volume. + ma_sound_set_fade_in_milliseconds(&sound, -1, 0, 1000); + ``` + +By default sounds will start immediately, but sometimes for timing and synchronization purposes it +can be useful to schedule a sound to start or stop: + + ```c + // Start the sound in 1 second from now. + ma_sound_set_start_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 1)); + + // Stop the sound in 2 seconds from now. + ma_sound_set_stop_time_in_pcm_frames(&sound, ma_engine_get_time(&engine) + (ma_engine_get_sample_rate(&engine) * 2)); + ``` + +Note that scheduling a start time still requires an explicit call to `ma_sound_start()` before +anything will play. + +The time is specified in global time which is controlled by the engine. You can get the engine's +current time with `ma_engine_get_time()`. The engine's global time is incremented automatically as +audio data is read, but it can be reset with `ma_engine_set_time()` in case it needs to be +resynchronized for some reason. + +To determine whether or not a sound is currently playing, use `ma_sound_is_playing()`. This will +take the scheduled start and stop times into account. + +Whether or not a sound should loop can be controlled with `ma_sound_set_looping()`. Sounds will not +be looping by default. Use `ma_sound_is_looping()` to determine whether or not a sound is looping. + +Use `ma_sound_at_end()` to determine whether or not a sound is currently at the end. For a looping +sound this should never return true. + +Internally a sound wraps around a data source. Some APIs exist to control the underlying data +source, mainly for convenience: + + ```c + ma_sound_seek_to_pcm_frame(&sound, frameIndex); + ma_sound_get_data_format(&sound, &format, &channels, &sampleRate, pChannelMap, channelMapCapacity); + ma_sound_get_cursor_in_pcm_frames(&sound, &cursor); + ma_sound_get_length_in_pcm_frames(&sound, &length); + ``` + +Sound groups have the same API as sounds, only they are called `ma_sound_group`, and since they do +not have any notion of a data source, anything relating to a data source is unavailable. + +Internally, sound data is loaded via the `ma_decoder` API which means by default in only supports +file formats that have built-in support in miniaudio. You can extend this to support any kind of +file format through the use of custom decoders. To do this you'll need to use a self-managed +resource manager and configure it appropriately. See the "Resource Management" section below for +details on how to set this up. + + +6. Resource Management +====================== +Many programs will want to manage sound resources for things such as reference counting and +streaming. This is supported by miniaudio via the `ma_resource_manager` API. + +The resource manager is mainly responsible for the following: + + * Loading of sound files into memory with reference counting. + * Streaming of sound data + +When loading a sound file, the resource manager will give you back a `ma_data_source` compatible +object called `ma_resource_manager_data_source`. This object can be passed into any +`ma_data_source` API which is how you can read and seek audio data. When loading a sound file, you +specify whether or not you want the sound to be fully loaded into memory (and optionally +pre-decoded) or streamed. When loading into memory, you can also specify whether or not you want +the data to be loaded asynchronously. + +The example below is how you can initialize a resource manager using it's default configuration: + + ```c + ma_resource_manager_config config; + ma_resource_manager resourceManager; + + config = ma_resource_manager_config_init(); + result = ma_resource_manager_init(&config, &resourceManager); + if (result != MA_SUCCESS) { + ma_device_uninit(&device); + printf("Failed to initialize the resource manager."); + return -1; + } + ``` + +You can configure the format, channels and sample rate of the decoded audio data. By default it +will use the file's native data format, but you can configure it to use a consistent format. This +is useful for offloading the cost of data conversion to load time rather than dynamically +converting at mixing time. To do this, you configure the decoded format, channels and sample rate +like the code below: + + ```c + config = ma_resource_manager_config_init(); + config.decodedFormat = device.playback.format; + config.decodedChannels = device.playback.channels; + config.decodedSampleRate = device.sampleRate; + ``` + +In the code above, the resource manager will be configured so that any decoded audio data will be +pre-converted at load time to the device's native data format. If instead you used defaults and +the data format of the file did not match the device's data format, you would need to convert the +data at mixing time which may be prohibitive in high-performance and large scale scenarios like +games. + +Internally the resource manager uses the `ma_decoder` API to load sounds. This means by default it +only supports decoders that are built into miniaudio. It's possible to support additional encoding +formats through the use of custom decoders. To do so, pass in your `ma_decoding_backend_vtable` +vtables into the resource manager config: + + ```c + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + ... + + resourceManagerConfig.ppCustomDecodingBackendVTables = pCustomBackendVTables; + resourceManagerConfig.customDecodingBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + resourceManagerConfig.pCustomDecodingBackendUserData = NULL; + ``` + +This system can allow you to support any kind of file format. See the "Decoding" section for +details on how to implement custom decoders. The miniaudio repository includes examples for Opus +via libopus and libopusfile and Vorbis via libvorbis and libvorbisfile. + +Asynchronicity is achieved via a job system. When an operation needs to be performed, such as the +decoding of a page, a job will be posted to a queue which will then be processed by a job thread. +By default there will be only one job thread running, but this can be configured, like so: + + ```c + config = ma_resource_manager_config_init(); + config.jobThreadCount = MY_JOB_THREAD_COUNT; + ``` + +By default job threads are managed internally by the resource manager, however you can also self +manage your job threads if, for example, you want to integrate the job processing into your +existing job infrastructure, or if you simply don't like the way the resource manager does it. To +do this, just set the job thread count to 0 and process jobs manually. To process jobs, you first +need to retrieve a job using `ma_resource_manager_next_job()` and then process it using +`ma_job_process()`: + + ```c + config = ma_resource_manager_config_init(); + config.jobThreadCount = 0; // Don't manage any job threads internally. + config.flags = MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; // Optional. Makes `ma_resource_manager_next_job()` non-blocking. + + // ... Initialize your custom job threads ... + + void my_custom_job_thread(...) + { + for (;;) { + ma_job job; + ma_result result = ma_resource_manager_next_job(pMyResourceManager, &job); + if (result != MA_SUCCESS) { + if (result == MA_NOT_DATA_AVAILABLE) { + // No jobs are available. Keep going. Will only get this if the resource manager was initialized + // with MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. + continue; + } else if (result == MA_CANCELLED) { + // MA_JOB_TYPE_QUIT was posted. Exit. + break; + } else { + // Some other error occurred. + break; + } + } + + ma_job_process(&job); + } + } + ``` + +In the example above, the `MA_JOB_TYPE_QUIT` event is the used as the termination +indicator, but you can use whatever you would like to terminate the thread. The call to +`ma_resource_manager_next_job()` is blocking by default, but can be configured to be non-blocking +by initializing the resource manager with the `MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING` configuration +flag. Note that the `MA_JOB_TYPE_QUIT` will never be removed from the job queue. This +is to give every thread the opportunity to catch the event and terminate naturally. + +When loading a file, it's sometimes convenient to be able to customize how files are opened and +read instead of using standard `fopen()`, `fclose()`, etc. which is what miniaudio will use by +default. This can be done by setting `pVFS` member of the resource manager's config: + + ```c + // Initialize your custom VFS object. See documentation for VFS for information on how to do this. + my_custom_vfs vfs = my_custom_vfs_init(); + + config = ma_resource_manager_config_init(); + config.pVFS = &vfs; + ``` + +This is particularly useful in programs like games where you want to read straight from an archive +rather than the normal file system. If you do not specify a custom VFS, the resource manager will +use the operating system's normal file operations. This is default. + +To load a sound file and create a data source, call `ma_resource_manager_data_source_init()`. When +loading a sound you need to specify the file path and options for how the sounds should be loaded. +By default a sound will be loaded synchronously. The returned data source is owned by the caller +which means the caller is responsible for the allocation and freeing of the data source. Below is +an example for initializing a data source: + + ```c + ma_resource_manager_data_source dataSource; + ma_result result = ma_resource_manager_data_source_init(pResourceManager, pFilePath, flags, &dataSource); + if (result != MA_SUCCESS) { + // Error. + } + + // ... + + // A ma_resource_manager_data_source object is compatible with the `ma_data_source` API. To read data, just call + // the `ma_data_source_read_pcm_frames()` like you would with any normal data source. + result = ma_data_source_read_pcm_frames(&dataSource, pDecodedData, frameCount, &framesRead); + if (result != MA_SUCCESS) { + // Failed to read PCM frames. + } + + // ... + + ma_resource_manager_data_source_uninit(pResourceManager, &dataSource); + ``` + +The `flags` parameter specifies how you want to perform loading of the sound file. It can be a +combination of the following flags: + + ``` + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT + ``` + +When no flags are specified (set to 0), the sound will be fully loaded into memory, but not +decoded, meaning the raw file data will be stored in memory, and then dynamically decoded when +`ma_data_source_read_pcm_frames()` is called. To instead decode the audio data before storing it in +memory, use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` flag. By default, the sound file will +be loaded synchronously, meaning `ma_resource_manager_data_source_init()` will only return after +the entire file has been loaded. This is good for simplicity, but can be prohibitively slow. You +can instead load the sound asynchronously using the `MA_RESOURCE_MANAGER_DATA_SOURCE_ASYNC` flag. +This will result in `ma_resource_manager_data_source_init()` returning quickly, but no data will be +returned by `ma_data_source_read_pcm_frames()` until some data is available. When no data is +available because the asynchronous decoding hasn't caught up, `MA_BUSY` will be returned by +`ma_data_source_read_pcm_frames()`. + +For large sounds, it's often prohibitive to store the entire file in memory. To mitigate this, you +can instead stream audio data which you can do by specifying the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag. When streaming, data will be decoded in 1 +second pages. When a new page needs to be decoded, a job will be posted to the job queue and then +subsequently processed in a job thread. + +For in-memory sounds, reference counting is used to ensure the data is loaded only once. This means +multiple calls to `ma_resource_manager_data_source_init()` with the same file path will result in +the file data only being loaded once. Each call to `ma_resource_manager_data_source_init()` must be +matched up with a call to `ma_resource_manager_data_source_uninit()`. Sometimes it can be useful +for a program to register self-managed raw audio data and associate it with a file path. Use the +`ma_resource_manager_register_*()` and `ma_resource_manager_unregister_*()` APIs to do this. +`ma_resource_manager_register_decoded_data()` is used to associate a pointer to raw, self-managed +decoded audio data in the specified data format with the specified name. Likewise, +`ma_resource_manager_register_encoded_data()` is used to associate a pointer to raw self-managed +encoded audio data (the raw file data) with the specified name. Note that these names need not be +actual file paths. When `ma_resource_manager_data_source_init()` is called (without the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag), the resource manager will look for these +explicitly registered data buffers and, if found, will use it as the backing data for the data +source. Note that the resource manager does *not* make a copy of this data so it is up to the +caller to ensure the pointer stays valid for it's lifetime. Use +`ma_resource_manager_unregister_data()` to unregister the self-managed data. You can also use +`ma_resource_manager_register_file()` and `ma_resource_manager_unregister_file()` to register and +unregister a file. It does not make sense to use the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` +flag with a self-managed data pointer. + + +6.1. Asynchronous Loading and Synchronization +--------------------------------------------- +When loading asynchronously, it can be useful to poll whether or not loading has finished. Use +`ma_resource_manager_data_source_result()` to determine this. For in-memory sounds, this will +return `MA_SUCCESS` when the file has been *entirely* decoded. If the sound is still being decoded, +`MA_BUSY` will be returned. Otherwise, some other error code will be returned if the sound failed +to load. For streaming data sources, `MA_SUCCESS` will be returned when the first page has been +decoded and the sound is ready to be played. If the first page is still being decoded, `MA_BUSY` +will be returned. Otherwise, some other error code will be returned if the sound failed to load. + +In addition to polling, you can also use a simple synchronization object called a "fence" to wait +for asynchronously loaded sounds to finish. This is called `ma_fence`. The advantage to using a +fence is that it can be used to wait for a group of sounds to finish loading rather than waiting +for sounds on an individual basis. There are two stages to loading a sound: + + * Initialization of the internal decoder; and + * Completion of decoding of the file (the file is fully decoded) + +You can specify separate fences for each of the different stages. Waiting for the initialization +of the internal decoder is important for when you need to know the sample format, channels and +sample rate of the file. + +The example below shows how you could use a fence when loading a number of sounds: + + ```c + // This fence will be released when all sounds are finished loading entirely. + ma_fence fence; + ma_fence_init(&fence); + + // This will be passed into the initialization routine for each sound. + ma_resource_manager_pipeline_notifications notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pFence = &fence; + + // Now load a bunch of sounds: + for (iSound = 0; iSound < soundCount; iSound += 1) { + ma_resource_manager_data_source_init(pResourceManager, pSoundFilePaths[iSound], flags, ¬ifications, &pSoundSources[iSound]); + } + + // ... DO SOMETHING ELSE WHILE SOUNDS ARE LOADING ... + + // Wait for loading of sounds to finish. + ma_fence_wait(&fence); + ``` + +In the example above we used a fence for waiting until the entire file has been fully decoded. If +you only need to wait for the initialization of the internal decoder to complete, you can use the +`init` member of the `ma_resource_manager_pipeline_notifications` object: + + ```c + notifications.init.pFence = &fence; + ``` + +If a fence is not appropriate for your situation, you can instead use a callback that is fired on +an individual sound basis. This is done in a very similar way to fences: + + ```c + typedef struct + { + ma_async_notification_callbacks cb; + void* pMyData; + } my_notification; + + void my_notification_callback(ma_async_notification* pNotification) + { + my_notification* pMyNotification = (my_notification*)pNotification; + + // Do something in response to the sound finishing loading. + } + + ... + + my_notification myCallback; + myCallback.cb.onSignal = my_notification_callback; + myCallback.pMyData = pMyData; + + ma_resource_manager_pipeline_notifications notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pNotification = &myCallback; + + ma_resource_manager_data_source_init(pResourceManager, "my_sound.wav", flags, ¬ifications, &mySound); + ``` + +In the example above we just extend the `ma_async_notification_callbacks` object and pass an +instantiation into the `ma_resource_manager_pipeline_notifications` in the same way as we did with +the fence, only we set `pNotification` instead of `pFence`. You can set both of these at the same +time and they should both work as expected. If using the `pNotification` system, you need to ensure +your `ma_async_notification_callbacks` object stays valid. + + + +6.2. Resource Manager Implementation Details +-------------------------------------------- +Resources are managed in two main ways: + + * By storing the entire sound inside an in-memory buffer (referred to as a data buffer) + * By streaming audio data on the fly (referred to as a data stream) + +A resource managed data source (`ma_resource_manager_data_source`) encapsulates a data buffer or +data stream, depending on whether or not the data source was initialized with the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag. If so, it will make use of a +`ma_resource_manager_data_stream` object. Otherwise it will use a `ma_resource_manager_data_buffer` +object. Both of these objects are data sources which means they can be used with any +`ma_data_source_*()` API. + +Another major feature of the resource manager is the ability to asynchronously decode audio files. +This relieves the audio thread of time-consuming decoding which can negatively affect scalability +due to the audio thread needing to complete it's work extremely quickly to avoid glitching. +Asynchronous decoding is achieved through a job system. There is a central multi-producer, +multi-consumer, fixed-capacity job queue. When some asynchronous work needs to be done, a job is +posted to the queue which is then read by a job thread. The number of job threads can be +configured for improved scalability, and job threads can all run in parallel without needing to +worry about the order of execution (how this is achieved is explained below). + +When a sound is being loaded asynchronously, playback can begin before the sound has been fully +decoded. This enables the application to start playback of the sound quickly, while at the same +time allowing to resource manager to keep loading in the background. Since there may be less +threads than the number of sounds being loaded at a given time, a simple scheduling system is used +to keep decoding time balanced and fair. The resource manager solves this by splitting decoding +into chunks called pages. By default, each page is 1 second long. When a page has been decoded, a +new job will be posted to start decoding the next page. By dividing up decoding into pages, an +individual sound shouldn't ever delay every other sound from having their first page decoded. Of +course, when loading many sounds at the same time, there will always be an amount of time required +to process jobs in the queue so in heavy load situations there will still be some delay. To +determine if a data source is ready to have some frames read, use +`ma_resource_manager_data_source_get_available_frames()`. This will return the number of frames +available starting from the current position. + + +6.2.1. Job Queue +---------------- +The resource manager uses a job queue which is multi-producer, multi-consumer, and fixed-capacity. +This job queue is not currently lock-free, and instead uses a spinlock to achieve thread-safety. +Only a fixed number of jobs can be allocated and inserted into the queue which is done through a +lock-free data structure for allocating an index into a fixed sized array, with reference counting +for mitigation of the ABA problem. The reference count is 32-bit. + +For many types of jobs it's important that they execute in a specific order. In these cases, jobs +are executed serially. For the resource manager, serial execution of jobs is only required on a +per-object basis (per data buffer or per data stream). Each of these objects stores an execution +counter. When a job is posted it is associated with an execution counter. When the job is +processed, it checks if the execution counter of the job equals the execution counter of the +owning object and if so, processes the job. If the counters are not equal, the job will be posted +back onto the job queue for later processing. When the job finishes processing the execution order +of the main object is incremented. This system means the no matter how many job threads are +executing, decoding of an individual sound will always get processed serially. The advantage to +having multiple threads comes into play when loading multiple sounds at the same time. + +The resource manager's job queue is not 100% lock-free and will use a spinlock to achieve +thread-safety for a very small section of code. This is only relevant when the resource manager +uses more than one job thread. If only using a single job thread, which is the default, the +lock should never actually wait in practice. The amount of time spent locking should be quite +short, but it's something to be aware of for those who have pedantic lock-free requirements and +need to use more than one job thread. There are plans to remove this lock in a future version. + +In addition, posting a job will release a semaphore, which on Win32 is implemented with +`ReleaseSemaphore` and on POSIX platforms via a condition variable: + + ```c + pthread_mutex_lock(&pSemaphore->lock); + { + pSemaphore->value += 1; + pthread_cond_signal(&pSemaphore->cond); + } + pthread_mutex_unlock(&pSemaphore->lock); + ``` + +Again, this is relevant for those with strict lock-free requirements in the audio thread. To avoid +this, you can use non-blocking mode (via the `MA_JOB_QUEUE_FLAG_NON_BLOCKING` +flag) and implement your own job processing routine (see the "Resource Manager" section above for +details on how to do this). + + + +6.2.2. Data Buffers +------------------- +When the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM` flag is excluded at initialization time, the +resource manager will try to load the data into an in-memory data buffer. Before doing so, however, +it will first check if the specified file is already loaded. If so, it will increment a reference +counter and just use the already loaded data. This saves both time and memory. When the data buffer +is uninitialized, the reference counter will be decremented. If the counter hits zero, the file +will be unloaded. This is a detail to keep in mind because it could result in excessive loading and +unloading of a sound. For example, the following sequence will result in a file be loaded twice, +once after the other: + + ```c + ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer0); // Refcount = 1. Initial load. + ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer0); // Refcount = 0. Unloaded. + + ma_resource_manager_data_source_init(pResourceManager, "my_file", ..., &myDataBuffer1); // Refcount = 1. Reloaded because previous uninit() unloaded it. + ma_resource_manager_data_source_uninit(pResourceManager, &myDataBuffer1); // Refcount = 0. Unloaded. + ``` + +A binary search tree (BST) is used for storing data buffers as it has good balance between +efficiency and simplicity. The key of the BST is a 64-bit hash of the file path that was passed +into `ma_resource_manager_data_source_init()`. The advantage of using a hash is that it saves +memory over storing the entire path, has faster comparisons, and results in a mostly balanced BST +due to the random nature of the hash. The disadvantage is that file names are case-sensitive. If +this is an issue, you should normalize your file names to upper- or lower-case before initializing +your data sources. + +When a sound file has not already been loaded and the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` +flag is excluded, the file will be decoded synchronously by the calling thread. There are two +options for controlling how the audio is stored in the data buffer - encoded or decoded. When the +`MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` option is excluded, the raw file data will be stored +in memory. Otherwise the sound will be decoded before storing it in memory. Synchronous loading is +a very simple and standard process of simply adding an item to the BST, allocating a block of +memory and then decoding (if `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE` is specified). + +When the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` flag is specified, loading of the data buffer +is done asynchronously. In this case, a job is posted to the queue to start loading and then the +function immediately returns, setting an internal result code to `MA_BUSY`. This result code is +returned when the program calls `ma_resource_manager_data_source_result()`. When decoding has fully +completed `MA_SUCCESS` will be returned. This can be used to know if loading has fully completed. + +When loading asynchronously, a single job is posted to the queue of the type +`MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE`. This involves making a copy of the file path and +associating it with job. When the job is processed by the job thread, it will first load the file +using the VFS associated with the resource manager. When using a custom VFS, it's important that it +be completely thread-safe because it will be used from one or more job threads at the same time. +Individual files should only ever be accessed by one thread at a time, however. After opening the +file via the VFS, the job will determine whether or not the file is being decoded. If not, it +simply allocates a block of memory and loads the raw file contents into it and returns. On the +other hand, when the file is being decoded, it will first allocate a decoder on the heap and +initialize it. Then it will check if the length of the file is known. If so it will allocate a +block of memory to store the decoded output and initialize it to silence. If the size is unknown, +it will allocate room for one page. After memory has been allocated, the first page will be +decoded. If the sound is shorter than a page, the result code will be set to `MA_SUCCESS` and the +completion event will be signalled and loading is now complete. If, however, there is more to +decode, a job with the code `MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE` is posted. This job +will decode the next page and perform the same process if it reaches the end. If there is more to +decode, the job will post another `MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE` job which will +keep on happening until the sound has been fully decoded. For sounds of an unknown length, each +page will be linked together as a linked list. Internally this is implemented via the +`ma_paged_audio_buffer` object. + + +6.2.3. Data Streams +------------------- +Data streams only ever store two pages worth of data for each instance. They are most useful for +large sounds like music tracks in games that would consume too much memory if fully decoded in +memory. After every frame from a page has been read, a job will be posted to load the next page +which is done from the VFS. + +For data streams, the `MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC` flag will determine whether or +not initialization of the data source waits until the two pages have been decoded. When unset, +`ma_resource_manager_data_source_init()` will wait until the two pages have been loaded, otherwise +it will return immediately. + +When frames are read from a data stream using `ma_resource_manager_data_source_read_pcm_frames()`, +`MA_BUSY` will be returned if there are no frames available. If there are some frames available, +but less than the number requested, `MA_SUCCESS` will be returned, but the actual number of frames +read will be less than the number requested. Due to the asynchronous nature of data streams, +seeking is also asynchronous. If the data stream is in the middle of a seek, `MA_BUSY` will be +returned when trying to read frames. + +When `ma_resource_manager_data_source_read_pcm_frames()` results in a page getting fully consumed +a job is posted to load the next page. This will be posted from the same thread that called +`ma_resource_manager_data_source_read_pcm_frames()`. + +Data streams are uninitialized by posting a job to the queue, but the function won't return until +that job has been processed. The reason for this is that the caller owns the data stream object and +therefore miniaudio needs to ensure everything completes before handing back control to the caller. +Also, if the data stream is uninitialized while pages are in the middle of decoding, they must +complete before destroying any underlying object and the job system handles this cleanly. + +Note that when a new page needs to be loaded, a job will be posted to the resource manager's job +thread from the audio thread. You must keep in mind the details mentioned in the "Job Queue" +section above regarding locking when posting an event if you require a strictly lock-free audio +thread. + + + +7. Node Graph +============= +miniaudio's routing infrastructure follows a node graph paradigm. The idea is that you create a +node whose outputs are attached to inputs of another node, thereby creating a graph. There are +different types of nodes, with each node in the graph processing input data to produce output, +which is then fed through the chain. Each node in the graph can apply their own custom effects. At +the start of the graph will usually be one or more data source nodes which have no inputs, but +instead pull their data from a data source. At the end of the graph is an endpoint which represents +the end of the chain and is where the final output is ultimately extracted from. + +Each node has a number of input buses and a number of output buses. An output bus from a node is +attached to an input bus of another. Multiple nodes can connect their output buses to another +node's input bus, in which case their outputs will be mixed before processing by the node. Below is +a diagram that illustrates a hypothetical node graph setup: + + ``` + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Data flows left to right >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + +---------------+ +-----------------+ + | Data Source 1 =----+ +----------+ +----= Low Pass Filter =----+ + +---------------+ | | =----+ +-----------------+ | +----------+ + +----= Splitter | +----= ENDPOINT | + +---------------+ | | =----+ +-----------------+ | +----------+ + | Data Source 2 =----+ +----------+ +----= Echo / Delay =----+ + +---------------+ +-----------------+ + ``` + +In the above graph, it starts with two data sources whose outputs are attached to the input of a +splitter node. It's at this point that the two data sources are mixed. After mixing, the splitter +performs it's processing routine and produces two outputs which is simply a duplication of the +input stream. One output is attached to a low pass filter, whereas the other output is attached to +a echo/delay. The outputs of the the low pass filter and the echo are attached to the endpoint, and +since they're both connected to the same input but, they'll be mixed. + +Each input bus must be configured to accept the same number of channels, but the number of channels +used by input buses can be different to the number of channels for output buses in which case +miniaudio will automatically convert the input data to the output channel count before processing. +The number of channels of an output bus of one node must match the channel count of the input bus +it's attached to. The channel counts cannot be changed after the node has been initialized. If you +attempt to attach an output bus to an input bus with a different channel count, attachment will +fail. + +To use a node graph, you first need to initialize a `ma_node_graph` object. This is essentially a +container around the entire graph. The `ma_node_graph` object is required for some thread-safety +issues which will be explained later. A `ma_node_graph` object is initialized using miniaudio's +standard config/init system: + + ```c + ma_node_graph_config nodeGraphConfig = ma_node_graph_config_init(myChannelCount); + + result = ma_node_graph_init(&nodeGraphConfig, NULL, &nodeGraph); // Second parameter is a pointer to allocation callbacks. + if (result != MA_SUCCESS) { + // Failed to initialize node graph. + } + ``` + +When you initialize the node graph, you're specifying the channel count of the endpoint. The +endpoint is a special node which has one input bus and one output bus, both of which have the +same channel count, which is specified in the config. Any nodes that connect directly to the +endpoint must be configured such that their output buses have the same channel count. When you read +audio data from the node graph, it'll have the channel count you specified in the config. To read +data from the graph: + + ```c + ma_uint32 framesRead; + result = ma_node_graph_read_pcm_frames(&nodeGraph, pFramesOut, frameCount, &framesRead); + if (result != MA_SUCCESS) { + // Failed to read data from the node graph. + } + ``` + +When you read audio data, miniaudio starts at the node graph's endpoint node which then pulls in +data from it's input attachments, which in turn recusively pull in data from their inputs, and so +on. At the start of the graph there will be some kind of data source node which will have zero +inputs and will instead read directly from a data source. The base nodes don't literally need to +read from a `ma_data_source` object, but they will always have some kind of underlying object that +sources some kind of audio. The `ma_data_source_node` node can be used to read from a +`ma_data_source`. Data is always in floating-point format and in the number of channels you +specified when the graph was initialized. The sample rate is defined by the underlying data sources. +It's up to you to ensure they use a consistent and appropraite sample rate. + +The `ma_node` API is designed to allow custom nodes to be implemented with relative ease, but +miniaudio includes a few stock nodes for common functionality. This is how you would initialize a +node which reads directly from a data source (`ma_data_source_node`) which is an example of one +of the stock nodes that comes with miniaudio: + + ```c + ma_data_source_node_config config = ma_data_source_node_config_init(pMyDataSource); + + ma_data_source_node dataSourceNode; + result = ma_data_source_node_init(&nodeGraph, &config, NULL, &dataSourceNode); + if (result != MA_SUCCESS) { + // Failed to create data source node. + } + ``` + +The data source node will use the output channel count to determine the channel count of the output +bus. There will be 1 output bus and 0 input buses (data will be drawn directly from the data +source). The data source must output to floating-point (`ma_format_f32`) or else an error will be +returned from `ma_data_source_node_init()`. + +By default the node will not be attached to the graph. To do so, use `ma_node_attach_output_bus()`: + + ```c + result = ma_node_attach_output_bus(&dataSourceNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); + if (result != MA_SUCCESS) { + // Failed to attach node. + } + ``` + +The code above connects the data source node directly to the endpoint. Since the data source node +has only a single output bus, the index will always be 0. Likewise, the endpoint only has a single +input bus which means the input bus index will also always be 0. + +To detach a specific output bus, use `ma_node_detach_output_bus()`. To detach all output buses, use +`ma_node_detach_all_output_buses()`. If you want to just move the output bus from one attachment to +another, you do not need to detach first. You can just call `ma_node_attach_output_bus()` and it'll +deal with it for you. + +Less frequently you may want to create a specialized node. This will be a node where you implement +your own processing callback to apply a custom effect of some kind. This is similar to initalizing +one of the stock node types, only this time you need to specify a pointer to a vtable containing a +pointer to the processing function and the number of input and output buses. Example: + + ```c + static void my_custom_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) + { + // Do some processing of ppFramesIn (one stream of audio data per input bus) + const float* pFramesIn_0 = ppFramesIn[0]; // Input bus @ index 0. + const float* pFramesIn_1 = ppFramesIn[1]; // Input bus @ index 1. + float* pFramesOut_0 = ppFramesOut[0]; // Output bus @ index 0. + + // Do some processing. On input, `pFrameCountIn` will be the number of input frames in each + // buffer in `ppFramesIn` and `pFrameCountOut` will be the capacity of each of the buffers + // in `ppFramesOut`. On output, `pFrameCountIn` should be set to the number of input frames + // your node consumed and `pFrameCountOut` should be set the number of output frames that + // were produced. + // + // You should process as many frames as you can. If your effect consumes input frames at the + // same rate as output frames (always the case, unless you're doing resampling), you need + // only look at `ppFramesOut` and process that exact number of frames. If you're doing + // resampling, you'll need to be sure to set both `pFrameCountIn` and `pFrameCountOut` + // properly. + } + + static ma_node_vtable my_custom_node_vtable = + { + my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing. + NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames. + 2, // 2 input buses. + 1, // 1 output bus. + 0 // Default flags. + }; + + ... + + // Each bus needs to have a channel count specified. To do this you need to specify the channel + // counts in an array and then pass that into the node config. + ma_uint32 inputChannels[2]; // Equal in size to the number of input channels specified in the vtable. + ma_uint32 outputChannels[1]; // Equal in size to the number of output channels specicied in the vtable. + + inputChannels[0] = channelsIn; + inputChannels[1] = channelsIn; + outputChannels[0] = channelsOut; + + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.pInputChannels = inputChannels; + nodeConfig.pOutputChannels = outputChannels; + + ma_node_base node; + result = ma_node_init(&nodeGraph, &nodeConfig, NULL, &node); + if (result != MA_SUCCESS) { + // Failed to initialize node. + } + ``` + +When initializing a custom node, as in the code above, you'll normally just place your vtable in +static space. The number of input and output buses are specified as part of the vtable. If you need +a variable number of buses on a per-node bases, the vtable should have the relevant bus count set +to `MA_NODE_BUS_COUNT_UNKNOWN`. In this case, the bus count should be set in the node config: + + ```c + static ma_node_vtable my_custom_node_vtable = + { + my_custom_node_process_pcm_frames, // The function that will be called process your custom node. This is where you'd implement your effect processing. + NULL, // Optional. A callback for calculating the number of input frames that are required to process a specified number of output frames. + MA_NODE_BUS_COUNT_UNKNOWN, // The number of input buses is determined on a per-node basis. + 1, // 1 output bus. + 0 // Default flags. + }; + + ... + + ma_node_config nodeConfig = ma_node_config_init(); + nodeConfig.vtable = &my_custom_node_vtable; + nodeConfig.inputBusCount = myBusCount; // <-- Since the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN, the input bus count should be set here. + nodeConfig.pInputChannels = inputChannels; // <-- Make sure there are nodeConfig.inputBusCount elements in this array. + nodeConfig.pOutputChannels = outputChannels; // <-- The vtable specifies 1 output bus, so there must be 1 element in this array. + ``` + +In the above example it's important to never set the `inputBusCount` and `outputBusCount` members +to anything other than their defaults if the vtable specifies an explicit count. They can only be +set if the vtable specifies MA_NODE_BUS_COUNT_UNKNOWN in the relevant bus count. + +Most often you'll want to create a structure to encapsulate your node with some extra data. You +need to make sure the `ma_node_base` object is your first member of the structure: + + ```c + typedef struct + { + ma_node_base base; // <-- Make sure this is always the first member. + float someCustomData; + } my_custom_node; + ``` + +By doing this, your object will be compatible with all `ma_node` APIs and you can attach it to the +graph just like any other node. + +In the custom processing callback (`my_custom_node_process_pcm_frames()` in the example above), the +number of channels for each bus is what was specified by the config when the node was initialized +with `ma_node_init()`. In addition, all attachments to each of the input buses will have been +pre-mixed by miniaudio. The config allows you to specify different channel counts for each +individual input and output bus. It's up to the effect to handle it appropriate, and if it can't, +return an error in it's initialization routine. + +Custom nodes can be assigned some flags to describe their behaviour. These are set via the vtable +and include the following: + + +-----------------------------------------+---------------------------------------------------+ + | Flag Name | Description | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_PASSTHROUGH | Useful for nodes that do not do any kind of audio | + | | processing, but are instead used for tracking | + | | time, handling events, etc. Also used by the | + | | internal endpoint node. It reads directly from | + | | the input bus to the output bus. Nodes with this | + | | flag must have exactly 1 input bus and 1 output | + | | bus, and both buses must have the same channel | + | | counts. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_CONTINUOUS_PROCESSING | Causes the processing callback to be called even | + | | when no data is available to be read from input | + | | attachments. This is useful for effects like | + | | echos where there will be a tail of audio data | + | | that still needs to be processed even when the | + | | original data sources have reached their ends. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_ALLOW_NULL_INPUT | Used in conjunction with | + | | `MA_NODE_FLAG_CONTINUOUS_PROCESSING`. When this | + | | is set, the `ppFramesIn` parameter of the | + | | processing callback will be set to NULL when | + | | there are no input frames are available. When | + | | this is unset, silence will be posted to the | + | | processing callback. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES | Used to tell miniaudio that input and output | + | | frames are processed at different rates. You | + | | should set this for any nodes that perform | + | | resampling. | + +-----------------------------------------+---------------------------------------------------+ + | MA_NODE_FLAG_SILENT_OUTPUT | Used to tell miniaudio that a node produces only | + | | silent output. This is useful for nodes where you | + | | don't want the output to contribute to the final | + | | mix. An example might be if you want split your | + | | stream and have one branch be output to a file. | + | | When using this flag, you should avoid writing to | + | | the output buffer of the node's processing | + | | callback because miniaudio will ignore it anyway. | + +-----------------------------------------+---------------------------------------------------+ + + +If you need to make a copy of an audio stream for effect processing you can use a splitter node +called `ma_splitter_node`. This takes has 1 input bus and splits the stream into 2 output buses. +You can use it like this: + + ```c + ma_splitter_node_config splitterNodeConfig = ma_splitter_node_config_init(channelsIn, channelsOut); + + ma_splitter_node splitterNode; + result = ma_splitter_node_init(&nodeGraph, &splitterNodeConfig, NULL, &splitterNode); + if (result != MA_SUCCESS) { + // Failed to create node. + } + + // Attach your output buses to two different input buses (can be on two different nodes). + ma_node_attach_output_bus(&splitterNode, 0, ma_node_graph_get_endpoint(&nodeGraph), 0); // Attach directly to the endpoint. + ma_node_attach_output_bus(&splitterNode, 1, &myEffectNode, 0); // Attach to input bus 0 of some effect node. + ``` + +The volume of an output bus can be configured on a per-bus basis: + + ```c + ma_node_set_output_bus_volume(&splitterNode, 0, 0.5f); + ma_node_set_output_bus_volume(&splitterNode, 1, 0.5f); + ``` + +In the code above we're using the splitter node from before and changing the volume of each of the +copied streams. + +You can start and stop a node with the following: + + ```c + ma_node_set_state(&splitterNode, ma_node_state_started); // The default state. + ma_node_set_state(&splitterNode, ma_node_state_stopped); + ``` + +By default the node is in a started state, but since it won't be connected to anything won't +actually be invoked by the node graph until it's connected. When you stop a node, data will not be +read from any of it's input connections. You can use this property to stop a group of sounds +atomically. + +You can configure the initial state of a node in it's config: + + ```c + nodeConfig.initialState = ma_node_state_stopped; + ``` + +Note that for the stock specialized nodes, all of their configs will have a `nodeConfig` member +which is the config to use with the base node. This is where the initial state can be configured +for specialized nodes: + + ```c + dataSourceNodeConfig.nodeConfig.initialState = ma_node_state_stopped; + ``` + +When using a specialized node like `ma_data_source_node` or `ma_splitter_node`, be sure to not +modify the `vtable` member of the `nodeConfig` object. + + +7.1. Timing +----------- +The node graph supports starting and stopping nodes at scheduled times. This is especially useful +for data source nodes where you want to get the node set up, but only start playback at a specific +time. There are two clocks: local and global. + +A local clock is per-node, whereas the global clock is per graph. Scheduling starts and stops can +only be done based on the global clock because the local clock will not be running while the node +is stopped. The global clocks advances whenever `ma_node_graph_read_pcm_frames()` is called. On the +other hand, the local clock only advances when the node's processing callback is fired, and is +advanced based on the output frame count. + +To retrieve the global time, use `ma_node_graph_get_time()`. The global time can be set with +`ma_node_graph_set_time()` which might be useful if you want to do seeking on a global timeline. +Getting and setting the local time is similar. Use `ma_node_get_time()` to retrieve the local time, +and `ma_node_set_time()` to set the local time. The global and local times will be advanced by the +audio thread, so care should be taken to avoid data races. Ideally you should avoid calling these +outside of the node processing callbacks which are always run on the audio thread. + +There is basic support for scheduling the starting and stopping of nodes. You can only schedule one +start and one stop at a time. This is mainly intended for putting nodes into a started or stopped +state in a frame-exact manner. Without this mechanism, starting and stopping of a node is limited +to the resolution of a call to `ma_node_graph_read_pcm_frames()` which would typically be in blocks +of several milliseconds. The following APIs can be used for scheduling node states: + + ```c + ma_node_set_state_time() + ma_node_get_state_time() + ``` + +The time is absolute and must be based on the global clock. An example is below: + + ```c + ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1); // Delay starting to 1 second. + ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5); // Delay stopping to 5 seconds. + ``` + +An example for changing the state using a relative time. + + ```c + ma_node_set_state_time(&myNode, ma_node_state_started, sampleRate*1 + ma_node_graph_get_time(&myNodeGraph)); + ma_node_set_state_time(&myNode, ma_node_state_stopped, sampleRate*5 + ma_node_graph_get_time(&myNodeGraph)); + ``` + +Note that due to the nature of multi-threading the times may not be 100% exact. If this is an +issue, consider scheduling state changes from within a processing callback. An idea might be to +have some kind of passthrough trigger node that is used specifically for tracking time and handling +events. + + + +7.2. Thread Safety and Locking +------------------------------ +When processing audio, it's ideal not to have any kind of locking in the audio thread. Since it's +expected that `ma_node_graph_read_pcm_frames()` would be run on the audio thread, it does so +without the use of any locks. This section discusses the implementation used by miniaudio and goes +over some of the compromises employed by miniaudio to achieve this goal. Note that the current +implementation may not be ideal - feedback and critiques are most welcome. + +The node graph API is not *entirely* lock-free. Only `ma_node_graph_read_pcm_frames()` is expected +to be lock-free. Attachment, detachment and uninitialization of nodes use locks to simplify the +implementation, but are crafted in a way such that such locking is not required when reading audio +data from the graph. Locking in these areas are achieved by means of spinlocks. + +The main complication with keeping `ma_node_graph_read_pcm_frames()` lock-free stems from the fact +that a node can be uninitialized, and it's memory potentially freed, while in the middle of being +processed on the audio thread. There are times when the audio thread will be referencing a node, +which means the uninitialization process of a node needs to make sure it delays returning until the +audio thread is finished so that control is not handed back to the caller thereby giving them a +chance to free the node's memory. + +When the audio thread is processing a node, it does so by reading from each of the output buses of +the node. In order for a node to process data for one of it's output buses, it needs to read from +each of it's input buses, and so on an so forth. It follows that once all output buses of a node +are detached, the node as a whole will be disconnected and no further processing will occur unless +it's output buses are reattached, which won't be happening when the node is being uninitialized. +By having `ma_node_detach_output_bus()` wait until the audio thread is finished with it, we can +simplify a few things, at the expense of making `ma_node_detach_output_bus()` a bit slower. By +doing this, the implementation of `ma_node_uninit()` becomes trivial - just detach all output +nodes, followed by each of the attachments to each of it's input nodes, and then do any final clean +up. + +With the above design, the worst-case scenario is `ma_node_detach_output_bus()` taking as long as +it takes to process the output bus being detached. This will happen if it's called at just the +wrong moment where the audio thread has just iterated it and has just started processing. The +caller of `ma_node_detach_output_bus()` will stall until the audio thread is finished, which +includes the cost of recursively processing it's inputs. This is the biggest compromise made with +the approach taken by miniaudio for it's lock-free processing system. The cost of detaching nodes +earlier in the pipeline (data sources, for example) will be cheaper than the cost of detaching +higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass +detachments, detach starting from the lowest level nodes and work your way towards the final +endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not +running, detachment will be fast and detachment in any order will be the same. The reason nodes +need to wait for their input attachments to complete is due to the potential for desyncs between +data sources. If the node was to terminate processing mid way through processing it's inputs, +there's a chance that some of the underlying data sources will have been read, but then others not. +That will then result in a potential desynchronization when detaching and reattaching higher-level +nodes. A possible solution to this is to have an option when detaching to terminate processing +before processing all input attachments which should be fairly simple. + +Another compromise, albeit less significant, is locking when attaching and detaching nodes. This +locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present +for each input bus and output bus. When an output bus is connected to an input bus, both the output +bus and input bus is locked. This locking is specifically for attaching and detaching across +different threads and does not affect `ma_node_graph_read_pcm_frames()` in any way. The locking and +unlocking is mostly self-explanatory, but a slightly less intuitive aspect comes into it when +considering that iterating over attachments must not break as a result of attaching or detaching a +node while iteration is occuring. + +Attaching and detaching are both quite simple. When an output bus of a node is attached to an input +bus of another node, it's added to a linked list. Basically, an input bus is a linked list, where +each item in the list is and output bus. We have some intentional (and convenient) restrictions on +what can done with the linked list in order to simplify the implementation. First of all, whenever +something needs to iterate over the list, it must do so in a forward direction. Backwards iteration +is not supported. Also, items can only be added to the start of the list. + +The linked list is a doubly-linked list where each item in the list (an output bus) holds a pointer +to the next item in the list, and another to the previous item. A pointer to the previous item is +only required for fast detachment of the node - it is never used in iteration. This is an +important property because it means from the perspective of iteration, attaching and detaching of +an item can be done with a single atomic assignment. This is exploited by both the attachment and +detachment process. When attaching the node, the first thing that is done is the setting of the +local "next" and "previous" pointers of the node. After that, the item is "attached" to the list +by simply performing an atomic exchange with the head pointer. After that, the node is "attached" +to the list from the perspective of iteration. Even though the "previous" pointer of the next item +hasn't yet been set, from the perspective of iteration it's been attached because iteration will +only be happening in a forward direction which means the "previous" pointer won't actually ever get +used. The same general process applies to detachment. See `ma_node_attach_output_bus()` and +`ma_node_detach_output_bus()` for the implementation of this mechanism. + + + +8. Decoding =========== -The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from devices and can be used independently. The following formats are -supported: +The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from +devices and can be used independently. The following formats are supported: +---------+------------------+----------+ | Format | Decoding Backend | Built-In | @@ -473,7 +2403,8 @@ supported: | Vorbis | stb_vorbis | No | +---------+------------------+----------+ -Vorbis is supported via stb_vorbis which can be enabled by including the header section before the implementation of miniaudio, like the following: +Vorbis is supported via stb_vorbis which can be enabled by including the header section before the +implementation of miniaudio, like the following: ```c #define STB_VORBIS_HEADER_ONLY @@ -489,8 +2420,9 @@ Vorbis is supported via stb_vorbis which can be enabled by including the header A copy of stb_vorbis is included in the "extras" folder in the miniaudio repository (https://github.com/mackron/miniaudio). -Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the built-in decoders by specifying one or more of the -following options before the miniaudio implementation: +Built-in decoders are amalgamated into the implementation section of miniaudio. You can disable the +built-in decoders by specifying one or more of the following options before the miniaudio +implementation: ```c #define MA_NO_WAV @@ -498,10 +2430,12 @@ following options before the miniaudio implementation: #define MA_NO_FLAC ``` -Disabling built-in decoding libraries is useful if you use these libraries independantly of the `ma_decoder` API. +Disabling built-in decoding libraries is useful if you use these libraries independantly of the +`ma_decoder` API. -A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with `ma_decoder_init_memory()`, or from data delivered via callbacks -with `ma_decoder_init()`. Here is an example for loading a decoder from a file: +A decoder can be initialized from a file with `ma_decoder_init_file()`, a block of memory with +`ma_decoder_init_memory()`, or from data delivered via callbacks with `ma_decoder_init()`. Here is +an example for loading a decoder from a file: ```c ma_decoder decoder; @@ -515,20 +2449,23 @@ with `ma_decoder_init()`. Here is an example for loading a decoder from a file: ma_decoder_uninit(&decoder); ``` -When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object (the `NULL` argument in the example above) which allows you -to configure the output format, channel count, sample rate and channel map: +When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object +(the `NULL` argument in the example above) which allows you to configure the output format, channel +count, sample rate and channel map: ```c ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, 48000); ``` -When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the same as that defined by the decoding backend. +When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the +same as that defined by the decoding backend. -Data is read from the decoder as PCM frames. This will return the number of PCM frames actually read. If the return value is less than the requested number of -PCM frames it means you've reached the end: +Data is read from the decoder as PCM frames. This will output the number of PCM frames actually +read. If this is less than the requested number of PCM frames it means you've reached the end. The +return value will be `MA_AT_END` if no samples have been read and the end has been reached. ```c - ma_uint64 framesRead = ma_decoder_read_pcm_frames(pDecoder, pFrames, framesToRead); + ma_result result = ma_decoder_read_pcm_frames(pDecoder, pFrames, framesToRead, &framesRead); if (framesRead < framesToRead) { // Reached the end. } @@ -549,8 +2486,10 @@ If you want to loop back to the start, you can simply seek back to the first PCM ma_decoder_seek_to_pcm_frame(pDecoder, 0); ``` -When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding backend. This can be unnecessarily inefficient if the type -is already known. In this case you can use `encodingFormat` variable in the device config to specify a specific encoding format you want to decode: +When loading a decoder, miniaudio uses a trial and error technique to find the appropriate decoding +backend. This can be unnecessarily inefficient if the type is already known. In this case you can +use `encodingFormat` variable in the device config to specify a specific encoding format you want +to decode: ```c decoderConfig.encodingFormat = ma_encoding_format_wav; @@ -558,24 +2497,95 @@ is already known. In this case you can use `encodingFormat` variable in the devi See the `ma_encoding_format` enum for possible encoding formats. -The `ma_decoder_init_file()` API will try using the file extension to determine which decoding backend to prefer. +The `ma_decoder_init_file()` API will try using the file extension to determine which decoding +backend to prefer. + + +8.1. Custom Decoders +-------------------- +It's possible to implement a custom decoder and plug it into miniaudio. This is extremely useful +when you want to use the `ma_decoder` API, but need to support an encoding format that's not one of +the stock formats supported by miniaudio. This can be put to particularly good use when using the +`ma_engine` and/or `ma_resource_manager` APIs because they use `ma_decoder` internally. If, for +example, you wanted to support Opus, you can do so with a custom decoder (there if a reference +Opus decoder in the "extras" folder of the miniaudio repository which uses libopus + libopusfile). + +A custom decoder must implement a data source. A vtable called `ma_decoding_backend_vtable` needs +to be implemented which is then passed into the decoder config: + + ```c + ma_decoding_backend_vtable* pCustomBackendVTables[] = + { + &g_ma_decoding_backend_vtable_libvorbis, + &g_ma_decoding_backend_vtable_libopus + }; + + ... + + decoderConfig = ma_decoder_config_init_default(); + decoderConfig.pCustomBackendUserData = NULL; + decoderConfig.ppCustomBackendVTables = pCustomBackendVTables; + decoderConfig.customBackendCount = sizeof(pCustomBackendVTables) / sizeof(pCustomBackendVTables[0]); + ``` + +The `ma_decoding_backend_vtable` vtable has the following functions: + + ``` + onInit + onInitFile + onInitFileW + onInitMemory + onUninit + ``` + +There are only two functions that must be implemented - `onInit` and `onUninit`. The other +functions can be implemented for a small optimization for loading from a file path or memory. If +these are not specified, miniaudio will deal with it for you via a generic implementation. + +When you initialize a custom data source (by implementing the `onInit` function in the vtable) you +will need to output a pointer to a `ma_data_source` which implements your custom decoder. See the +section about data sources for details on how to implemen this. Alternatively, see the +"custom_decoders" example in the miniaudio repository. + +The `onInit` function takes a pointer to some callbacks for the purpose of reading raw audio data +from some abitrary source. You'll use these functions to read from the raw data and perform the +decoding. When you call them, you will pass in the `pReadSeekTellUserData` pointer to the relevant +parameter. + +The `pConfig` parameter in `onInit` can be used to configure the backend if appropriate. It's only +used as a hint and can be ignored. However, if any of the properties are relevant to your decoder, +an optimal implementation will handle the relevant properties appropriately. + +If memory allocation is required, it should be done so via the specified allocation callbacks if +possible (the `pAllocationCallbacks` parameter). + +If an error occurs when initializing the decoder, you should leave `ppBackend` unset, or set to +NULL, and make sure everything is cleaned up appropriately and an appropriate result code returned. +When multiple custom backends are specified, miniaudio will cycle through the vtables in the order +they're listed in the array that's passed into the decoder config so it's important that your +initialization routine is clean. + +When a decoder is uninitialized, the `onUninit` callback will be fired which will give you an +opportunity to clean up and internal data. -5. Encoding +9. Encoding =========== -The `ma_encoding` API is used for writing audio files. The only supported output format is WAV which is achieved via dr_wav which is amalgamated into the -implementation section of miniaudio. This can be disabled by specifying the following option before the implementation of miniaudio: +The `ma_encoding` API is used for writing audio files. The only supported output format is WAV +which is achieved via dr_wav which is amalgamated into the implementation section of miniaudio. +This can be disabled by specifying the following option before the implementation of miniaudio: ```c #define MA_NO_WAV ``` -An encoder can be initialized to write to a file with `ma_encoder_init_file()` or from data delivered via callbacks with `ma_encoder_init()`. Below is an -example for initializing an encoder to output to a file. +An encoder can be initialized to write to a file with `ma_encoder_init_file()` or from data +delivered via callbacks with `ma_encoder_init()`. Below is an example for initializing an encoder +to output to a file. ```c - ma_encoder_config config = ma_encoder_config_init(ma_resource_format_wav, FORMAT, CHANNELS, SAMPLE_RATE); + ma_encoder_config config = ma_encoder_config_init(ma_encoding_format_wav, FORMAT, CHANNELS, SAMPLE_RATE); ma_encoder encoder; ma_result result = ma_encoder_init_file("my_file.wav", &config, &encoder); if (result != MA_SUCCESS) { @@ -587,17 +2597,20 @@ example for initializing an encoder to output to a file. ma_encoder_uninit(&encoder); ``` -When initializing an encoder you must specify a config which is initialized with `ma_encoder_config_init()`. Here you must specify the file type, the output -sample format, output channel count and output sample rate. The following file types are supported: +When initializing an encoder you must specify a config which is initialized with +`ma_encoder_config_init()`. Here you must specify the file type, the output sample format, output +channel count and output sample rate. The following file types are supported: +------------------------+-------------+ | Enum | Description | +------------------------+-------------+ - | ma_resource_format_wav | WAV | + | ma_encoding_format_wav | WAV | +------------------------+-------------+ -If the format, channel count or sample rate is not supported by the output file type an error will be returned. The encoder will not perform data conversion so -you will need to convert it before outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frames()`, like in the example below: +If the format, channel count or sample rate is not supported by the output file type an error will +be returned. The encoder will not perform data conversion so you will need to convert it before +outputting any audio data. To output audio data, use `ma_encoder_write_pcm_frames()`, like in the +example below: ```c framesWritten = ma_encoder_write_pcm_frames(&encoder, pPCMFramesToWrite, framesToWrite); @@ -606,21 +2619,25 @@ you will need to convert it before outputting any audio data. To output audio da Encoders must be uninitialized with `ma_encoder_uninit()`. -6. Data Conversion -================== -A data conversion API is included with miniaudio which supports the majority of data conversion requirements. This supports conversion between sample formats, -channel counts (with channel mapping) and sample rates. + +10. Data Conversion +=================== +A data conversion API is included with miniaudio which supports the majority of data conversion +requirements. This supports conversion between sample formats, channel counts (with channel +mapping) and sample rates. -6.1. Sample Format Conversion ------------------------------ -Conversion between sample formats is achieved with the `ma_pcm_*_to_*()`, `ma_pcm_convert()` and `ma_convert_pcm_frames_format()` APIs. Use `ma_pcm_*_to_*()` -to convert between two specific formats. Use `ma_pcm_convert()` to convert based on a `ma_format` variable. Use `ma_convert_pcm_frames_format()` to convert -PCM frames where you want to specify the frame count and channel count as a variable instead of the total sample count. +10.1. Sample Format Conversion +------------------------------ +Conversion between sample formats is achieved with the `ma_pcm_*_to_*()`, `ma_pcm_convert()` and +`ma_convert_pcm_frames_format()` APIs. Use `ma_pcm_*_to_*()` to convert between two specific +formats. Use `ma_pcm_convert()` to convert based on a `ma_format` variable. Use +`ma_convert_pcm_frames_format()` to convert PCM frames where you want to specify the frame count +and channel count as a variable instead of the total sample count. -6.1.1. Dithering ----------------- +10.1.1. Dithering +----------------- Dithering can be set using the ditherMode parameter. The different dithering modes include the following, in order of efficiency: @@ -633,8 +2650,9 @@ The different dithering modes include the following, in order of efficiency: | Triangle | ma_dither_mode_triangle | +-----------+--------------------------+ -Note that even if the dither mode is set to something other than `ma_dither_mode_none`, it will be ignored for conversions where dithering is not needed. -Dithering is available for the following conversions: +Note that even if the dither mode is set to something other than `ma_dither_mode_none`, it will be +ignored for conversions where dithering is not needed. Dithering is available for the following +conversions: ``` s16 -> u8 @@ -646,14 +2664,16 @@ Dithering is available for the following conversions: f32 -> s16 ``` -Note that it is not an error to pass something other than ma_dither_mode_none for conversions where dither is not used. It will just be ignored. +Note that it is not an error to pass something other than ma_dither_mode_none for conversions where +dither is not used. It will just be ignored. -6.2. Channel Conversion ------------------------ -Channel conversion is used for channel rearrangement and conversion from one channel count to another. The `ma_channel_converter` API is used for channel -conversion. Below is an example of initializing a simple channel converter which converts from mono to stereo. +10.2. Channel Conversion +------------------------ +Channel conversion is used for channel rearrangement and conversion from one channel count to +another. The `ma_channel_converter` API is used for channel conversion. Below is an example of +initializing a simple channel converter which converts from mono to stereo. ```c ma_channel_converter_config config = ma_channel_converter_config_init( @@ -664,7 +2684,7 @@ conversion. Below is an example of initializing a simple channel converter which NULL, // Output channel map ma_channel_mix_mode_default); // The mixing algorithm to use when combining channels. - result = ma_channel_converter_init(&config, &converter); + result = ma_channel_converter_init(&config, NULL, &converter); if (result != MA_SUCCESS) { // Error. } @@ -679,34 +2699,43 @@ To perform the conversion simply call `ma_channel_converter_process_pcm_frames() } ``` -It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM frames. +It is up to the caller to ensure the output buffer is large enough to accomodate the new PCM +frames. Input and output PCM frames are always interleaved. Deinterleaved layouts are not supported. -6.2.1. Channel Mapping ----------------------- -In addition to converting from one channel count to another, like the example above, the channel converter can also be used to rearrange channels. When -initializing the channel converter, you can optionally pass in channel maps for both the input and output frames. If the channel counts are the same, and each -channel map contains the same channel positions with the exception that they're in a different order, a simple shuffling of the channels will be performed. If, -however, there is not a 1:1 mapping of channel positions, or the channel counts differ, the input channels will be mixed based on a mixing mode which is -specified when initializing the `ma_channel_converter_config` object. +10.2.1. Channel Mapping +----------------------- +In addition to converting from one channel count to another, like the example above, the channel +converter can also be used to rearrange channels. When initializing the channel converter, you can +optionally pass in channel maps for both the input and output frames. If the channel counts are the +same, and each channel map contains the same channel positions with the exception that they're in +a different order, a simple shuffling of the channels will be performed. If, however, there is not +a 1:1 mapping of channel positions, or the channel counts differ, the input channels will be mixed +based on a mixing mode which is specified when initializing the `ma_channel_converter_config` +object. -When converting from mono to multi-channel, the mono channel is simply copied to each output channel. When going the other way around, the audio of each output -channel is simply averaged and copied to the mono channel. +When converting from mono to multi-channel, the mono channel is simply copied to each output +channel. When going the other way around, the audio of each output channel is simply averaged and +copied to the mono channel. -In more complicated cases blending is used. The `ma_channel_mix_mode_simple` mode will drop excess channels and silence extra channels. For example, converting -from 4 to 2 channels, the 3rd and 4th channels will be dropped, whereas converting from 2 to 4 channels will put silence into the 3rd and 4th channels. +In more complicated cases blending is used. The `ma_channel_mix_mode_simple` mode will drop excess +channels and silence extra channels. For example, converting from 4 to 2 channels, the 3rd and 4th +channels will be dropped, whereas converting from 2 to 4 channels will put silence into the 3rd and +4th channels. -The `ma_channel_mix_mode_rectangle` mode uses spacial locality based on a rectangle to compute a simple distribution between input and output. Imagine sitting -in the middle of a room, with speakers on the walls representing channel positions. The MA_CHANNEL_FRONT_LEFT position can be thought of as being in the corner -of the front and left walls. +The `ma_channel_mix_mode_rectangle` mode uses spacial locality based on a rectangle to compute a +simple distribution between input and output. Imagine sitting in the middle of a room, with +speakers on the walls representing channel positions. The `MA_CHANNEL_FRONT_LEFT` position can be +thought of as being in the corner of the front and left walls. -Finally, the `ma_channel_mix_mode_custom_weights` mode can be used to use custom user-defined weights. Custom weights can be passed in as the last parameter of +Finally, the `ma_channel_mix_mode_custom_weights` mode can be used to use custom user-defined +weights. Custom weights can be passed in as the last parameter of `ma_channel_converter_config_init()`. -Predefined channel maps can be retrieved with `ma_get_standard_channel_map()`. This takes a `ma_standard_channel_map` enum as it's first parameter, which can -be one of the following: +Predefined channel maps can be retrieved with `ma_channel_map_init_standard()`. This takes a +`ma_standard_channel_map` enum as it's first parameter, which can be one of the following: +-----------------------------------+-----------------------------------------------------------+ | Name | Description | @@ -778,9 +2807,10 @@ Below are the channel maps used by default in miniaudio (`ma_standard_channel_ma -6.3. Resampling ---------------- -Resampling is achieved with the `ma_resampler` object. To create a resampler object, do something like the following: +10.3. Resampling +---------------- +Resampling is achieved with the `ma_resampler` object. To create a resampler object, do something +like the following: ```c ma_resampler_config config = ma_resampler_config_init( @@ -817,104 +2847,128 @@ The following example shows how data can be processed // number of output frames written. ``` -To initialize the resampler you first need to set up a config (`ma_resampler_config`) with `ma_resampler_config_init()`. You need to specify the sample format -you want to use, the number of channels, the input and output sample rate, and the algorithm. +To initialize the resampler you first need to set up a config (`ma_resampler_config`) with +`ma_resampler_config_init()`. You need to specify the sample format you want to use, the number of +channels, the input and output sample rate, and the algorithm. -The sample format can be either `ma_format_s16` or `ma_format_f32`. If you need a different format you will need to perform pre- and post-conversions yourself -where necessary. Note that the format is the same for both input and output. The format cannot be changed after initialization. +The sample format can be either `ma_format_s16` or `ma_format_f32`. If you need a different format +you will need to perform pre- and post-conversions yourself where necessary. Note that the format +is the same for both input and output. The format cannot be changed after initialization. -The resampler supports multiple channels and is always interleaved (both input and output). The channel count cannot be changed after initialization. +The resampler supports multiple channels and is always interleaved (both input and output). The +channel count cannot be changed after initialization. -The sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the -only configuration property that can be changed after initialization. +The sample rates can be anything other than zero, and are always specified in hertz. They should be +set to something like 44100, etc. The sample rate is the only configuration property that can be +changed after initialization. -The miniaudio resampler supports multiple algorithms: +The miniaudio resampler has built-in support for the following algorithms: +-----------+------------------------------+ | Algorithm | Enum Token | +-----------+------------------------------+ | Linear | ma_resample_algorithm_linear | - | Speex | ma_resample_algorithm_speex | + | Custom | ma_resample_algorithm_custom | +-----------+------------------------------+ -Because Speex is not public domain it is strictly opt-in and the code is stored in separate files. if you opt-in to the Speex backend you will need to consider -it's license, the text of which can be found in it's source files in "extras/speex_resampler". Details on how to opt-in to the Speex resampler is explained in -the Speex Resampler section below. - The algorithm cannot be changed after initialization. -Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process -frames, use `ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number of -input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer and the -number of input frames that were consumed in the process. You can pass in NULL for the input buffer in which case it will be treated as an infinitely large -buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. +Processing always happens on a per PCM frame basis and always assumes interleaved input and output. +De-interleaved processing is not supported. To process frames, use +`ma_resampler_process_pcm_frames()`. On input, this function takes the number of output frames you +can fit in the output buffer and the number of input frames contained in the input buffer. On +output these variables contain the number of output frames that were written to the output buffer +and the number of input frames that were consumed in the process. You can pass in NULL for the +input buffer in which case it will be treated as an infinitely large buffer of zeros. The output +buffer can also be NULL, in which case the processing will be treated as seek. -The sample rate can be changed dynamically on the fly. You can change this with explicit sample rates with `ma_resampler_set_rate()` and also with a decimal -ratio with `ma_resampler_set_rate_ratio()`. The ratio is in/out. +The sample rate can be changed dynamically on the fly. You can change this with explicit sample +rates with `ma_resampler_set_rate()` and also with a decimal ratio with +`ma_resampler_set_rate_ratio()`. The ratio is in/out. -Sometimes it's useful to know exactly how many input frames will be required to output a specific number of frames. You can calculate this with -`ma_resampler_get_required_input_frame_count()`. Likewise, it's sometimes useful to know exactly how many frames would be output given a certain number of -input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. +Sometimes it's useful to know exactly how many input frames will be required to output a specific +number of frames. You can calculate this with `ma_resampler_get_required_input_frame_count()`. +Likewise, it's sometimes useful to know exactly how many frames would be output given a certain +number of input frames. You can do this with `ma_resampler_get_expected_output_frame_count()`. -Due to the nature of how resampling works, the resampler introduces some latency. This can be retrieved in terms of both the input rate and the output rate -with `ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. +Due to the nature of how resampling works, the resampler introduces some latency. This can be +retrieved in terms of both the input rate and the output rate with +`ma_resampler_get_input_latency()` and `ma_resampler_get_output_latency()`. -6.3.1. Resampling Algorithms ----------------------------- -The choice of resampling algorithm depends on your situation and requirements. The linear resampler is the most efficient and has the least amount of latency, -but at the expense of poorer quality. The Speex resampler is higher quality, but slower with more latency. It also performs several heap allocations internally -for memory management. +10.3.1. Resampling Algorithms +----------------------------- +The choice of resampling algorithm depends on your situation and requirements. -6.3.1.1. Linear Resampling --------------------------- -The linear resampler is the fastest, but comes at the expense of poorer quality. There is, however, some control over the quality of the linear resampler which -may make it a suitable option depending on your requirements. +10.3.1.1. Linear Resampling +--------------------------- +The linear resampler is the fastest, but comes at the expense of poorer quality. There is, however, +some control over the quality of the linear resampler which may make it a suitable option depending +on your requirements. -The linear resampler performs low-pass filtering before or after downsampling or upsampling, depending on the sample rates you're converting between. When -decreasing the sample rate, the low-pass filter will be applied before downsampling. When increasing the rate it will be performed after upsampling. By default -a fourth order low-pass filter will be applied. This can be configured via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. +The linear resampler performs low-pass filtering before or after downsampling or upsampling, +depending on the sample rates you're converting between. When decreasing the sample rate, the +low-pass filter will be applied before downsampling. When increasing the rate it will be performed +after upsampling. By default a fourth order low-pass filter will be applied. This can be configured +via the `lpfOrder` configuration variable. Setting this to 0 will disable filtering. -The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of the input and output sample rates (Nyquist Frequency). This -can be controlled with the `lpfNyquistFactor` config variable. This defaults to 1, and should be in the range of 0..1, although a value of 0 does not make -sense and should be avoided. A value of 1 will use the Nyquist Frequency as the cutoff. A value of 0.5 will use half the Nyquist Frequency as the cutoff, etc. -Values less than 1 will result in more washed out sound due to more of the higher frequencies being removed. This config variable has no impact on performance -and is a purely perceptual configuration. +The low-pass filter has a cutoff frequency which defaults to half the sample rate of the lowest of +the input and output sample rates (Nyquist Frequency). -The API for the linear resampler is the same as the main resampler API, only it's called `ma_linear_resampler`. +The API for the linear resampler is the same as the main resampler API, only it's called +`ma_linear_resampler`. -6.3.1.2. Speex Resampling +10.3.2. Custom Resamplers ------------------------- -The Speex resampler is made up of third party code which is released under the BSD license. Because it is licensed differently to miniaudio, which is public -domain, it is strictly opt-in and all of it's code is stored in separate files. If you opt-in to the Speex resampler you must consider the license text in it's -source files. To opt-in, you must first `#include` the following file before the implementation of miniaudio.h: +You can implement a custom resampler by using the `ma_resample_algorithm_custom` resampling +algorithm and setting a vtable in the resampler config: ```c - #include "extras/speex_resampler/ma_speex_resampler.h" + ma_resampler_config config = ma_resampler_config_init(..., ma_resample_algorithm_custom); + config.pBackendVTable = &g_customResamplerVTable; ``` -Both the header and implementation is contained within the same file. The implementation can be included in your program like so: +Custom resamplers are useful if the stock algorithms are not appropriate for your use case. You +need to implement the required functions in `ma_resampling_backend_vtable`. Note that not all +functions in the vtable need to be implemented, but if it's possible to implement, they should be. - ```c - #define MINIAUDIO_SPEEX_RESAMPLER_IMPLEMENTATION - #include "extras/speex_resampler/ma_speex_resampler.h" - ``` +You can use the `ma_linear_resampler` object for an example on how to implement the vtable. The +`onGetHeapSize` callback is used to calculate the size of any internal heap allocation the custom +resampler will need to make given the supplied config. When you initialize the resampler via the +`onInit` callback, you'll be given a pointer to a heap allocation which is where you should store +the heap allocated data. You should not free this data in `onUninit` because miniaudio will manage +it for you. -Note that even if you opt-in to the Speex backend, miniaudio won't use it unless you explicitly ask for it in the respective config of the object you are -initializing. If you try to use the Speex resampler without opting in, initialization of the `ma_resampler` object will fail with `MA_NO_BACKEND`. +The `onProcess` callback is where the actual resampling takes place. On input, `pFrameCountIn` +points to a variable containing the number of frames in the `pFramesIn` buffer and +`pFrameCountOut` points to a variable containing the capacity in frames of the `pFramesOut` buffer. +On output, `pFrameCountIn` should be set to the number of input frames that were fully consumed, +whereas `pFrameCountOut` should be set to the number of frames that were written to `pFramesOut`. -The only configuration option to consider with the Speex resampler is the `speex.quality` config variable. This is a value between 0 and 10, with 0 being -the fastest with the poorest quality and 10 being the slowest with the highest quality. The default value is 3. +The `onSetRate` callback is optional and is used for dynamically changing the sample rate. If +dynamic rate changes are not supported, you can set this callback to NULL. + +The `onGetInputLatency` and `onGetOutputLatency` functions are used for retrieving the latency in +input and output rates respectively. These can be NULL in which case latency calculations will be +assumed to be NULL. + +The `onGetRequiredInputFrameCount` callback is used to give miniaudio a hint as to how many input +frames are required to be available to produce the given number of output frames. Likewise, the +`onGetExpectedOutputFrameCount` callback is used to determine how many output frames will be +produced given the specified number of input frames. miniaudio will use these as a hint, but they +are optional and can be set to NULL if you're unable to implement them. -6.4. General Data Conversion ----------------------------- -The `ma_data_converter` API can be used to wrap sample format conversion, channel conversion and resampling into one operation. This is what miniaudio uses -internally to convert between the format requested when the device was initialized and the format of the backend's native device. The API for general data -conversion is very similar to the resampling API. Create a `ma_data_converter` object like this: +10.4. General Data Conversion +----------------------------- +The `ma_data_converter` API can be used to wrap sample format conversion, channel conversion and +resampling into one operation. This is what miniaudio uses internally to convert between the format +requested when the device was initialized and the format of the backend's native device. The API +for general data conversion is very similar to the resampling API. Create a `ma_data_converter` +object like this: ```c ma_data_converter_config config = ma_data_converter_config_init( @@ -927,14 +2981,15 @@ conversion is very similar to the resampling API. Create a `ma_data_converter` o ); ma_data_converter converter; - ma_result result = ma_data_converter_init(&config, &converter); + ma_result result = ma_data_converter_init(&config, NULL, &converter); if (result != MA_SUCCESS) { // An error occurred... } ``` -In the example above we use `ma_data_converter_config_init()` to initialize the config, however there's many more properties that can be configured, such as -channel maps and resampling quality. Something like the following may be more suitable depending on your requirements: +In the example above we use `ma_data_converter_config_init()` to initialize the config, however +there's many more properties that can be configured, such as channel maps and resampling quality. +Something like the following may be more suitable depending on your requirements: ```c ma_data_converter_config config = ma_data_converter_config_init_default(); @@ -944,14 +2999,14 @@ channel maps and resampling quality. Something like the following may be more su config.channelsOut = outputChannels; config.sampleRateIn = inputSampleRate; config.sampleRateOut = outputSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_flac, config.channelCountIn, config.channelMapIn); + ma_channel_map_init_standard(ma_standard_channel_map_flac, config.channelMapIn, sizeof(config.channelMapIn)/sizeof(config.channelMapIn[0]), config.channelCountIn); config.resampling.linear.lpfOrder = MA_MAX_FILTER_ORDER; ``` Do the following to uninitialize the data converter: ```c - ma_data_converter_uninit(&converter); + ma_data_converter_uninit(&converter, NULL); ``` The following example shows how data can be processed @@ -968,33 +3023,42 @@ The following example shows how data can be processed // of output frames written. ``` -The data converter supports multiple channels and is always interleaved (both input and output). The channel count cannot be changed after initialization. +The data converter supports multiple channels and is always interleaved (both input and output). +The channel count cannot be changed after initialization. -Sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the only -configuration property that can be changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of `ma_data_converter_config` is -set to `MA_TRUE`. To change the sample rate, use `ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. The -resampling algorithm cannot be changed after initialization. +Sample rates can be anything other than zero, and are always specified in hertz. They should be set +to something like 44100, etc. The sample rate is the only configuration property that can be +changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of +`ma_data_converter_config` is set to `MA_TRUE`. To change the sample rate, use +`ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. +The resampling algorithm cannot be changed after initialization. -Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process -frames, use `ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number -of input frames contained in the input buffer. On output these variables contain the number of output frames that were written to the output buffer and the -number of input frames that were consumed in the process. You can pass in NULL for the input buffer in which case it will be treated as an infinitely large -buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated as seek. +Processing always happens on a per PCM frame basis and always assumes interleaved input and output. +De-interleaved processing is not supported. To process frames, use +`ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames +you can fit in the output buffer and the number of input frames contained in the input buffer. On +output these variables contain the number of output frames that were written to the output buffer +and the number of input frames that were consumed in the process. You can pass in NULL for the +input buffer in which case it will be treated as an infinitely large +buffer of zeros. The output buffer can also be NULL, in which case the processing will be treated +as seek. -Sometimes it's useful to know exactly how many input frames will be required to output a specific number of frames. You can calculate this with -`ma_data_converter_get_required_input_frame_count()`. Likewise, it's sometimes useful to know exactly how many frames would be output given a certain number of -input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. +Sometimes it's useful to know exactly how many input frames will be required to output a specific +number of frames. You can calculate this with `ma_data_converter_get_required_input_frame_count()`. +Likewise, it's sometimes useful to know exactly how many frames would be output given a certain +number of input frames. You can do this with `ma_data_converter_get_expected_output_frame_count()`. -Due to the nature of how resampling works, the data converter introduces some latency if resampling is required. This can be retrieved in terms of both the -input rate and the output rate with `ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. +Due to the nature of how resampling works, the data converter introduces some latency if resampling +is required. This can be retrieved in terms of both the input rate and the output rate with +`ma_data_converter_get_input_latency()` and `ma_data_converter_get_output_latency()`. -7. Filtering -============ +11. Filtering +============= -7.1. Biquad Filtering ---------------------- +11.1. Biquad Filtering +---------------------- Biquad filtering is achieved with the `ma_biquad` API. Example: ```c @@ -1009,28 +3073,33 @@ Biquad filtering is achieved with the `ma_biquad` API. Example: ma_biquad_process_pcm_frames(&biquad, pFramesOut, pFramesIn, frameCount); ``` -Biquad filtering is implemented using transposed direct form 2. The numerator coefficients are b0, b1 and b2, and the denominator coefficients are a0, a1 and -a2. The a0 coefficient is required and coefficients must not be pre-normalized. +Biquad filtering is implemented using transposed direct form 2. The numerator coefficients are b0, +b1 and b2, and the denominator coefficients are a0, a1 and a2. The a0 coefficient is required and +coefficients must not be pre-normalized. -Supported formats are `ma_format_s16` and `ma_format_f32`. If you need to use a different format you need to convert it yourself beforehand. When using -`ma_format_s16` the biquad filter will use fixed point arithmetic. When using `ma_format_f32`, floating point arithmetic will be used. +Supported formats are `ma_format_s16` and `ma_format_f32`. If you need to use a different format +you need to convert it yourself beforehand. When using `ma_format_s16` the biquad filter will use +fixed point arithmetic. When using `ma_format_f32`, floating point arithmetic will be used. Input and output frames are always interleaved. -Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so: +Filtering can be applied in-place by passing in the same pointer for both the input and output +buffers, like so: ```c ma_biquad_process_pcm_frames(&biquad, pMyData, pMyData, frameCount); ``` -If you need to change the values of the coefficients, but maintain the values in the registers you can do so with `ma_biquad_reinit()`. This is useful if you -need to change the properties of the filter while keeping the values of registers valid to avoid glitching. Do not use `ma_biquad_init()` for this as it will -do a full initialization which involves clearing the registers to 0. Note that changing the format or channel count after initialization is invalid and will -result in an error. +If you need to change the values of the coefficients, but maintain the values in the registers you +can do so with `ma_biquad_reinit()`. This is useful if you need to change the properties of the +filter while keeping the values of registers valid to avoid glitching. Do not use +`ma_biquad_init()` for this as it will do a full initialization which involves clearing the +registers to 0. Note that changing the format or channel count after initialization is invalid and +will result in an error. -7.2. Low-Pass Filtering ------------------------ +11.2. Low-Pass Filtering +------------------------ Low-pass filtering is achieved with the following APIs: +---------+------------------------------------------+ @@ -1055,16 +3124,18 @@ Low-pass filter example: ma_lpf_process_pcm_frames(&lpf, pFramesOut, pFramesIn, frameCount); ``` -Supported formats are `ma_format_s16` and` ma_format_f32`. If you need to use a different format you need to convert it yourself beforehand. Input and output -frames are always interleaved. +Supported formats are `ma_format_s16` and` ma_format_f32`. If you need to use a different format +you need to convert it yourself beforehand. Input and output frames are always interleaved. -Filtering can be applied in-place by passing in the same pointer for both the input and output buffers, like so: +Filtering can be applied in-place by passing in the same pointer for both the input and output +buffers, like so: ```c ma_lpf_process_pcm_frames(&lpf, pMyData, pMyData, frameCount); ``` -The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, you can chain first and second order filters together. +The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, +you can chain first and second order filters together. ```c for (iFilter = 0; iFilter < filterCount; iFilter += 1) { @@ -1072,19 +3143,22 @@ The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. } ``` -If you need to change the configuration of the filter, but need to maintain the state of internal registers you can do so with `ma_lpf_reinit()`. This may be -useful if you need to change the sample rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the format or channel -count after initialization is invalid and will result in an error. +If you need to change the configuration of the filter, but need to maintain the state of internal +registers you can do so with `ma_lpf_reinit()`. This may be useful if you need to change the sample +rate and/or cutoff frequency dynamically while maintaing smooth transitions. Note that changing the +format or channel count after initialization is invalid and will result in an error. -The `ma_lpf` object supports a configurable order, but if you only need a first order filter you may want to consider using `ma_lpf1`. Likewise, if you only -need a second order filter you can use `ma_lpf2`. The advantage of this is that they're lighter weight and a bit more efficient. +The `ma_lpf` object supports a configurable order, but if you only need a first order filter you +may want to consider using `ma_lpf1`. Likewise, if you only need a second order filter you can use +`ma_lpf2`. The advantage of this is that they're lighter weight and a bit more efficient. -If an even filter order is specified, a series of second order filters will be processed in a chain. If an odd filter order is specified, a first order filter -will be applied, followed by a series of second order filters in a chain. +If an even filter order is specified, a series of second order filters will be processed in a +chain. If an odd filter order is specified, a first order filter will be applied, followed by a +series of second order filters in a chain. -7.3. High-Pass Filtering ------------------------- +11.3. High-Pass Filtering +------------------------- High-pass filtering is achieved with the following APIs: +---------+-------------------------------------------+ @@ -1095,12 +3169,12 @@ High-pass filtering is achieved with the following APIs: | ma_hpf | High order high-pass filter (Butterworth) | +---------+-------------------------------------------+ -High-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_hpf1`, `ma_hpf2` and `ma_hpf`. See example code for low-pass filters -for example usage. +High-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_hpf1`, +`ma_hpf2` and `ma_hpf`. See example code for low-pass filters for example usage. -7.4. Band-Pass Filtering ------------------------- +11.4. Band-Pass Filtering +------------------------- Band-pass filtering is achieved with the following APIs: +---------+-------------------------------+ @@ -1110,13 +3184,14 @@ Band-pass filtering is achieved with the following APIs: | ma_bpf | High order band-pass filter | +---------+-------------------------------+ -Band-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_bpf2` and `ma_hpf`. See example code for low-pass filters for example -usage. Note that the order for band-pass filters must be an even number which means there is no first order band-pass filter, unlike low-pass and high-pass -filters. +Band-pass filters work exactly the same as low-pass filters, only the APIs are called `ma_bpf2` and +`ma_hpf`. See example code for low-pass filters for example usage. Note that the order for +band-pass filters must be an even number which means there is no first order band-pass filter, +unlike low-pass and high-pass filters. -7.5. Notch Filtering --------------------- +11.5. Notch Filtering +--------------------- Notch filtering is achieved with the following APIs: +-----------+------------------------------------------+ @@ -1126,7 +3201,7 @@ Notch filtering is achieved with the following APIs: +-----------+------------------------------------------+ -7.6. Peaking EQ Filtering +11.6. Peaking EQ Filtering ------------------------- Peaking filtering is achieved with the following APIs: @@ -1137,8 +3212,8 @@ Peaking filtering is achieved with the following APIs: +----------+------------------------------------------+ -7.7. Low Shelf Filtering ------------------------- +11.7. Low Shelf Filtering +------------------------- Low shelf filtering is achieved with the following APIs: +-------------+------------------------------------------+ @@ -1147,11 +3222,12 @@ Low shelf filtering is achieved with the following APIs: | ma_loshelf2 | Second order low shelf filter | +-------------+------------------------------------------+ -Where a high-pass filter is used to eliminate lower frequencies, a low shelf filter can be used to just turn them down rather than eliminate them entirely. +Where a high-pass filter is used to eliminate lower frequencies, a low shelf filter can be used to +just turn them down rather than eliminate them entirely. -7.8. High Shelf Filtering -------------------------- +11.8. High Shelf Filtering +-------------------------- High shelf filtering is achieved with the following APIs: +-------------+------------------------------------------+ @@ -1160,18 +3236,20 @@ High shelf filtering is achieved with the following APIs: | ma_hishelf2 | Second order high shelf filter | +-------------+------------------------------------------+ -The high shelf filter has the same API as the low shelf filter, only you would use `ma_hishelf` instead of `ma_loshelf`. Where a low shelf filter is used to -adjust the volume of low frequencies, the high shelf filter does the same thing for high frequencies. +The high shelf filter has the same API as the low shelf filter, only you would use `ma_hishelf` +instead of `ma_loshelf`. Where a low shelf filter is used to adjust the volume of low frequencies, +the high shelf filter does the same thing for high frequencies. -8. Waveform and Noise Generation -================================ +12. Waveform and Noise Generation +================================= -8.1. Waveforms --------------- -miniaudio supports generation of sine, square, triangle and sawtooth waveforms. This is achieved with the `ma_waveform` API. Example: +12.1. Waveforms +--------------- +miniaudio supports generation of sine, square, triangle and sawtooth waveforms. This is achieved +with the `ma_waveform` API. Example: ```c ma_waveform_config config = ma_waveform_config_init( @@ -1193,11 +3271,12 @@ miniaudio supports generation of sine, square, triangle and sawtooth waveforms. ma_waveform_read_pcm_frames(&waveform, pOutput, frameCount); ``` -The amplitude, frequency, type, and sample rate can be changed dynamically with `ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, -`ma_waveform_set_type()`, and `ma_waveform_set_sample_rate()` respectively. +The amplitude, frequency, type, and sample rate can be changed dynamically with +`ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, `ma_waveform_set_type()`, and +`ma_waveform_set_sample_rate()` respectively. -You can invert the waveform by setting the amplitude to a negative value. You can use this to control whether or not a sawtooth has a positive or negative -ramp, for example. +You can invert the waveform by setting the amplitude to a negative value. You can use this to +control whether or not a sawtooth has a positive or negative ramp, for example. Below are the supported waveform types: @@ -1212,8 +3291,8 @@ Below are the supported waveform types: -8.2. Noise ----------- +12.2. Noise +----------- miniaudio supports generation of white, pink and Brownian noise via the `ma_noise` API. Example: ```c @@ -1235,13 +3314,16 @@ miniaudio supports generation of white, pink and Brownian noise via the `ma_nois ma_noise_read_pcm_frames(&noise, pOutput, frameCount); ``` -The noise API uses simple LCG random number generation. It supports a custom seed which is useful for things like automated testing requiring reproducibility. -Setting the seed to zero will default to `MA_DEFAULT_LCG_SEED`. +The noise API uses simple LCG random number generation. It supports a custom seed which is useful +for things like automated testing requiring reproducibility. Setting the seed to zero will default +to `MA_DEFAULT_LCG_SEED`. -The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, `ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. +The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, +`ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. -By default, the noise API will use different values for different channels. So, for example, the left side in a stereo stream will be different to the right -side. To instead have each channel use the same random value, set the `duplicateChannels` member of the noise config to true, like so: +By default, the noise API will use different values for different channels. So, for example, the +left side in a stereo stream will be different to the right side. To instead have each channel use +the same random value, set the `duplicateChannels` member of the noise config to true, like so: ```c config.duplicateChannels = MA_TRUE; @@ -1259,10 +3341,11 @@ Below are the supported noise types. -9. Audio Buffers -================ -miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can read from memory that's managed by the application, but -can also handle the memory management for you internally. Memory management is flexible and should support most use cases. +13. Audio Buffers +================= +miniaudio supports reading from a buffer of raw audio data via the `ma_audio_buffer` API. This can +read from memory that's managed by the application, but can also handle the memory management for +you internally. Memory management is flexible and should support most use cases. Audio buffers are initialised using the standard configuration system used everywhere in miniaudio: @@ -1285,11 +3368,14 @@ Audio buffers are initialised using the standard configuration system used every ma_audio_buffer_uninit(&buffer); ``` -In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an application can do self-managed memory allocation. If you -would rather make a copy of the data, use `ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. +In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an +application can do self-managed memory allocation. If you would rather make a copy of the data, use +`ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. -Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the raw audio data in a contiguous block of memory. That is, -the raw audio data will be located immediately after the `ma_audio_buffer` structure. To do this, use `ma_audio_buffer_alloc_and_init()`: +Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the +raw audio data in a contiguous block of memory. That is, the raw audio data will be located +immediately after the `ma_audio_buffer` structure. To do this, use +`ma_audio_buffer_alloc_and_init()`: ```c ma_audio_buffer_config config = ma_audio_buffer_config_init( @@ -1310,13 +3396,18 @@ the raw audio data will be located immediately after the `ma_audio_buffer` struc ma_audio_buffer_uninit_and_free(&buffer); ``` -If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it with `ma_audio_buffer_uninit_and_free()`. In the example above, -the memory pointed to by `pExistingData` will be copied into the buffer, which is contrary to the behavior of `ma_audio_buffer_init()`. +If you initialize the buffer with `ma_audio_buffer_alloc_and_init()` you should uninitialize it +with `ma_audio_buffer_uninit_and_free()`. In the example above, the memory pointed to by +`pExistingData` will be copied into the buffer, which is contrary to the behavior of +`ma_audio_buffer_init()`. -An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the cursor moves forward. The last parameter (`loop`) can be -used to determine if the buffer should loop. The return value is the number of frames actually read. If this is less than the number of frames requested it -means the end has been reached. This should never happen if the `loop` parameter is set to true. If you want to manually loop back to the start, you can do so -with with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an audio buffer. +An audio buffer has a playback cursor just like a decoder. As you read frames from the buffer, the +cursor moves forward. The last parameter (`loop`) can be used to determine if the buffer should +loop. The return value is the number of frames actually read. If this is less than the number of +frames requested it means the end has been reached. This should never happen if the `loop` +parameter is set to true. If you want to manually loop back to the start, you can do so with with +`ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an example for reading data from an +audio buffer. ```c ma_uint64 framesRead = ma_audio_buffer_read_pcm_frames(pAudioBuffer, pFramesOut, desiredFrameCount, isLooping); @@ -1325,8 +3416,8 @@ with with `ma_audio_buffer_seek_to_pcm_frame(pAudioBuffer, 0)`. Below is an exam } ``` -Sometimes you may want to avoid the cost of data movement between the internal buffer and the output buffer. Instead you can use memory mapping to retrieve a -pointer to a segment of data: +Sometimes you may want to avoid the cost of data movement between the internal buffer and the +output buffer. Instead you can use memory mapping to retrieve a pointer to a segment of data: ```c void* pMappedFrames; @@ -1342,23 +3433,30 @@ pointer to a segment of data: } ``` -When you use memory mapping, the read cursor is increment by the frame count passed in to `ma_audio_buffer_unmap()`. If you decide not to process every frame -you can pass in a value smaller than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is that it does not handle looping -for you. You can determine if the buffer is at the end for the purpose of looping with `ma_audio_buffer_at_end()` or by inspecting the return value of -`ma_audio_buffer_unmap()` and checking if it equals `MA_AT_END`. You should not treat `MA_AT_END` as an error when returned by `ma_audio_buffer_unmap()`. +When you use memory mapping, the read cursor is increment by the frame count passed in to +`ma_audio_buffer_unmap()`. If you decide not to process every frame you can pass in a value smaller +than the value returned by `ma_audio_buffer_map()`. The disadvantage to using memory mapping is +that it does not handle looping for you. You can determine if the buffer is at the end for the +purpose of looping with `ma_audio_buffer_at_end()` or by inspecting the return value of +`ma_audio_buffer_unmap()` and checking if it equals `MA_AT_END`. You should not treat `MA_AT_END` +as an error when returned by `ma_audio_buffer_unmap()`. -10. Ring Buffers +14. Ring Buffers ================ -miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates -on bytes, whereas the `ma_pcm_rb` operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around `ma_rb`. +miniaudio supports lock free (single producer, single consumer) ring buffers which are exposed via +the `ma_rb` and `ma_pcm_rb` APIs. The `ma_rb` API operates on bytes, whereas the `ma_pcm_rb` +operates on PCM frames. They are otherwise identical as `ma_pcm_rb` is just a wrapper around +`ma_rb`. -Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved streams. The caller can also allocate their own backing memory for -the ring buffer to use internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for you. +Unlike most other APIs in miniaudio, ring buffers support both interleaved and deinterleaved +streams. The caller can also allocate their own backing memory for the ring buffer to use +internally for added flexibility. Otherwise the ring buffer will manage it's internal memory for +you. -The examples below use the PCM frame variant of the ring buffer since that's most likely the one you will want to use. To initialize a ring buffer, do -something like the following: +The examples below use the PCM frame variant of the ring buffer since that's most likely the one +you will want to use. To initialize a ring buffer, do something like the following: ```c ma_pcm_rb rb; @@ -1368,39 +3466,53 @@ something like the following: } ``` -The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because it's the PCM varient of the ring buffer API. For the regular -ring buffer that operates on bytes you would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes instead of frames. The -fourth parameter is an optional pre-allocated buffer and the fifth parameter is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation -routines. Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. +The `ma_pcm_rb_init()` function takes the sample format and channel count as parameters because +it's the PCM varient of the ring buffer API. For the regular ring buffer that operates on bytes you +would call `ma_rb_init()` which leaves these out and just takes the size of the buffer in bytes +instead of frames. The fourth parameter is an optional pre-allocated buffer and the fifth parameter +is a pointer to a `ma_allocation_callbacks` structure for custom memory allocation routines. +Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` being used. -Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is offset from each other based on the stride. To manage your -sub-buffers you can use `ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and `ma_pcm_rb_get_subbuffer_ptr()`. +Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is +offset from each other based on the stride. To manage your sub-buffers you can use +`ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and +`ma_pcm_rb_get_subbuffer_ptr()`. -Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section of the ring buffer. You specify the number of frames you -need, and on output it will set to what was actually acquired. If the read or write pointer is positioned such that the number of frames requested will require -a loop, it will be clamped to the end of the buffer. Therefore, the number of frames you're given may be less than the number you requested. +Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section +of the ring buffer. You specify the number of frames you need, and on output it will set to what +was actually acquired. If the read or write pointer is positioned such that the number of frames +requested will require a loop, it will be clamped to the end of the buffer. Therefore, the number +of frames you're given may be less than the number you requested. -After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the buffer and then "commit" it with `ma_pcm_rb_commit_read()` or -`ma_pcm_rb_commit_write()`. This is where the read/write pointers are updated. When you commit you need to pass in the buffer that was returned by the earlier -call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and -`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was originally requested. +After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the +buffer and then "commit" it with `ma_pcm_rb_commit_read()` or `ma_pcm_rb_commit_write()`. This is +where the read/write pointers are updated. When you commit you need to pass in the buffer that was +returned by the earlier call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is +only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and +`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was +originally requested. -If you want to correct for drift between the write pointer and the read pointer you can use a combination of `ma_pcm_rb_pointer_distance()`, -`ma_pcm_rb_seek_read()` and `ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only move the read pointer forward via -the consumer thread, and the write pointer forward by the producer thread. If there is too much space between the pointers, move the read pointer forward. If +If you want to correct for drift between the write pointer and the read pointer you can use a +combination of `ma_pcm_rb_pointer_distance()`, `ma_pcm_rb_seek_read()` and +`ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only +move the read pointer forward via the consumer thread, and the write pointer forward by the +producer thread. If there is too much space between the pointers, move the read pointer forward. If there is too little space between the pointers, move the write pointer forward. -You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` API. This is exactly the same, only you will use the `ma_rb` -functions instead of `ma_pcm_rb` and instead of frame counts you will pass around byte counts. +You can use a ring buffer at the byte level instead of the PCM frame level by using the `ma_rb` +API. This is exactly the same, only you will use the `ma_rb` functions instead of `ma_pcm_rb` and +instead of frame counts you will pass around byte counts. -The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most significant bit being used to encode a loop flag and the internally -managed buffers always being aligned to MA_SIMD_ALIGNMENT. +The maximum size of the buffer in bytes is `0x7FFFFFFF-(MA_SIMD_ALIGNMENT-1)` due to the most +significant bit being used to encode a loop flag and the internally managed buffers always being +aligned to `MA_SIMD_ALIGNMENT`. -Note that the ring buffer is only thread safe when used by a single consumer thread and single producer thread. +Note that the ring buffer is only thread safe when used by a single consumer thread and single +producer thread. -11. Backends +15. Backends ============ The following backends are supported by miniaudio. @@ -1426,28 +3538,36 @@ The following backends are supported by miniaudio. Some backends have some nuance details you may want to be aware of. -11.1. WASAPI +15.1. WASAPI ------------ -- Low-latency shared mode will be disabled when using an application-defined sample rate which is different to the device's native sample rate. To work around - this, set `wasapi.noAutoConvertSRC` to true in the device config. This is due to IAudioClient3_InitializeSharedAudioStream() failing when the - `AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM` flag is specified. Setting wasapi.noAutoConvertSRC will result in miniaudio's internal resampler being used instead - which will in turn enable the use of low-latency shared mode. +- Low-latency shared mode will be disabled when using an application-defined sample rate which is + different to the device's native sample rate. To work around this, set `wasapi.noAutoConvertSRC` + to true in the device config. This is due to IAudioClient3_InitializeSharedAudioStream() failing + when the `AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM` flag is specified. Setting wasapi.noAutoConvertSRC + will result in miniaudio's internal resampler being used instead which will in turn enable the + use of low-latency shared mode. -11.2. PulseAudio +15.2. PulseAudio ---------------- - If you experience bad glitching/noise on Arch Linux, consider this fix from the Arch wiki: - https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling. Alternatively, consider using a different backend such as ALSA. + https://wiki.archlinux.org/index.php/PulseAudio/Troubleshooting#Glitches,_skips_or_crackling. + Alternatively, consider using a different backend such as ALSA. -11.3. Android +15.3. Android ------------- -- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: `` -- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a limitation with OpenSL|ES. -- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration API (devices are enumerated through Java). You can however - perform your own device enumeration through Java and then set the ID in the ma_device_id structure (ma_device_id.aaudio) and pass it to ma_device_init(). -- The backend API will perform resampling where possible. The reason for this as opposed to using miniaudio's built-in resampler is to take advantage of any - potential device-specific optimizations the driver may implement. +- To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: + `` +- With OpenSL|ES, only a single ma_context can be active at any given time. This is due to a + limitation with OpenSL|ES. +- With AAudio, only default devices are enumerated. This is due to AAudio not having an enumeration + API (devices are enumerated through Java). You can however perform your own device enumeration + through Java and then set the ID in the ma_device_id structure (ma_device_id.aaudio) and pass it + to ma_device_init(). +- The backend API will perform resampling where possible. The reason for this as opposed to using + miniaudio's built-in resampler is to take advantage of any potential device-specific + optimizations the driver may implement. -11.4. UWP +15.4. UWP --------- - UWP only supports default playback and capture devices. - UWP requires the Microphone capability to be enabled in the application's manifest (Package.appxmanifest): @@ -1461,29 +3581,51 @@ Some backends have some nuance details you may want to be aware of. ``` -11.5. Web Audio / Emscripten +15.5. Web Audio / Emscripten ---------------------------- - You cannot use `-std=c*` compiler flags, nor `-ansi`. This only applies to the Emscripten build. -- The first time a context is initialized it will create a global object called "miniaudio" whose primary purpose is to act as a factory for device objects. -- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as they've been deprecated. -- Google has implemented a policy in their browsers that prevent automatic media output without first receiving some kind of user input. The following web page - has additional details: https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device may fail if you try to start playback - without first handling some kind of user input. +- The first time a context is initialized it will create a global object called "miniaudio" whose + primary purpose is to act as a factory for device objects. +- Currently the Web Audio backend uses ScriptProcessorNode's, but this may need to change later as + they've been deprecated. +- Google has implemented a policy in their browsers that prevent automatic media output without + first receiving some kind of user input. The following web page has additional details: + https://developers.google.com/web/updates/2017/09/autoplay-policy-changes. Starting the device + may fail if you try to start playback without first handling some kind of user input. -12. Miscellaneous Notes +16. Optimization Tips +===================== + +16.1. High Level API +-------------------- +- If a sound does not require doppler or pitch shifting, consider disabling pitching by + initializing the sound with the `MA_SOUND_FLAG_NO_PITCH` flag. +- If a sound does not require spatialization, disable it by initialzing the sound with the + `MA_SOUND_FLAG_NO_SPATIALIZATION` flag. It can be renabled again post-initialization with + `ma_sound_set_spatialization_enabled()`. + + + +17. Miscellaneous Notes ======================= -- Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for WASAPI and Core Audio, however other backends such as - PulseAudio may naturally support it, though not all have been tested. -- The contents of the output buffer passed into the data callback will always be pre-initialized to silence unless the `noPreZeroedOutputBuffer` config variable - in `ma_device_config` is set to true, in which case it'll be undefined which will require you to write something to the entire buffer. -- By default miniaudio will automatically clip samples. This only applies when the playback sample format is configured as `ma_format_f32`. If you are doing - clipping yourself, you can disable this overhead by setting `noClip` to true in the device config. -- The sndio backend is currently only enabled on OpenBSD builds. -- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can use it. +- Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for + WASAPI and Core Audio, however other backends such as PulseAudio may naturally support it, though + not all have been tested. +- The contents of the output buffer passed into the data callback will always be pre-initialized to + silence unless the `noPreSilencedOutputBuffer` config variable in `ma_device_config` is set to + true, in which case it'll be undefined which will require you to write something to the entire + buffer. +- By default miniaudio will automatically clip samples. This only applies when the playback sample + format is configured as `ma_format_f32`. If you are doing clipping yourself, you can disable this + overhead by setting `noClip` to true in the device config. - Note that GCC and Clang requires `-msse2`, `-mavx2`, etc. for SIMD optimizations. -- When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This is due to 64-bit file APIs not being available. +- The sndio backend is currently only enabled on OpenBSD builds. +- The audio(4) backend is supported on OpenBSD, but you may need to disable sndiod before you can + use it. +- When compiling with VC6 and earlier, decoding is restricted to files less than 2GB in size. This + is due to 64-bit file APIs not being available. */ #ifndef miniaudio_h @@ -1497,8 +3639,8 @@ extern "C" { #define MA_XSTRINGIFY(x) MA_STRINGIFY(x) #define MA_VERSION_MAJOR 0 -#define MA_VERSION_MINOR 10 -#define MA_VERSION_REVISION 42 +#define MA_VERSION_MINOR 11 +#define MA_VERSION_REVISION 9 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) @@ -1513,66 +3655,55 @@ extern "C" { #pragma GCC diagnostic ignored "-Wc11-extensions" /* anonymous unions are a C11 extension */ #endif #endif + -/* Platform/backend detection. */ -#ifdef _WIN32 - #define MA_WIN32 - #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PC_APP || WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) - #define MA_WIN32_UWP - #else - #define MA_WIN32_DESKTOP - #endif + +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) + #define MA_SIZEOF_PTR 8 #else - #define MA_POSIX - #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ - - #ifdef __unix__ - #define MA_UNIX - #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) - #define MA_BSD - #endif - #endif - #ifdef __linux__ - #define MA_LINUX - #endif - #ifdef __APPLE__ - #define MA_APPLE - #endif - #ifdef __ANDROID__ - #define MA_ANDROID - #endif - #ifdef __EMSCRIPTEN__ - #define MA_EMSCRIPTEN - #endif + #define MA_SIZEOF_PTR 4 #endif #include /* For size_t. */ /* Sized types. */ -typedef signed char ma_int8; -typedef unsigned char ma_uint8; -typedef signed short ma_int16; -typedef unsigned short ma_uint16; -typedef signed int ma_int32; -typedef unsigned int ma_uint32; -#if defined(_MSC_VER) - typedef signed __int64 ma_int64; - typedef unsigned __int64 ma_uint64; +#if defined(MA_USE_STDINT) + #include + typedef int8_t ma_int8; + typedef uint8_t ma_uint8; + typedef int16_t ma_int16; + typedef uint16_t ma_uint16; + typedef int32_t ma_int32; + typedef uint32_t ma_uint32; + typedef int64_t ma_int64; + typedef uint64_t ma_uint64; #else - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wlong-long" - #if defined(__clang__) - #pragma GCC diagnostic ignored "-Wc++11-long-long" + typedef signed char ma_int8; + typedef unsigned char ma_uint8; + typedef signed short ma_int16; + typedef unsigned short ma_uint16; + typedef signed int ma_int32; + typedef unsigned int ma_uint32; + #if defined(_MSC_VER) && !defined(__clang__) + typedef signed __int64 ma_int64; + typedef unsigned __int64 ma_uint64; + #else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long ma_int64; + typedef unsigned long long ma_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop #endif #endif - typedef signed long long ma_int64; - typedef unsigned long long ma_uint64; - #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) - #pragma GCC diagnostic pop - #endif -#endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#endif /* MA_USE_STDINT */ + +#if MA_SIZEOF_PTR == 8 typedef ma_uint64 ma_uintptr; #else typedef ma_uint32 ma_uintptr; @@ -1603,6 +3734,58 @@ typedef ma_uint16 wchar_t; #endif +/* Platform/backend detection. */ +#ifdef _WIN32 + #define MA_WIN32 + #if defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_PC_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PC_APP) || (defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)) + #define MA_WIN32_UWP + #elif defined(WINAPI_FAMILY) && (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES) + #define MA_WIN32_GDK + #else + #define MA_WIN32_DESKTOP + #endif +#else + #define MA_POSIX + + /* + Use the MA_NO_PTHREAD_IN_HEADER option at your own risk. This is intentionally undocumented. + You can use this to avoid including pthread.h in the header section. The downside is that it + results in some fixed sized structures being declared for the various types that are used in + miniaudio. The risk here is that these types might be too small for a given platform. This + risk is yours to take and no support will be offered if you enable this option. + */ + #ifndef MA_NO_PTHREAD_IN_HEADER + #include /* Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. */ + typedef pthread_t ma_pthread_t; + typedef pthread_mutex_t ma_pthread_mutex_t; + typedef pthread_cond_t ma_pthread_cond_t; + #else + typedef ma_uintptr ma_pthread_t; + typedef union ma_pthread_mutex_t { char __data[40]; ma_uint64 __alignment; } ma_pthread_mutex_t; + typedef union ma_pthread_cond_t { char __data[48]; ma_uint64 __alignment; } ma_pthread_cond_t; + #endif + + #ifdef __unix__ + #define MA_UNIX + #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define MA_BSD + #endif + #endif + #ifdef __linux__ + #define MA_LINUX + #endif + #ifdef __APPLE__ + #define MA_APPLE + #endif + #ifdef __ANDROID__ + #define MA_ANDROID + #endif + #ifdef __EMSCRIPTEN__ + #define MA_EMSCRIPTEN + #endif +#endif + + #ifdef _MSC_VER #define MA_INLINE __forceinline #elif defined(__GNUC__) @@ -1614,9 +3797,15 @@ typedef ma_uint16 wchar_t; I am using "__inline__" only when we're compiling in strict ANSI mode. */ #if defined(__STRICT_ANSI__) - #define MA_INLINE __inline__ __attribute__((always_inline)) + #define MA_GNUC_INLINE_HINT __inline__ #else - #define MA_INLINE inline __attribute__((always_inline)) + #define MA_GNUC_INLINE_HINT inline + #endif + + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define MA_INLINE MA_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define MA_INLINE MA_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define MA_INLINE __inline @@ -1654,201 +3843,219 @@ typedef ma_uint16 wchar_t; #endif #endif -/* SIMD alignment in bytes. Currently set to 64 bytes in preparation for future AVX-512 optimizations. */ -#define MA_SIMD_ALIGNMENT 64 +/* SIMD alignment in bytes. Currently set to 32 bytes in preparation for future AVX optimizations. */ +#define MA_SIMD_ALIGNMENT 32 /* Logging Levels ============== Log levels are only used to give logging callbacks some context as to the severity of a log message -so they can do filtering. All log levels will be posted to registered logging callbacks, except for -MA_LOG_LEVEL_DEBUG which will only get processed if MA_DEBUG_OUTPUT is enabled. +so they can do filtering. All log levels will be posted to registered logging callbacks. If you +don't want to output a certain log level you can discriminate against the log level in the callback. MA_LOG_LEVEL_DEBUG - Used for debugging. These log messages are only posted when `MA_DEBUG_OUTPUT` is enabled. + Used for debugging. Useful for debug and test builds, but should be disabled in release builds. MA_LOG_LEVEL_INFO - Informational logging. Useful for debugging. This will also enable warning and error logs. This - will never be called from within the data callback. + Informational logging. Useful for debugging. This will never be called from within the data + callback. MA_LOG_LEVEL_WARNING - Warnings. You should enable this in you development builds and action them when encounted. This - will also enable error logs. These logs usually indicate a potential problem or - misconfiguration, but still allow you to keep running. This will never be called from within - the data callback. + Warnings. You should enable this in you development builds and action them when encounted. These + logs usually indicate a potential problem or misconfiguration, but still allow you to keep + running. This will never be called from within the data callback. MA_LOG_LEVEL_ERROR Error logging. This will be fired when an operation fails and is subsequently aborted. This can be fired from within the data callback, in which case the device will be stopped. You should always have this log level enabled. */ -#define MA_LOG_LEVEL_DEBUG 4 -#define MA_LOG_LEVEL_INFO 3 -#define MA_LOG_LEVEL_WARNING 2 -#define MA_LOG_LEVEL_ERROR 1 - -/* Deprecated. */ -#define MA_LOG_LEVEL_VERBOSE MA_LOG_LEVEL_DEBUG - -/* Deprecated. */ -#ifndef MA_LOG_LEVEL -#define MA_LOG_LEVEL MA_LOG_LEVEL_ERROR -#endif +typedef enum +{ + MA_LOG_LEVEL_DEBUG = 4, + MA_LOG_LEVEL_INFO = 3, + MA_LOG_LEVEL_WARNING = 2, + MA_LOG_LEVEL_ERROR = 1 +} ma_log_level; /* -An annotation for variables which must be used atomically. This doesn't actually do anything - it's -just used as a way for humans to identify variables that should be used atomically. +Variables needing to be accessed atomically should be declared with this macro for two reasons: + + 1) It allows people who read the code to identify a variable as such; and + 2) It forces alignment on platforms where it's required or optimal. + +Note that for x86/64, alignment is not strictly necessary, but does have some performance +implications. Where supported by the compiler, alignment will be used, but otherwise if the CPU +architecture does not require it, it will simply leave it unaligned. This is the case with old +versions of Visual Studio, which I've confirmed with at least VC6. */ -#define MA_ATOMIC +#if defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) + #include + #define MA_ATOMIC(alignment, type) alignas(alignment) type +#else + #if defined(__GNUC__) + /* GCC-style compilers. */ + #define MA_ATOMIC(alignment, type) type __attribute__((aligned(alignment))) + #elif defined(_MSC_VER) && _MSC_VER > 1200 /* 1200 = VC6. Alignment not supported, but not necessary because x86 is the only supported target. */ + /* MSVC. */ + #define MA_ATOMIC(alignment, type) __declspec(align(alignment)) type + #else + /* Other compilers. */ + #define MA_ATOMIC(alignment, type) type + #endif +#endif typedef struct ma_context ma_context; typedef struct ma_device ma_device; typedef ma_uint8 ma_channel; -#define MA_CHANNEL_NONE 0 -#define MA_CHANNEL_MONO 1 -#define MA_CHANNEL_FRONT_LEFT 2 -#define MA_CHANNEL_FRONT_RIGHT 3 -#define MA_CHANNEL_FRONT_CENTER 4 -#define MA_CHANNEL_LFE 5 -#define MA_CHANNEL_BACK_LEFT 6 -#define MA_CHANNEL_BACK_RIGHT 7 -#define MA_CHANNEL_FRONT_LEFT_CENTER 8 -#define MA_CHANNEL_FRONT_RIGHT_CENTER 9 -#define MA_CHANNEL_BACK_CENTER 10 -#define MA_CHANNEL_SIDE_LEFT 11 -#define MA_CHANNEL_SIDE_RIGHT 12 -#define MA_CHANNEL_TOP_CENTER 13 -#define MA_CHANNEL_TOP_FRONT_LEFT 14 -#define MA_CHANNEL_TOP_FRONT_CENTER 15 -#define MA_CHANNEL_TOP_FRONT_RIGHT 16 -#define MA_CHANNEL_TOP_BACK_LEFT 17 -#define MA_CHANNEL_TOP_BACK_CENTER 18 -#define MA_CHANNEL_TOP_BACK_RIGHT 19 -#define MA_CHANNEL_AUX_0 20 -#define MA_CHANNEL_AUX_1 21 -#define MA_CHANNEL_AUX_2 22 -#define MA_CHANNEL_AUX_3 23 -#define MA_CHANNEL_AUX_4 24 -#define MA_CHANNEL_AUX_5 25 -#define MA_CHANNEL_AUX_6 26 -#define MA_CHANNEL_AUX_7 27 -#define MA_CHANNEL_AUX_8 28 -#define MA_CHANNEL_AUX_9 29 -#define MA_CHANNEL_AUX_10 30 -#define MA_CHANNEL_AUX_11 31 -#define MA_CHANNEL_AUX_12 32 -#define MA_CHANNEL_AUX_13 33 -#define MA_CHANNEL_AUX_14 34 -#define MA_CHANNEL_AUX_15 35 -#define MA_CHANNEL_AUX_16 36 -#define MA_CHANNEL_AUX_17 37 -#define MA_CHANNEL_AUX_18 38 -#define MA_CHANNEL_AUX_19 39 -#define MA_CHANNEL_AUX_20 40 -#define MA_CHANNEL_AUX_21 41 -#define MA_CHANNEL_AUX_22 42 -#define MA_CHANNEL_AUX_23 43 -#define MA_CHANNEL_AUX_24 44 -#define MA_CHANNEL_AUX_25 45 -#define MA_CHANNEL_AUX_26 46 -#define MA_CHANNEL_AUX_27 47 -#define MA_CHANNEL_AUX_28 48 -#define MA_CHANNEL_AUX_29 49 -#define MA_CHANNEL_AUX_30 50 -#define MA_CHANNEL_AUX_31 51 -#define MA_CHANNEL_LEFT MA_CHANNEL_FRONT_LEFT -#define MA_CHANNEL_RIGHT MA_CHANNEL_FRONT_RIGHT -#define MA_CHANNEL_POSITION_COUNT (MA_CHANNEL_AUX_31 + 1) +typedef enum +{ + MA_CHANNEL_NONE = 0, + MA_CHANNEL_MONO = 1, + MA_CHANNEL_FRONT_LEFT = 2, + MA_CHANNEL_FRONT_RIGHT = 3, + MA_CHANNEL_FRONT_CENTER = 4, + MA_CHANNEL_LFE = 5, + MA_CHANNEL_BACK_LEFT = 6, + MA_CHANNEL_BACK_RIGHT = 7, + MA_CHANNEL_FRONT_LEFT_CENTER = 8, + MA_CHANNEL_FRONT_RIGHT_CENTER = 9, + MA_CHANNEL_BACK_CENTER = 10, + MA_CHANNEL_SIDE_LEFT = 11, + MA_CHANNEL_SIDE_RIGHT = 12, + MA_CHANNEL_TOP_CENTER = 13, + MA_CHANNEL_TOP_FRONT_LEFT = 14, + MA_CHANNEL_TOP_FRONT_CENTER = 15, + MA_CHANNEL_TOP_FRONT_RIGHT = 16, + MA_CHANNEL_TOP_BACK_LEFT = 17, + MA_CHANNEL_TOP_BACK_CENTER = 18, + MA_CHANNEL_TOP_BACK_RIGHT = 19, + MA_CHANNEL_AUX_0 = 20, + MA_CHANNEL_AUX_1 = 21, + MA_CHANNEL_AUX_2 = 22, + MA_CHANNEL_AUX_3 = 23, + MA_CHANNEL_AUX_4 = 24, + MA_CHANNEL_AUX_5 = 25, + MA_CHANNEL_AUX_6 = 26, + MA_CHANNEL_AUX_7 = 27, + MA_CHANNEL_AUX_8 = 28, + MA_CHANNEL_AUX_9 = 29, + MA_CHANNEL_AUX_10 = 30, + MA_CHANNEL_AUX_11 = 31, + MA_CHANNEL_AUX_12 = 32, + MA_CHANNEL_AUX_13 = 33, + MA_CHANNEL_AUX_14 = 34, + MA_CHANNEL_AUX_15 = 35, + MA_CHANNEL_AUX_16 = 36, + MA_CHANNEL_AUX_17 = 37, + MA_CHANNEL_AUX_18 = 38, + MA_CHANNEL_AUX_19 = 39, + MA_CHANNEL_AUX_20 = 40, + MA_CHANNEL_AUX_21 = 41, + MA_CHANNEL_AUX_22 = 42, + MA_CHANNEL_AUX_23 = 43, + MA_CHANNEL_AUX_24 = 44, + MA_CHANNEL_AUX_25 = 45, + MA_CHANNEL_AUX_26 = 46, + MA_CHANNEL_AUX_27 = 47, + MA_CHANNEL_AUX_28 = 48, + MA_CHANNEL_AUX_29 = 49, + MA_CHANNEL_AUX_30 = 50, + MA_CHANNEL_AUX_31 = 51, + MA_CHANNEL_LEFT = MA_CHANNEL_FRONT_LEFT, + MA_CHANNEL_RIGHT = MA_CHANNEL_FRONT_RIGHT, + MA_CHANNEL_POSITION_COUNT = (MA_CHANNEL_AUX_31 + 1) +} _ma_channel_position; /* Do not use `_ma_channel_position` directly. Use `ma_channel` instead. */ + +typedef enum +{ + MA_SUCCESS = 0, + MA_ERROR = -1, /* A generic error. */ + MA_INVALID_ARGS = -2, + MA_INVALID_OPERATION = -3, + MA_OUT_OF_MEMORY = -4, + MA_OUT_OF_RANGE = -5, + MA_ACCESS_DENIED = -6, + MA_DOES_NOT_EXIST = -7, + MA_ALREADY_EXISTS = -8, + MA_TOO_MANY_OPEN_FILES = -9, + MA_INVALID_FILE = -10, + MA_TOO_BIG = -11, + MA_PATH_TOO_LONG = -12, + MA_NAME_TOO_LONG = -13, + MA_NOT_DIRECTORY = -14, + MA_IS_DIRECTORY = -15, + MA_DIRECTORY_NOT_EMPTY = -16, + MA_AT_END = -17, + MA_NO_SPACE = -18, + MA_BUSY = -19, + MA_IO_ERROR = -20, + MA_INTERRUPT = -21, + MA_UNAVAILABLE = -22, + MA_ALREADY_IN_USE = -23, + MA_BAD_ADDRESS = -24, + MA_BAD_SEEK = -25, + MA_BAD_PIPE = -26, + MA_DEADLOCK = -27, + MA_TOO_MANY_LINKS = -28, + MA_NOT_IMPLEMENTED = -29, + MA_NO_MESSAGE = -30, + MA_BAD_MESSAGE = -31, + MA_NO_DATA_AVAILABLE = -32, + MA_INVALID_DATA = -33, + MA_TIMEOUT = -34, + MA_NO_NETWORK = -35, + MA_NOT_UNIQUE = -36, + MA_NOT_SOCKET = -37, + MA_NO_ADDRESS = -38, + MA_BAD_PROTOCOL = -39, + MA_PROTOCOL_UNAVAILABLE = -40, + MA_PROTOCOL_NOT_SUPPORTED = -41, + MA_PROTOCOL_FAMILY_NOT_SUPPORTED = -42, + MA_ADDRESS_FAMILY_NOT_SUPPORTED = -43, + MA_SOCKET_NOT_SUPPORTED = -44, + MA_CONNECTION_RESET = -45, + MA_ALREADY_CONNECTED = -46, + MA_NOT_CONNECTED = -47, + MA_CONNECTION_REFUSED = -48, + MA_NO_HOST = -49, + MA_IN_PROGRESS = -50, + MA_CANCELLED = -51, + MA_MEMORY_ALREADY_MAPPED = -52, + + /* General miniaudio-specific errors. */ + MA_FORMAT_NOT_SUPPORTED = -100, + MA_DEVICE_TYPE_NOT_SUPPORTED = -101, + MA_SHARE_MODE_NOT_SUPPORTED = -102, + MA_NO_BACKEND = -103, + MA_NO_DEVICE = -104, + MA_API_NOT_FOUND = -105, + MA_INVALID_DEVICE_CONFIG = -106, + MA_LOOP = -107, + + /* State errors. */ + MA_DEVICE_NOT_INITIALIZED = -200, + MA_DEVICE_ALREADY_INITIALIZED = -201, + MA_DEVICE_NOT_STARTED = -202, + MA_DEVICE_NOT_STOPPED = -203, + + /* Operation errors. */ + MA_FAILED_TO_INIT_BACKEND = -300, + MA_FAILED_TO_OPEN_BACKEND_DEVICE = -301, + MA_FAILED_TO_START_BACKEND_DEVICE = -302, + MA_FAILED_TO_STOP_BACKEND_DEVICE = -303 +} ma_result; -typedef int ma_result; -#define MA_SUCCESS 0 -#define MA_ERROR -1 /* A generic error. */ -#define MA_INVALID_ARGS -2 -#define MA_INVALID_OPERATION -3 -#define MA_OUT_OF_MEMORY -4 -#define MA_OUT_OF_RANGE -5 -#define MA_ACCESS_DENIED -6 -#define MA_DOES_NOT_EXIST -7 -#define MA_ALREADY_EXISTS -8 -#define MA_TOO_MANY_OPEN_FILES -9 -#define MA_INVALID_FILE -10 -#define MA_TOO_BIG -11 -#define MA_PATH_TOO_LONG -12 -#define MA_NAME_TOO_LONG -13 -#define MA_NOT_DIRECTORY -14 -#define MA_IS_DIRECTORY -15 -#define MA_DIRECTORY_NOT_EMPTY -16 -#define MA_AT_END -17 -#define MA_NO_SPACE -18 -#define MA_BUSY -19 -#define MA_IO_ERROR -20 -#define MA_INTERRUPT -21 -#define MA_UNAVAILABLE -22 -#define MA_ALREADY_IN_USE -23 -#define MA_BAD_ADDRESS -24 -#define MA_BAD_SEEK -25 -#define MA_BAD_PIPE -26 -#define MA_DEADLOCK -27 -#define MA_TOO_MANY_LINKS -28 -#define MA_NOT_IMPLEMENTED -29 -#define MA_NO_MESSAGE -30 -#define MA_BAD_MESSAGE -31 -#define MA_NO_DATA_AVAILABLE -32 -#define MA_INVALID_DATA -33 -#define MA_TIMEOUT -34 -#define MA_NO_NETWORK -35 -#define MA_NOT_UNIQUE -36 -#define MA_NOT_SOCKET -37 -#define MA_NO_ADDRESS -38 -#define MA_BAD_PROTOCOL -39 -#define MA_PROTOCOL_UNAVAILABLE -40 -#define MA_PROTOCOL_NOT_SUPPORTED -41 -#define MA_PROTOCOL_FAMILY_NOT_SUPPORTED -42 -#define MA_ADDRESS_FAMILY_NOT_SUPPORTED -43 -#define MA_SOCKET_NOT_SUPPORTED -44 -#define MA_CONNECTION_RESET -45 -#define MA_ALREADY_CONNECTED -46 -#define MA_NOT_CONNECTED -47 -#define MA_CONNECTION_REFUSED -48 -#define MA_NO_HOST -49 -#define MA_IN_PROGRESS -50 -#define MA_CANCELLED -51 -#define MA_MEMORY_ALREADY_MAPPED -52 - -/* General miniaudio-specific errors. */ -#define MA_FORMAT_NOT_SUPPORTED -100 -#define MA_DEVICE_TYPE_NOT_SUPPORTED -101 -#define MA_SHARE_MODE_NOT_SUPPORTED -102 -#define MA_NO_BACKEND -103 -#define MA_NO_DEVICE -104 -#define MA_API_NOT_FOUND -105 -#define MA_INVALID_DEVICE_CONFIG -106 -#define MA_LOOP -107 - -/* State errors. */ -#define MA_DEVICE_NOT_INITIALIZED -200 -#define MA_DEVICE_ALREADY_INITIALIZED -201 -#define MA_DEVICE_NOT_STARTED -202 -#define MA_DEVICE_NOT_STOPPED -203 - -/* Operation errors. */ -#define MA_FAILED_TO_INIT_BACKEND -300 -#define MA_FAILED_TO_OPEN_BACKEND_DEVICE -301 -#define MA_FAILED_TO_START_BACKEND_DEVICE -302 -#define MA_FAILED_TO_STOP_BACKEND_DEVICE -303 - - -#define MA_MIN_CHANNELS 1 -#ifndef MA_MAX_CHANNELS -#define MA_MAX_CHANNELS 32 +#define MA_MIN_CHANNELS 1 +#ifndef MA_MAX_CHANNELS +#define MA_MAX_CHANNELS 254 #endif - #ifndef MA_MAX_FILTER_ORDER -#define MA_MAX_FILTER_ORDER 8 +#define MA_MAX_FILTER_ORDER 8 #endif typedef enum @@ -1911,17 +4118,12 @@ typedef enum ma_standard_sample_rate_count = 14 /* Need to maintain the count manually. Make sure this is updated if items are added to enum. */ } ma_standard_sample_rate; -/* These are deprecated. Use ma_standard_sample_rate_min and ma_standard_sample_rate_max. */ -#define MA_MIN_SAMPLE_RATE (ma_uint32)ma_standard_sample_rate_min -#define MA_MAX_SAMPLE_RATE (ma_uint32)ma_standard_sample_rate_max - typedef enum { ma_channel_mix_mode_rectangular = 0, /* Simple averaging based on the plane(s) the channel is sitting on. */ ma_channel_mix_mode_simple, /* Drop excess channels; zeroed out extra channels. */ ma_channel_mix_mode_custom_weights, /* Use custom weights specified in ma_channel_router_config. */ - ma_channel_mix_mode_planar_blend = ma_channel_mix_mode_rectangular, ma_channel_mix_mode_default = ma_channel_mix_mode_rectangular } ma_channel_mix_mode; @@ -1959,6 +4161,9 @@ typedef struct } ma_lcg; +/* Spinlocks are 32-bit for compatibility reasons. */ +typedef ma_uint32 ma_spinlock; + #ifndef MA_NO_THREADING /* Thread priorities should be ordered such that the default priority of the worker thread is 0. */ typedef enum @@ -1973,21 +4178,18 @@ typedef enum ma_thread_priority_default = 0 } ma_thread_priority; -/* Spinlocks are 32-bit for compatibility reasons. */ -typedef ma_uint32 ma_spinlock; - #if defined(MA_WIN32) typedef ma_handle ma_thread; #endif #if defined(MA_POSIX) -typedef pthread_t ma_thread; +typedef ma_pthread_t ma_thread; #endif #if defined(MA_WIN32) typedef ma_handle ma_mutex; #endif #if defined(MA_POSIX) -typedef pthread_mutex_t ma_mutex; +typedef ma_pthread_mutex_t ma_mutex; #endif #if defined(MA_WIN32) @@ -1997,8 +4199,8 @@ typedef ma_handle ma_event; typedef struct { ma_uint32 value; - pthread_mutex_t lock; - pthread_cond_t cond; + ma_pthread_mutex_t lock; + ma_pthread_cond_t cond; } ma_event; #endif /* MA_POSIX */ @@ -2009,8 +4211,8 @@ typedef ma_handle ma_semaphore; typedef struct { int value; - pthread_mutex_t lock; - pthread_cond_t cond; + ma_pthread_mutex_t lock; + ma_pthread_cond_t cond; } ma_semaphore; #endif /* MA_POSIX */ #else @@ -2052,6 +4254,36 @@ Logging #define MA_MAX_LOG_CALLBACKS 4 #endif + +/* +The callback for handling log messages. + + +Parameters +---------- +pUserData (in) + The user data pointer that was passed into ma_log_register_callback(). + +logLevel (in) + The log level. This can be one of the following: + + +----------------------+ + | Log Level | + +----------------------+ + | MA_LOG_LEVEL_DEBUG | + | MA_LOG_LEVEL_INFO | + | MA_LOG_LEVEL_WARNING | + | MA_LOG_LEVEL_ERROR | + +----------------------+ + +pMessage (in) + The log message. + + +Remarks +------- +Do not modify the state of the device from inside the callback. +*/ typedef void (* ma_log_callback_proc)(void* pUserData, ma_uint32 level, const char* pMessage); typedef struct @@ -2116,12 +4348,20 @@ typedef struct ma_biquad_coefficient b2; ma_biquad_coefficient a1; ma_biquad_coefficient a2; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; - ma_biquad_coefficient r2[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + ma_biquad_coefficient* pR2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_biquad; -MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ); +MA_API ma_result ma_biquad_get_heap_size(const ma_biquad_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_biquad_init_preallocated(const ma_biquad_config* pConfig, void* pHeap, ma_biquad* pBQ); +MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad* pBQ); +MA_API void ma_biquad_uninit(ma_biquad* pBQ, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ); +MA_API ma_result ma_biquad_clear_cache(ma_biquad* pBQ); MA_API ma_result ma_biquad_process_pcm_frames(ma_biquad* pBQ, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_biquad_get_latency(const ma_biquad* pBQ); @@ -2148,11 +4388,19 @@ typedef struct ma_format format; ma_uint32 channels; ma_biquad_coefficient a; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_lpf1; -MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, ma_lpf1* pLPF); +MA_API ma_result ma_lpf1_get_heap_size(const ma_lpf1_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf1_init_preallocated(const ma_lpf1_config* pConfig, void* pHeap, ma_lpf1* pLPF); +MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf1* pLPF); +MA_API void ma_lpf1_uninit(ma_lpf1* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF); +MA_API ma_result ma_lpf1_clear_cache(ma_lpf1* pLPF); MA_API ma_result ma_lpf1_process_pcm_frames(ma_lpf1* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf1_get_latency(const ma_lpf1* pLPF); @@ -2161,8 +4409,12 @@ typedef struct ma_biquad bq; /* The second order low-pass filter is implemented as a biquad filter. */ } ma_lpf2; -MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF); +MA_API ma_result ma_lpf2_get_heap_size(const ma_lpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf2_init_preallocated(const ma_lpf2_config* pConfig, void* pHeap, ma_lpf2* pHPF); +MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf2* pLPF); +MA_API void ma_lpf2_uninit(ma_lpf2* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF); +MA_API ma_result ma_lpf2_clear_cache(ma_lpf2* pLPF); MA_API ma_result ma_lpf2_process_pcm_frames(ma_lpf2* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf2_get_latency(const ma_lpf2* pLPF); @@ -2185,12 +4437,20 @@ typedef struct ma_uint32 sampleRate; ma_uint32 lpf1Count; ma_uint32 lpf2Count; - ma_lpf1 lpf1[1]; - ma_lpf2 lpf2[MA_MAX_FILTER_ORDER/2]; + ma_lpf1* pLPF1; + ma_lpf2* pLPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_lpf; -MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF); +MA_API ma_result ma_lpf_get_heap_size(const ma_lpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_lpf_init_preallocated(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF); +MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf* pLPF); +MA_API void ma_lpf_uninit(ma_lpf* pLPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF); +MA_API ma_result ma_lpf_clear_cache(ma_lpf* pLPF); MA_API ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_lpf_get_latency(const ma_lpf* pLPF); @@ -2217,10 +4477,17 @@ typedef struct ma_format format; ma_uint32 channels; ma_biquad_coefficient a; - ma_biquad_coefficient r1[MA_MAX_CHANNELS]; + ma_biquad_coefficient* pR1; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_hpf1; -MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, ma_hpf1* pHPF); +MA_API ma_result ma_hpf1_get_heap_size(const ma_hpf1_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf1_init_preallocated(const ma_hpf1_config* pConfig, void* pHeap, ma_hpf1* pLPF); +MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf1* pHPF); +MA_API void ma_hpf1_uninit(ma_hpf1* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf1_reinit(const ma_hpf1_config* pConfig, ma_hpf1* pHPF); MA_API ma_result ma_hpf1_process_pcm_frames(ma_hpf1* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf1_get_latency(const ma_hpf1* pHPF); @@ -2230,7 +4497,10 @@ typedef struct ma_biquad bq; /* The second order high-pass filter is implemented as a biquad filter. */ } ma_hpf2; -MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF); +MA_API ma_result ma_hpf2_get_heap_size(const ma_hpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf2_init_preallocated(const ma_hpf2_config* pConfig, void* pHeap, ma_hpf2* pHPF); +MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf2* pHPF); +MA_API void ma_hpf2_uninit(ma_hpf2* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf2_reinit(const ma_hpf2_config* pConfig, ma_hpf2* pHPF); MA_API ma_result ma_hpf2_process_pcm_frames(ma_hpf2* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf2_get_latency(const ma_hpf2* pHPF); @@ -2254,11 +4524,18 @@ typedef struct ma_uint32 sampleRate; ma_uint32 hpf1Count; ma_uint32 hpf2Count; - ma_hpf1 hpf1[1]; - ma_hpf2 hpf2[MA_MAX_FILTER_ORDER/2]; + ma_hpf1* pHPF1; + ma_hpf2* pHPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_hpf; -MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, ma_hpf* pHPF); +MA_API ma_result ma_hpf_get_heap_size(const ma_hpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hpf_init_preallocated(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pLPF); +MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf* pHPF); +MA_API void ma_hpf_uninit(ma_hpf* pHPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hpf_reinit(const ma_hpf_config* pConfig, ma_hpf* pHPF); MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hpf_get_latency(const ma_hpf* pHPF); @@ -2285,7 +4562,10 @@ typedef struct ma_biquad bq; /* The second order band-pass filter is implemented as a biquad filter. */ } ma_bpf2; -MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF); +MA_API ma_result ma_bpf2_get_heap_size(const ma_bpf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_bpf2_init_preallocated(const ma_bpf2_config* pConfig, void* pHeap, ma_bpf2* pBPF); +MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf2* pBPF); +MA_API void ma_bpf2_uninit(ma_bpf2* pBPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_bpf2_reinit(const ma_bpf2_config* pConfig, ma_bpf2* pBPF); MA_API ma_result ma_bpf2_process_pcm_frames(ma_bpf2* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_bpf2_get_latency(const ma_bpf2* pBPF); @@ -2307,10 +4587,17 @@ typedef struct ma_format format; ma_uint32 channels; ma_uint32 bpf2Count; - ma_bpf2 bpf2[MA_MAX_FILTER_ORDER/2]; + ma_bpf2* pBPF2; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_bpf; -MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF); +MA_API ma_result ma_bpf_get_heap_size(const ma_bpf_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_bpf_init_preallocated(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF); +MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf* pBPF); +MA_API void ma_bpf_uninit(ma_bpf* pBPF, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_bpf_reinit(const ma_bpf_config* pConfig, ma_bpf* pBPF); MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_bpf_get_latency(const ma_bpf* pBPF); @@ -2337,7 +4624,10 @@ typedef struct ma_biquad bq; } ma_notch2; -MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFilter); +MA_API ma_result ma_notch2_get_heap_size(const ma_notch2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_notch2_init_preallocated(const ma_notch2_config* pConfig, void* pHeap, ma_notch2* pFilter); +MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch2* pFilter); +MA_API void ma_notch2_uninit(ma_notch2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_notch2_reinit(const ma_notch2_config* pConfig, ma_notch2* pFilter); MA_API ma_result ma_notch2_process_pcm_frames(ma_notch2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_notch2_get_latency(const ma_notch2* pFilter); @@ -2365,7 +4655,10 @@ typedef struct ma_biquad bq; } ma_peak2; -MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter); +MA_API ma_result ma_peak2_get_heap_size(const ma_peak2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_peak2_init_preallocated(const ma_peak2_config* pConfig, void* pHeap, ma_peak2* pFilter); +MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak2* pFilter); +MA_API void ma_peak2_uninit(ma_peak2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_peak2_reinit(const ma_peak2_config* pConfig, ma_peak2* pFilter); MA_API ma_result ma_peak2_process_pcm_frames(ma_peak2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_peak2_get_latency(const ma_peak2* pFilter); @@ -2393,7 +4686,10 @@ typedef struct ma_biquad bq; } ma_loshelf2; -MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter); +MA_API ma_result ma_loshelf2_get_heap_size(const ma_loshelf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_loshelf2_init_preallocated(const ma_loshelf2_config* pConfig, void* pHeap, ma_loshelf2* pFilter); +MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf2* pFilter); +MA_API void ma_loshelf2_uninit(ma_loshelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_loshelf2_reinit(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter); MA_API ma_result ma_loshelf2_process_pcm_frames(ma_loshelf2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_loshelf2_get_latency(const ma_loshelf2* pFilter); @@ -2421,13 +4717,316 @@ typedef struct ma_biquad bq; } ma_hishelf2; -MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter); +MA_API ma_result ma_hishelf2_get_heap_size(const ma_hishelf2_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_hishelf2_init_preallocated(const ma_hishelf2_config* pConfig, void* pHeap, ma_hishelf2* pFilter); +MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf2* pFilter); +MA_API void ma_hishelf2_uninit(ma_hishelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_hishelf2_reinit(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter); MA_API ma_result ma_hishelf2_process_pcm_frames(ma_hishelf2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); MA_API ma_uint32 ma_hishelf2_get_latency(const ma_hishelf2* pFilter); +/* +Delay +*/ +typedef struct +{ + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 delayInFrames; + ma_bool32 delayStart; /* Set to true to delay the start of the output; false otherwise. */ + float wet; /* 0..1. Default = 1. */ + float dry; /* 0..1. Default = 1. */ + float decay; /* 0..1. Default = 0 (no feedback). Feedback decay. Use this for echo. */ +} ma_delay_config; + +MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay); + + +typedef struct +{ + ma_delay_config config; + ma_uint32 cursor; /* Feedback is written to this cursor. Always equal or in front of the read cursor. */ + ma_uint32 bufferSizeInFrames; /* The maximum of config.startDelayInFrames and config.feedbackDelayInFrames. */ + float* pBuffer; +} ma_delay; + +MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay); +MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount); +MA_API void ma_delay_set_wet(ma_delay* pDelay, float value); +MA_API float ma_delay_get_wet(const ma_delay* pDelay); +MA_API void ma_delay_set_dry(ma_delay* pDelay, float value); +MA_API float ma_delay_get_dry(const ma_delay* pDelay); +MA_API void ma_delay_set_decay(ma_delay* pDelay, float value); +MA_API float ma_delay_get_decay(const ma_delay* pDelay); + + +/* Gainer for smooth volume changes. */ +typedef struct +{ + ma_uint32 channels; + ma_uint32 smoothTimeInFrames; +} ma_gainer_config; + +MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames); + + +typedef struct +{ + ma_gainer_config config; + ma_uint32 t; + float* pOldGains; + float* pNewGains; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_gainer; + +MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer); +MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer); +MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain); +MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains); + + + +/* Stereo panner. */ +typedef enum +{ + ma_pan_mode_balance = 0, /* Does not blend one side with the other. Technically just a balance. Compatible with other popular audio engines and therefore the default. */ + ma_pan_mode_pan /* A true pan. The sound from one side will "move" to the other side and blend with it. */ +} ma_pan_mode; + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_pan_mode mode; + float pan; +} ma_panner_config; + +MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels); + + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_pan_mode mode; + float pan; /* -1..1 where 0 is no pan, -1 is left side, +1 is right side. Defaults to 0. */ +} ma_panner; + +MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner); +MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API void ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode); +MA_API ma_pan_mode ma_panner_get_mode(const ma_panner* pPanner); +MA_API void ma_panner_set_pan(ma_panner* pPanner, float pan); +MA_API float ma_panner_get_pan(const ma_panner* pPanner); + + + +/* Fader. */ +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; +} ma_fader_config; + +MA_API ma_fader_config ma_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate); + +typedef struct +{ + ma_fader_config config; + float volumeBeg; /* If volumeBeg and volumeEnd is equal to 1, no fading happens (ma_fader_process_pcm_frames() will run as a passthrough). */ + float volumeEnd; + ma_uint64 lengthInFrames; /* The total length of the fade. */ + ma_uint64 cursorInFrames; /* The current time in frames. Incremented by ma_fader_process_pcm_frames(). */ +} ma_fader; + +MA_API ma_result ma_fader_init(const ma_fader_config* pConfig, ma_fader* pFader); +MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); +MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames); +MA_API float ma_fader_get_current_volume(ma_fader* pFader); + + + +/* Spatializer. */ +typedef struct +{ + float x; + float y; + float z; +} ma_vec3f; + +typedef enum +{ + ma_attenuation_model_none, /* No distance attenuation and no spatialization. */ + ma_attenuation_model_inverse, /* Equivalent to OpenAL's AL_INVERSE_DISTANCE_CLAMPED. */ + ma_attenuation_model_linear, /* Linear attenuation. Equivalent to OpenAL's AL_LINEAR_DISTANCE_CLAMPED. */ + ma_attenuation_model_exponential /* Exponential attenuation. Equivalent to OpenAL's AL_EXPONENT_DISTANCE_CLAMPED. */ +} ma_attenuation_model; + +typedef enum +{ + ma_positioning_absolute, + ma_positioning_relative +} ma_positioning; + +typedef enum +{ + ma_handedness_right, + ma_handedness_left +} ma_handedness; + + +typedef struct +{ + ma_uint32 channelsOut; + ma_channel* pChannelMapOut; + ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + float coneInnerAngleInRadians; + float coneOuterAngleInRadians; + float coneOuterGain; + float speedOfSound; + ma_vec3f worldUp; +} ma_spatializer_listener_config; + +MA_API ma_spatializer_listener_config ma_spatializer_listener_config_init(ma_uint32 channelsOut); + + +typedef struct +{ + ma_spatializer_listener_config config; + ma_vec3f position; /* The absolute position of the listener. */ + ma_vec3f direction; /* The direction the listener is facing. The world up vector is config.worldUp. */ + ma_vec3f velocity; + ma_bool32 isEnabled; + + /* Memory management. */ + ma_bool32 _ownsHeap; + void* _pHeap; +} ma_spatializer_listener; + +MA_API ma_result ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config* pConfig, void* pHeap, ma_spatializer_listener* pListener); +MA_API ma_result ma_spatializer_listener_init(const ma_spatializer_listener_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_uninit(ma_spatializer_listener* pListener, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_channel* ma_spatializer_listener_get_channel_map(ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_cone(ma_spatializer_listener* pListener, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_spatializer_listener_get_cone(const ma_spatializer_listener* pListener, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_spatializer_listener_set_position(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_position(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_direction(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_direction(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_velocity(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_velocity(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener* pListener, float speedOfSound); +MA_API float ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_world_up(ma_spatializer_listener* pListener, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_listener_get_world_up(const ma_spatializer_listener* pListener); +MA_API void ma_spatializer_listener_set_enabled(ma_spatializer_listener* pListener, ma_bool32 isEnabled); +MA_API ma_bool32 ma_spatializer_listener_is_enabled(const ma_spatializer_listener* pListener); + + +typedef struct +{ + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_channel* pChannelMapIn; + ma_attenuation_model attenuationModel; + ma_positioning positioning; + ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + float minGain; + float maxGain; + float minDistance; + float maxDistance; + float rolloff; + float coneInnerAngleInRadians; + float coneOuterAngleInRadians; + float coneOuterGain; + float dopplerFactor; /* Set to 0 to disable doppler effect. */ + float directionalAttenuationFactor; /* Set to 0 to disable directional attenuation. */ + ma_uint32 gainSmoothTimeInFrames; /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ +} ma_spatializer_config; + +MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma_uint32 channelsOut); + + +typedef struct +{ + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_channel* pChannelMapIn; + ma_attenuation_model attenuationModel; + ma_positioning positioning; + ma_handedness handedness; /* Defaults to right. Forward is -1 on the Z axis. In a left handed system, forward is +1 on the Z axis. */ + float minGain; + float maxGain; + float minDistance; + float maxDistance; + float rolloff; + float coneInnerAngleInRadians; + float coneOuterAngleInRadians; + float coneOuterGain; + float dopplerFactor; /* Set to 0 to disable doppler effect. */ + float directionalAttenuationFactor; /* Set to 0 to disable directional attenuation. */ + ma_uint32 gainSmoothTimeInFrames; /* When the gain of a channel changes during spatialization, the transition will be linearly interpolated over this number of frames. */ + ma_vec3f position; + ma_vec3f direction; + ma_vec3f velocity; /* For doppler effect. */ + float dopplerPitch; /* Will be updated by ma_spatializer_process_pcm_frames() and can be used by higher level functions to apply a pitch shift for doppler effect. */ + ma_gainer gainer; /* For smooth gain transitions. */ + float* pNewChannelGainsOut; /* An offset of _pHeap. Used by ma_spatializer_process_pcm_frames() to store new channel gains. The number of elements in this array is equal to config.channelsOut. */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_spatializer; + +MA_API ma_result ma_spatializer_get_heap_size(const ma_spatializer_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* pConfig, void* pHeap, ma_spatializer* pSpatializer); +MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer* pSpatializer); +MA_API void ma_spatializer_uninit(ma_spatializer* pSpatializer, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer); +MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning); +MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_rolloff(ma_spatializer* pSpatializer, float rolloff); +MA_API float ma_spatializer_get_rolloff(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_min_gain(ma_spatializer* pSpatializer, float minGain); +MA_API float ma_spatializer_get_min_gain(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_max_gain(ma_spatializer* pSpatializer, float maxGain); +MA_API float ma_spatializer_get_max_gain(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_min_distance(ma_spatializer* pSpatializer, float minDistance); +MA_API float ma_spatializer_get_min_distance(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_max_distance(ma_spatializer* pSpatializer, float maxDistance); +MA_API float ma_spatializer_get_max_distance(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_cone(ma_spatializer* pSpatializer, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_spatializer_get_cone(const ma_spatializer* pSpatializer, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_spatializer_set_doppler_factor(ma_spatializer* pSpatializer, float dopplerFactor); +MA_API float ma_spatializer_get_doppler_factor(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_directional_attenuation_factor(ma_spatializer* pSpatializer, float directionalAttenuationFactor); +MA_API float ma_spatializer_get_directional_attenuation_factor(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_position(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_position(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_direction(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_direction(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_set_velocity(ma_spatializer* pSpatializer, float x, float y, float z); +MA_API ma_vec3f ma_spatializer_get_velocity(const ma_spatializer* pSpatializer); +MA_API void ma_spatializer_get_relative_position_and_direction(const ma_spatializer* pSpatializer, const ma_spatializer_listener* pListener, ma_vec3f* pRelativePos, ma_vec3f* pRelativeDir); + + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -2465,75 +5064,106 @@ typedef struct ma_uint32 inTimeFrac; union { - float f32[MA_MAX_CHANNELS]; - ma_int16 s16[MA_MAX_CHANNELS]; + float* f32; + ma_int16* s16; } x0; /* The previous input frame. */ union { - float f32[MA_MAX_CHANNELS]; - ma_int16 s16[MA_MAX_CHANNELS]; + float* f32; + ma_int16* s16; } x1; /* The next input frame. */ ma_lpf lpf; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_linear_resampler; -MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, ma_linear_resampler* pResampler); -MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler_config* pConfig, void* pHeap, ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_linear_resampler* pResampler); +MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut); -MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount); -MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount); MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler); MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler); +MA_API ma_result ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); +MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); +MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler); + + +typedef struct ma_resampler_config ma_resampler_config; + +typedef void ma_resampling_backend; +typedef struct +{ + ma_result (* onGetHeapSize )(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes); + ma_result (* onInit )(void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend); + void (* onUninit )(void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); + ma_result (* onProcess )(void* pUserData, ma_resampling_backend* pBackend, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); + ma_result (* onSetRate )(void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); /* Optional. Rate changes will be disabled. */ + ma_uint64 (* onGetInputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ + ma_uint64 (* onGetOutputLatency )(void* pUserData, const ma_resampling_backend* pBackend); /* Optional. Latency will be reported as 0. */ + ma_result (* onGetRequiredInputFrameCount )(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); /* Optional. Latency mitigation will be disabled. */ + ma_result (* onGetExpectedOutputFrameCount)(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); /* Optional. Latency mitigation will be disabled. */ + ma_result (* onReset )(void* pUserData, ma_resampling_backend* pBackend); +} ma_resampling_backend_vtable; typedef enum { - ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ - ma_resample_algorithm_speex + ma_resample_algorithm_linear = 0, /* Fastest, lowest quality. Optional low-pass filtering. Default. */ + ma_resample_algorithm_custom, } ma_resample_algorithm; -typedef struct +struct ma_resampler_config { ma_format format; /* Must be either ma_format_f32 or ma_format_s16. */ ma_uint32 channels; ma_uint32 sampleRateIn; ma_uint32 sampleRateOut; - ma_resample_algorithm algorithm; + ma_resample_algorithm algorithm; /* When set to ma_resample_algorithm_custom, pBackendVTable will be used. */ + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; struct { ma_uint32 lpfOrder; - double lpfNyquistFactor; } linear; - struct - { - int quality; /* 0 to 10. Defaults to 3. */ - } speex; -} ma_resampler_config; +}; MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_resample_algorithm algorithm); typedef struct { - ma_resampler_config config; + ma_resampling_backend* pBackend; + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRateIn; + ma_uint32 sampleRateOut; union { ma_linear_resampler linear; - struct - { - void* pSpeexResamplerState; /* SpeexResamplerState* */ - } speex; - } state; + } state; /* State for stock resamplers so we can avoid a malloc. For stock resamplers, pBackend will point here. */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_resampler; +MA_API ma_result ma_resampler_get_heap_size(const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_resampler_init_preallocated(const ma_resampler_config* pConfig, void* pHeap, ma_resampler* pResampler); + /* Initializes a new resampler object from a config. */ -MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pResampler); +MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_resampler* pResampler); /* Uninitializes a resampler. */ -MA_API void ma_resampler_uninit(ma_resampler* pResampler); +MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks); /* Converts the given input data. @@ -2572,23 +5202,6 @@ The ration is in/out. */ MA_API ma_result ma_resampler_set_rate_ratio(ma_resampler* pResampler, float ratio); - -/* -Calculates the number of whole input frames that would need to be read from the client in order to output the specified -number of output frames. - -The returned value does not include cached input frames. It only returns the number of extra frames that would need to be -read from the input buffer in order to output the specified number of output frames. -*/ -MA_API ma_uint64 ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount); - -/* -Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of -input frames. -*/ -MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount); - - /* Retrieves the latency introduced by the resampler in input frames. */ @@ -2599,6 +5212,25 @@ Retrieves the latency introduced by the resampler in output frames. */ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler); +/* +Calculates the number of whole input frames that would need to be read from the client in order to output the specified +number of output frames. + +The returned value does not include cached input frames. It only returns the number of extra frames that would need to be +read from the input buffer in order to output the specified number of output frames. +*/ +MA_API ma_result ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); + +/* +Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of +input frames. +*/ +MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); + +/* +Resets the resampler's timer and clears it's internal cache. +*/ +MA_API ma_result ma_resampler_reset(ma_resampler* pResampler); /************************************************************************************************************************************************************** @@ -2606,15 +5238,33 @@ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) Channel Conversion **************************************************************************************************************************************************************/ +typedef enum +{ + ma_channel_conversion_path_unknown, + ma_channel_conversion_path_passthrough, + ma_channel_conversion_path_mono_out, /* Converting to mono. */ + ma_channel_conversion_path_mono_in, /* Converting from mono. */ + ma_channel_conversion_path_shuffle, /* Simple shuffle. Will use this when all channels are present in both input and output channel maps, but just in a different order. */ + ma_channel_conversion_path_weights /* Blended based on weights. */ +} ma_channel_conversion_path; + +typedef enum +{ + ma_mono_expansion_mode_duplicate = 0, /* The default. */ + ma_mono_expansion_mode_average, /* Average the mono channel across all channels. */ + ma_mono_expansion_mode_stereo_only, /* Duplicate to the left and right channels only and ignore the others. */ + ma_mono_expansion_mode_default = ma_mono_expansion_mode_duplicate +} ma_mono_expansion_mode; + typedef struct { ma_format format; ma_uint32 channelsIn; ma_uint32 channelsOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; + const ma_channel* pChannelMapIn; + const ma_channel* pChannelMapOut; ma_channel_mix_mode mixingMode; - float weights[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + float** ppWeights; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ } ma_channel_converter_config; MA_API ma_channel_converter_config ma_channel_converter_config_init(ma_format format, ma_uint32 channelsIn, const ma_channel* pChannelMapIn, ma_uint32 channelsOut, const ma_channel* pChannelMapOut, ma_channel_mix_mode mixingMode); @@ -2624,24 +5274,29 @@ typedef struct ma_format format; ma_uint32 channelsIn; ma_uint32 channelsOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; ma_channel_mix_mode mixingMode; + ma_channel_conversion_path conversionPath; + ma_channel* pChannelMapIn; + ma_channel* pChannelMapOut; + ma_uint8* pShuffleTable; /* Indexed by output channel index. */ union { - float f32[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; - ma_int32 s16[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; - } weights; - ma_bool8 isPassthrough; - ma_bool8 isSimpleShuffle; - ma_bool8 isSimpleMonoExpansion; - ma_bool8 isStereoToMono; - ma_uint8 shuffleTable[MA_MAX_CHANNELS]; + float** f32; + ma_int32** s16; + } weights; /* [in][out] */ + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_channel_converter; -MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, ma_channel_converter* pConverter); -MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter); +MA_API ma_result ma_channel_converter_get_heap_size(const ma_channel_converter_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_channel_converter_init_preallocated(const ma_channel_converter_config* pConfig, void* pHeap, ma_channel_converter* pConverter); +MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_converter* pConverter); +MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_channel_converter_process_pcm_frames(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_channel_converter_get_input_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_channel_converter_get_output_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); /************************************************************************************************************************************************************** @@ -2657,33 +5312,39 @@ typedef struct ma_uint32 channelsOut; ma_uint32 sampleRateIn; ma_uint32 sampleRateOut; - ma_channel channelMapIn[MA_MAX_CHANNELS]; - ma_channel channelMapOut[MA_MAX_CHANNELS]; + ma_channel* pChannelMapIn; + ma_channel* pChannelMapOut; ma_dither_mode ditherMode; ma_channel_mix_mode channelMixMode; - float channelWeights[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; /* [in][out]. Only used when channelMixMode is set to ma_channel_mix_mode_custom_weights. */ - struct - { - ma_resample_algorithm algorithm; - ma_bool32 allowDynamicSampleRate; - struct - { - ma_uint32 lpfOrder; - double lpfNyquistFactor; - } linear; - struct - { - int quality; - } speex; - } resampling; + float** ppChannelWeights; /* [in][out]. Only used when mixingMode is set to ma_channel_mix_mode_custom_weights. */ + ma_bool32 allowDynamicSampleRate; + ma_resampler_config resampling; } ma_data_converter_config; MA_API ma_data_converter_config ma_data_converter_config_init_default(void); MA_API ma_data_converter_config ma_data_converter_config_init(ma_format formatIn, ma_format formatOut, ma_uint32 channelsIn, ma_uint32 channelsOut, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); + +typedef enum +{ + ma_data_converter_execution_path_passthrough, /* No conversion. */ + ma_data_converter_execution_path_format_only, /* Only format conversion. */ + ma_data_converter_execution_path_channels_only, /* Only channel conversion. */ + ma_data_converter_execution_path_resample_only, /* Only resampling. */ + ma_data_converter_execution_path_resample_first, /* All conversions, but resample as the first step. */ + ma_data_converter_execution_path_channels_first /* All conversions, but channels as the first step. */ +} ma_data_converter_execution_path; + typedef struct { - ma_data_converter_config config; + ma_format formatIn; + ma_format formatOut; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_uint32 sampleRateIn; + ma_uint32 sampleRateOut; + ma_dither_mode ditherMode; + ma_data_converter_execution_path executionPath; /* The execution path the data converter will follow when processing. */ ma_channel_converter channelConverter; ma_resampler resampler; ma_bool8 hasPreFormatConversion; @@ -2691,17 +5352,26 @@ typedef struct ma_bool8 hasChannelConverter; ma_bool8 hasResampler; ma_bool8 isPassthrough; + + /* Memory management. */ + ma_bool8 _ownsHeap; + void* _pHeap; } ma_data_converter; -MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, ma_data_converter* pConverter); -MA_API void ma_data_converter_uninit(ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_get_heap_size(const ma_data_converter_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_data_converter_init_preallocated(const ma_data_converter_config* pConfig, void* pHeap, ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_converter* pConverter); +MA_API void ma_data_converter_uninit(ma_data_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks); MA_API ma_result ma_data_converter_process_pcm_frames(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); MA_API ma_result ma_data_converter_set_rate(ma_data_converter* pConverter, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, float ratioInOut); -MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount); -MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount); MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter); MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* pConverter); +MA_API ma_result ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount); +MA_API ma_result ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount); +MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_data_converter_get_output_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_data_converter_reset(ma_data_converter* pConverter); /************************************************************************************************************************************************************ @@ -2753,9 +5423,6 @@ This is used in the shuffle table to indicate that the channel index is undefine */ #define MA_CHANNEL_INDEX_NULL 255 -/* Retrieves the channel position of the specified channel based on miniaudio's default channel map. */ -MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_uint32 channelIndex); - /* Retrieves the channel position of the specified channel in the given channel map. @@ -2768,14 +5435,14 @@ Initializes a blank channel map. When a blank channel map is specified anywhere it indicates that the native channel map should be used. */ -MA_API void ma_channel_map_init_blank(ma_uint32 channels, ma_channel* pChannelMap); +MA_API void ma_channel_map_init_blank(ma_channel* pChannelMap, ma_uint32 channels); /* Helper for retrieving a standard channel map. -The output channel map buffer must have a capacity of at least `channels`. +The output channel map buffer must have a capacity of at least `channelMapCap`. */ -MA_API void ma_get_standard_channel_map(ma_standard_channel_map standardChannelMap, ma_uint32 channels, ma_channel* pChannelMap); +MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannelMap, ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channels); /* Copies a channel map. @@ -2789,7 +5456,7 @@ Copies a channel map if one is specified, otherwise copies the default channel m The output buffer must have a capacity of at least `channels`. If not NULL, the input channel map must also have a capacity of at least `channels`. */ -MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels); +MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, size_t channelMapCapOut, const ma_channel* pIn, ma_uint32 channels); /* @@ -2804,7 +5471,7 @@ Invalid channel maps: The channel map buffer must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pChannelMap); +MA_API ma_bool32 ma_channel_map_is_valid(const ma_channel* pChannelMap, ma_uint32 channels); /* Helper for comparing two channel maps for equality. @@ -2813,14 +5480,14 @@ This assumes the channel count is the same between the two. Both channels map buffers must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pChannelMapA, const ma_channel* pChannelMapB); +MA_API ma_bool32 ma_channel_map_is_equal(const ma_channel* pChannelMapA, const ma_channel* pChannelMapB, ma_uint32 channels); /* Helper for determining if a channel map is blank (all channels set to MA_CHANNEL_NONE). The channel map buffer must have a capacity of at least `channels`. */ -MA_API ma_bool32 ma_channel_map_blank(ma_uint32 channels, const ma_channel* pChannelMap); +MA_API ma_bool32 ma_channel_map_is_blank(const ma_channel* pChannelMap, ma_uint32 channels); /* Helper for determining whether or not a channel is present in the given channel map. @@ -2860,10 +5527,10 @@ typedef struct ma_uint32 subbufferSizeInBytes; ma_uint32 subbufferCount; ma_uint32 subbufferStrideInBytes; - MA_ATOMIC ma_uint32 encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ - MA_ATOMIC ma_uint32 encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ - ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ - ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ + MA_ATOMIC(4, ma_uint32) encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ + MA_ATOMIC(4, ma_uint32) encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ + ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ + ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ ma_allocation_callbacks allocationCallbacks; } ma_rb; @@ -2872,9 +5539,9 @@ MA_API ma_result ma_rb_init(size_t bufferSizeInBytes, void* pOptionalPreallocate MA_API void ma_rb_uninit(ma_rb* pRB); MA_API void ma_rb_reset(ma_rb* pRB); MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); -MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut); +MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes); MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut); -MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut); +MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes); MA_API ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes); MA_API ma_result ma_rb_seek_write(ma_rb* pRB, size_t offsetInBytes); MA_API ma_int32 ma_rb_pointer_distance(ma_rb* pRB); /* Returns the distance between the write pointer and the read pointer. Should never be negative for a correct program. Will return the number of bytes that can be read before the read pointer hits the write pointer. */ @@ -2898,9 +5565,9 @@ MA_API ma_result ma_pcm_rb_init(ma_format format, ma_uint32 channels, ma_uint32 MA_API void ma_pcm_rb_uninit(ma_pcm_rb* pRB); MA_API void ma_pcm_rb_reset(ma_pcm_rb* pRB); MA_API ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); -MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut); +MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames); MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut); -MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut); +MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames); MA_API ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); MA_API ma_result ma_pcm_rb_seek_write(ma_pcm_rb* pRB, ma_uint32 offsetInFrames); MA_API ma_int32 ma_pcm_rb_pointer_distance(ma_pcm_rb* pRB); /* Return value is in frames. */ @@ -2942,17 +5609,22 @@ Retrieves a human readable description of the given result code. MA_API const char* ma_result_description(ma_result result); /* -malloc(). Calls MA_MALLOC(). +malloc() */ MA_API void* ma_malloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); /* -realloc(). Calls MA_REALLOC(). +calloc() +*/ +MA_API void* ma_calloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); + +/* +realloc() */ MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllocationCallbacks); /* -free(). Calls MA_FREE(). +free() */ MA_API void ma_free(void* p, const ma_allocation_callbacks* pAllocationCallbacks); @@ -2994,6 +5666,413 @@ MA_API const char* ma_log_level_to_string(ma_uint32 logLevel); + +/************************************************************************************************************************************************************ + +Synchronization + +************************************************************************************************************************************************************/ +/* +Locks a spinlock. +*/ +MA_API ma_result ma_spinlock_lock(volatile ma_spinlock* pSpinlock); + +/* +Locks a spinlock, but does not yield() when looping. +*/ +MA_API ma_result ma_spinlock_lock_noyield(volatile ma_spinlock* pSpinlock); + +/* +Unlocks a spinlock. +*/ +MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock); + + +#ifndef MA_NO_THREADING + +/* +Creates a mutex. + +A mutex must be created from a valid context. A mutex is initially unlocked. +*/ +MA_API ma_result ma_mutex_init(ma_mutex* pMutex); + +/* +Deletes a mutex. +*/ +MA_API void ma_mutex_uninit(ma_mutex* pMutex); + +/* +Locks a mutex with an infinite timeout. +*/ +MA_API void ma_mutex_lock(ma_mutex* pMutex); + +/* +Unlocks a mutex. +*/ +MA_API void ma_mutex_unlock(ma_mutex* pMutex); + + +/* +Initializes an auto-reset event. +*/ +MA_API ma_result ma_event_init(ma_event* pEvent); + +/* +Uninitializes an auto-reset event. +*/ +MA_API void ma_event_uninit(ma_event* pEvent); + +/* +Waits for the specified auto-reset event to become signalled. +*/ +MA_API ma_result ma_event_wait(ma_event* pEvent); + +/* +Signals the specified auto-reset event. +*/ +MA_API ma_result ma_event_signal(ma_event* pEvent); +#endif /* MA_NO_THREADING */ + + +/* +Fence +===== +This locks while the counter is larger than 0. Counter can be incremented and decremented by any +thread, but care needs to be taken when waiting. It is possible for one thread to acquire the +fence just as another thread returns from ma_fence_wait(). + +The idea behind a fence is to allow you to wait for a group of operations to complete. When an +operation starts, the counter is incremented which locks the fence. When the operation completes, +the fence will be released which decrements the counter. ma_fence_wait() will block until the +counter hits zero. + +If threading is disabled, ma_fence_wait() will spin on the counter. +*/ +typedef struct +{ +#ifndef MA_NO_THREADING + ma_event e; +#endif + ma_uint32 counter; +} ma_fence; + +MA_API ma_result ma_fence_init(ma_fence* pFence); +MA_API void ma_fence_uninit(ma_fence* pFence); +MA_API ma_result ma_fence_acquire(ma_fence* pFence); /* Increment counter. */ +MA_API ma_result ma_fence_release(ma_fence* pFence); /* Decrement counter. */ +MA_API ma_result ma_fence_wait(ma_fence* pFence); /* Wait for counter to reach 0. */ + + + +/* +Notification callback for asynchronous operations. +*/ +typedef void ma_async_notification; + +typedef struct +{ + void (* onSignal)(ma_async_notification* pNotification); +} ma_async_notification_callbacks; + +MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification); + + +/* +Simple polling notification. + +This just sets a variable when the notification has been signalled which is then polled with ma_async_notification_poll_is_signalled() +*/ +typedef struct +{ + ma_async_notification_callbacks cb; + ma_bool32 signalled; +} ma_async_notification_poll; + +MA_API ma_result ma_async_notification_poll_init(ma_async_notification_poll* pNotificationPoll); +MA_API ma_bool32 ma_async_notification_poll_is_signalled(const ma_async_notification_poll* pNotificationPoll); + + +/* +Event Notification + +This uses an ma_event. If threading is disabled (MA_NO_THREADING), initialization will fail. +*/ +typedef struct +{ + ma_async_notification_callbacks cb; +#ifndef MA_NO_THREADING + ma_event e; +#endif +} ma_async_notification_event; + +MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent); +MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent); + + + + +/************************************************************************************************************************************************************ + +Job Queue + +************************************************************************************************************************************************************/ + +/* +Slot Allocator +-------------- +The idea of the slot allocator is for it to be used in conjunction with a fixed sized buffer. You use the slot allocator to allocator an index that can be used +as the insertion point for an object. + +Slots are reference counted to help mitigate the ABA problem in the lock-free queue we use for tracking jobs. + +The slot index is stored in the low 32 bits. The reference counter is stored in the high 32 bits: + + +-----------------+-----------------+ + | 32 Bits | 32 Bits | + +-----------------+-----------------+ + | Reference Count | Slot Index | + +-----------------+-----------------+ +*/ +typedef struct +{ + ma_uint32 capacity; /* The number of slots to make available. */ +} ma_slot_allocator_config; + +MA_API ma_slot_allocator_config ma_slot_allocator_config_init(ma_uint32 capacity); + + +typedef struct +{ + MA_ATOMIC(4, ma_uint32) bitfield; /* Must be used atomically because the allocation and freeing routines need to make copies of this which must never be optimized away by the compiler. */ +} ma_slot_allocator_group; + +typedef struct +{ + ma_slot_allocator_group* pGroups; /* Slots are grouped in chunks of 32. */ + ma_uint32* pSlots; /* 32 bits for reference counting for ABA mitigation. */ + ma_uint32 count; /* Allocation count. */ + ma_uint32 capacity; + + /* Memory management. */ + ma_bool32 _ownsHeap; + void* _pHeap; +} ma_slot_allocator; + +MA_API ma_result ma_slot_allocator_get_heap_size(const ma_slot_allocator_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_slot_allocator_init_preallocated(const ma_slot_allocator_config* pConfig, void* pHeap, ma_slot_allocator* pAllocator); +MA_API ma_result ma_slot_allocator_init(const ma_slot_allocator_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_slot_allocator* pAllocator); +MA_API void ma_slot_allocator_uninit(ma_slot_allocator* pAllocator, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot); +MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot); + + +typedef struct ma_job ma_job; + +/* +Callback for processing a job. Each job type will have their own processing callback which will be +called by ma_job_process(). +*/ +typedef ma_result (* ma_job_proc)(ma_job* pJob); + +/* When a job type is added here an callback needs to be added go "g_jobVTable" in the implementation section. */ +typedef enum +{ + /* Miscellaneous. */ + MA_JOB_TYPE_QUIT = 0, + MA_JOB_TYPE_CUSTOM, + + /* Resource Manager. */ + MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE, + MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE, + MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE, + MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER, + MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER, + MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM, + MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM, + MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_STREAM, + MA_JOB_TYPE_RESOURCE_MANAGER_SEEK_DATA_STREAM, + + /* Device. */ + MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE, + + /* Count. Must always be last. */ + MA_JOB_TYPE_COUNT +} ma_job_type; + +struct ma_job +{ + union + { + struct + { + ma_uint16 code; /* Job type. */ + ma_uint16 slot; /* Index into a ma_slot_allocator. */ + ma_uint32 refcount; + } breakup; + ma_uint64 allocation; + } toc; /* 8 bytes. We encode the job code into the slot allocation data to save space. */ + MA_ATOMIC(8, ma_uint64) next; /* refcount + slot for the next item. Does not include the job code. */ + ma_uint32 order; /* Execution order. Used to create a data dependency and ensure a job is executed in order. Usage is contextual depending on the job type. */ + + union + { + /* Miscellaneous. */ + struct + { + ma_job_proc proc; + ma_uintptr data0; + ma_uintptr data1; + } custom; + + /* Resource Manager */ + union + { + struct + { + /*ma_resource_manager**/ void* pResourceManager; + /*ma_resource_manager_data_buffer_node**/ void* pDataBufferNode; + char* pFilePath; + wchar_t* pFilePathW; + ma_uint32 flags; /* Resource manager data source flags that were used when initializing the data buffer. */ + ma_async_notification* pInitNotification; /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE when decoding. */ + ma_fence* pInitFence; /* Released when initialization of the decoder is complete. */ + ma_fence* pDoneFence; /* Released if initialization of the decoder fails. Passed through to PAGE_DATA_BUFFER_NODE untouched if init is successful. */ + } loadDataBufferNode; + struct + { + /*ma_resource_manager**/ void* pResourceManager; + /*ma_resource_manager_data_buffer_node**/ void* pDataBufferNode; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataBufferNode; + struct + { + /*ma_resource_manager**/ void* pResourceManager; + /*ma_resource_manager_data_buffer_node**/ void* pDataBufferNode; + /*ma_decoder**/ void* pDecoder; + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. */ + ma_fence* pDoneFence; /* Passed through from LOAD_DATA_BUFFER_NODE and released when the data buffer completes decoding or an error occurs. */ + } pageDataBufferNode; + + struct + { + /*ma_resource_manager_data_buffer**/ void* pDataBuffer; + ma_async_notification* pInitNotification; /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_async_notification* pDoneNotification; /* Signalled when the data buffer has been fully decoded. */ + ma_fence* pInitFence; /* Released when the data buffer has been initialized and the format/channels/rate can be retrieved. */ + ma_fence* pDoneFence; /* Released when the data buffer has been fully decoded. */ + ma_uint64 rangeBegInPCMFrames; + ma_uint64 rangeEndInPCMFrames; + ma_uint64 loopPointBegInPCMFrames; + ma_uint64 loopPointEndInPCMFrames; + ma_uint32 isLooping; + } loadDataBuffer; + struct + { + /*ma_resource_manager_data_buffer**/ void* pDataBuffer; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataBuffer; + + struct + { + /*ma_resource_manager_data_stream**/ void* pDataStream; + char* pFilePath; /* Allocated when the job is posted, freed by the job thread after loading. */ + wchar_t* pFilePathW; /* ^ As above ^. Only used if pFilePath is NULL. */ + ma_uint64 initialSeekPoint; + ma_async_notification* pInitNotification; /* Signalled after the first two pages have been decoded and frames can be read from the stream. */ + ma_fence* pInitFence; + } loadDataStream; + struct + { + /*ma_resource_manager_data_stream**/ void* pDataStream; + ma_async_notification* pDoneNotification; + ma_fence* pDoneFence; + } freeDataStream; + struct + { + /*ma_resource_manager_data_stream**/ void* pDataStream; + ma_uint32 pageIndex; /* The index of the page to decode into. */ + } pageDataStream; + struct + { + /*ma_resource_manager_data_stream**/ void* pDataStream; + ma_uint64 frameIndex; + } seekDataStream; + } resourceManager; + + /* Device. */ + union + { + union + { + struct + { + /*ma_device**/ void* pDevice; + /*ma_device_type*/ ma_uint32 deviceType; + } reroute; + } aaudio; + } device; + } data; +}; + +MA_API ma_job ma_job_init(ma_uint16 code); +MA_API ma_result ma_job_process(ma_job* pJob); + + +/* +When set, ma_job_queue_next() will not wait and no semaphore will be signaled in +ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if nothing is available. + +This flag should always be used for platforms that do not support multithreading. +*/ +typedef enum +{ + MA_JOB_QUEUE_FLAG_NON_BLOCKING = 0x00000001 +} ma_job_queue_flags; + +typedef struct +{ + ma_uint32 flags; + ma_uint32 capacity; /* The maximum number of jobs that can fit in the queue at a time. */ +} ma_job_queue_config; + +MA_API ma_job_queue_config ma_job_queue_config_init(ma_uint32 flags, ma_uint32 capacity); + + +typedef struct +{ + ma_uint32 flags; /* Flags passed in at initialization time. */ + ma_uint32 capacity; /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */ + MA_ATOMIC(8, ma_uint64) head; /* The first item in the list. Required for removing from the top of the list. */ + MA_ATOMIC(8, ma_uint64) tail; /* The last item in the list. Required for appending to the end of the list. */ +#ifndef MA_NO_THREADING + ma_semaphore sem; /* Only used when MA_JOB_QUEUE_FLAG_NON_BLOCKING is unset. */ +#endif + ma_slot_allocator allocator; + ma_job* pJobs; +#ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock lock; +#endif + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; +} ma_job_queue; + +MA_API ma_result ma_job_queue_get_heap_size(const ma_job_queue_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_job_queue_init_preallocated(const ma_job_queue_config* pConfig, void* pHeap, ma_job_queue* pQueue); +MA_API ma_result ma_job_queue_init(const ma_job_queue_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_job_queue* pQueue); +MA_API void ma_job_queue_uninit(ma_job_queue* pQueue, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_job_queue_post(ma_job_queue* pQueue, const ma_job* pJob); +MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob); /* Returns MA_CANCELLED if the next job is a quit job. */ + + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -3100,11 +6179,14 @@ This section contains the APIs for device playback and capture. Here is where yo #define MA_HAS_NULL #endif -#define MA_STATE_UNINITIALIZED 0 -#define MA_STATE_STOPPED 1 /* The device's default state after initialization. */ -#define MA_STATE_STARTED 2 /* The device is started and is requesting and/or delivering audio data. */ -#define MA_STATE_STARTING 3 /* Transitioning from a stopped state to started. */ -#define MA_STATE_STOPPING 4 /* Transitioning from a started state to stopped. */ +typedef enum +{ + ma_device_state_uninitialized = 0, + ma_device_state_stopped = 1, /* The device's default state after initialization. */ + ma_device_state_started = 2, /* The device is started and is requesting and/or delivering audio data. */ + ma_device_state_starting = 3, /* Transitioning from a stopped state to started. */ + ma_device_state_stopping = 4 /* Transitioning from a started state to stopped. */ +} ma_device_state; #ifdef MA_SUPPORT_WASAPI /* We need a IMMNotificationClient object for WASAPI. */ @@ -3139,6 +6221,114 @@ typedef enum #define MA_BACKEND_COUNT (ma_backend_null+1) +/* +Device job thread. This is used by backends that require asynchronous processing of certain +operations. It is not used by all backends. + +The device job thread is made up of a thread and a job queue. You can post a job to the thread with +ma_device_job_thread_post(). The thread will do the processing of the job. +*/ +typedef struct +{ + ma_bool32 noThread; /* Set this to true if you want to process jobs yourself. */ + ma_uint32 jobQueueCapacity; + ma_uint32 jobQueueFlags; +} ma_device_job_thread_config; + +MA_API ma_device_job_thread_config ma_device_job_thread_config_init(void); + +typedef struct +{ + ma_thread thread; + ma_job_queue jobQueue; + ma_bool32 _hasThread; +} ma_device_job_thread; + +MA_API ma_result ma_device_job_thread_init(const ma_device_job_thread_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_job_thread* pJobThread); +MA_API void ma_device_job_thread_uninit(ma_device_job_thread* pJobThread, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_device_job_thread_post(ma_device_job_thread* pJobThread, const ma_job* pJob); +MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_job* pJob); + + + +/* Device notification types. */ +typedef enum +{ + ma_device_notification_type_started, + ma_device_notification_type_stopped, + ma_device_notification_type_rerouted, + ma_device_notification_type_interruption_began, + ma_device_notification_type_interruption_ended +} ma_device_notification_type; + +typedef struct +{ + ma_device* pDevice; + ma_device_notification_type type; + union + { + struct + { + int _unused; + } started; + struct + { + int _unused; + } stopped; + struct + { + int _unused; + } rerouted; + struct + { + int _unused; + } interruption; + } data; +} ma_device_notification; + +/* +The notification callback for when the application should be notified of a change to the device. + +This callback is used for notifying the application of changes such as when the device has started, +stopped, rerouted or an interruption has occurred. Note that not all backends will post all +notification types. For example, some backends will perform automatic stream routing without any +kind of notification to the host program which means miniaudio will never know about it and will +never be able to fire the rerouted notification. You should keep this in mind when designing your +program. + +The stopped notification will *not* get fired when a device is rerouted. + + +Parameters +---------- +pNotification (in) + A pointer to a structure containing information about the event. Use the `pDevice` member of + this object to retrieve the relevant device. The `type` member can be used to discriminate + against each of the notification types. + + +Remarks +------- +Do not restart or uninitialize the device from the callback. + +Not all notifications will be triggered by all backends, however the started and stopped events +should be reliable for all backends. Some backends do not have a good way to detect device +stoppages due to unplugging the device which may result in the stopped callback not getting +fired. This has been observed with at least one BSD variant. + +The rerouted notification is fired *after* the reroute has occurred. The stopped notification will +*not* get fired when a device is rerouted. The following backends are known to do automatic stream +rerouting, but do not have a way to be notified of the change: + + * DirectSound + +The interruption notifications are used on mobile platforms for detecting when audio is interrupted +due to things like an incoming phone call. Currently this is only implemented on iOS. None of the +Android backends will report this notification. +*/ +typedef void (* ma_device_notification_proc)(const ma_device_notification* pNotification); + + /* The callback for processing audio data from the device. @@ -3179,9 +6369,14 @@ callback. The following APIs cannot be called from inside the callback: The proper way to stop the device is to call `ma_device_stop()` from a different thread, normally the main application thread. */ -typedef void (* ma_device_callback_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); +typedef void (* ma_device_data_proc)(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + + + /* +DEPRECATED. Use ma_device_notification_proc instead. + The callback for when the device has been stopped. This will be called when the device is stopped explicitly with `ma_device_stop()` and also called implicitly when the device is stopped through external forces @@ -3198,41 +6393,7 @@ Remarks ------- Do not restart or uninitialize the device from the callback. */ -typedef void (* ma_stop_proc)(ma_device* pDevice); - -/* -The callback for handling log messages. - - -Parameters ----------- -pContext (in) - A pointer to the context the log message originated from. - -pDevice (in) - A pointer to the device the log message originate from, if any. This can be null, in which case the message came from the context. - -logLevel (in) - The log level. This can be one of the following: - - +----------------------+ - | Log Level | - +----------------------+ - | MA_LOG_LEVEL_DEBUG | - | MA_LOG_LEVEL_INFO | - | MA_LOG_LEVEL_WARNING | - | MA_LOG_LEVEL_ERROR | - +----------------------+ - -message (in) - The log message. - - -Remarks -------- -Do not modify the state of the device from inside the callback. -*/ -typedef void (* ma_log_proc)(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message); +typedef void (* ma_stop_proc)(ma_device* pDevice); /* DEPRECATED. Use ma_device_notification_proc instead. */ typedef enum { @@ -3378,30 +6539,17 @@ typedef struct ma_backend_callbacks ma_backend_callbacks; #define MA_DATA_FORMAT_FLAG_EXCLUSIVE_MODE (1U << 1) /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ +#ifndef MA_MAX_DEVICE_NAME_LENGTH +#define MA_MAX_DEVICE_NAME_LENGTH 255 +#endif + typedef struct { /* Basic info. This is the only information guaranteed to be filled in during device enumeration. */ ma_device_id id; - char name[256]; + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* +1 for null terminator. */ ma_bool32 isDefault; - /* - Detailed info. As much of this is filled as possible with ma_context_get_device_info(). Note that you are allowed to initialize - a device with settings outside of this range, but it just means the data will be converted using miniaudio's data conversion - pipeline before sending the data to/from the device. Most programs will need to not worry about these values, but it's provided - here mainly for informational purposes or in the rare case that someone might find it useful. - - These will be set to 0 when returned by ma_context_enumerate_devices() or ma_context_get_devices(). - */ - ma_uint32 formatCount; - ma_format formats[ma_format_count]; - ma_uint32 minChannels; - ma_uint32 maxChannels; - ma_uint32 minSampleRate; - ma_uint32 maxSampleRate; - - - /* Experimental. Don't use these right now. */ ma_uint32 nativeDataFormatCount; struct { @@ -3420,29 +6568,21 @@ struct ma_device_config ma_uint32 periodSizeInMilliseconds; ma_uint32 periods; ma_performance_profile performanceProfile; - ma_bool8 noPreZeroedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ + ma_bool8 noPreSilencedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to silence. */ ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ - ma_device_callback_proc dataCallback; + ma_bool8 noDisableDenormals; /* Do not disable denormals when firing the data callback. */ + ma_bool8 noFixedSizedCallback; /* Disables strict fixed-sized data callbacks. Setting this to true will result in the period size being treated only as a hint to the backend. This is an optimization for those who don't need fixed sized callbacks. */ + ma_device_data_proc dataCallback; + ma_device_notification_proc notificationCallback; ma_stop_proc stopCallback; void* pUserData; - struct - { - ma_resample_algorithm algorithm; - struct - { - ma_uint32 lpfOrder; - } linear; - struct - { - int quality; - } speex; - } resampling; + ma_resampler_config resampling; struct { const ma_device_id* pDeviceID; ma_format format; ma_uint32 channels; - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_share_mode shareMode; } playback; @@ -3451,7 +6591,7 @@ struct ma_device_config const ma_device_id* pDeviceID; ma_format format; ma_uint32 channels; - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_share_mode shareMode; } capture; @@ -3489,6 +6629,7 @@ struct ma_device_config ma_aaudio_usage usage; ma_aaudio_content_type contentType; ma_aaudio_input_preset inputPreset; + ma_bool32 noAutoStartAfterReroute; } aaudio; }; @@ -3554,7 +6695,7 @@ callbacks defined in this structure. Once the context has been initialized you can initialize a device. Before doing so, however, the application may want to know which physical devices are available. This is where `onContextEnumerateDevices()` comes in. This is fairly simple. For each device, fire the given callback with, at a minimum, the basic information filled out in `ma_device_info`. When the callback returns `MA_FALSE`, enumeration -needs to stop and the `onContextEnumerateDevices()` function return with a success code. +needs to stop and the `onContextEnumerateDevices()` function returns with a success code. Detailed device information can be retrieved from a device ID using `onContextGetDeviceInfo()`. This takes as input the device type and ID, and on output returns detailed information about the device in `ma_device_info`. The `onContextGetDeviceInfo()` callback must handle the @@ -3569,7 +6710,7 @@ internally by miniaudio. On input, if the sample format is set to `ma_format_unknown`, the backend is free to use whatever sample format it desires, so long as it's supported by miniaudio. When the channel count is set to 0, the backend should use the device's native channel count. The same applies for -sample rate. For the channel map, the default should be used when `ma_channel_map_blank()` returns true (all channels set to +sample rate. For the channel map, the default should be used when `ma_channel_map_is_blank()` returns true (all channels set to `MA_CHANNEL_NONE`). On input, the `periodSizeInFrames` or `periodSizeInMilliseconds` option should always be set. The backend should inspect both of these variables. If `periodSizeInFrames` is set, it should take priority, otherwise it needs to be derived from the period size in milliseconds (`periodSizeInMilliseconds`) and the sample rate, keeping in mind that the sample rate may be 0, in which case the @@ -3588,14 +6729,17 @@ This allows miniaudio to then process any necessary data conversion and then pas If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceDataLoop()` callback which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. -The audio thread should run data delivery logic in a loop while `ma_device_get_state() == MA_STATE_STARTED` and no errors have been +The audio thread should run data delivery logic in a loop while `ma_device_get_state() == ma_device_state_started` and no errors have been encounted. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback. The invocation of the `onDeviceDataLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this -callback. When the device is stopped, the `ma_device_get_state() == MA_STATE_STARTED` condition will fail and the loop will be terminated +callback. When the device is stopped, the `ma_device_get_state() == ma_device_state_started` condition will fail and the loop will be terminated which will then fall through to the part that stops the device. For an example on how to implement the `onDeviceDataLoop()` callback, look at `ma_device_audio_thread__default_read_write()`. Implement the `onDeviceDataLoopWakeup()` callback if you need a mechanism to wake up the audio thread. + +If the backend supports an optimized retrieval of device information from an initialized `ma_device` object, it should implement the +`onDeviceGetInfo()` callback. This is optional, in which case it will fall back to `onContextGetDeviceInfo()` which is less efficient. */ struct ma_backend_callbacks { @@ -3611,11 +6755,11 @@ struct ma_backend_callbacks ma_result (* onDeviceWrite)(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten); ma_result (* onDeviceDataLoop)(ma_device* pDevice); ma_result (* onDeviceDataLoopWakeup)(ma_device* pDevice); + ma_result (* onDeviceGetInfo)(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo); }; struct ma_context_config { - ma_log_proc logCallback; /* Legacy logging callback. Will be removed in version 0.11. */ ma_log* pLog; ma_thread_priority threadPriority; size_t threadStackSize; @@ -3678,7 +6822,6 @@ struct ma_context ma_backend backend; /* DirectSound, ALSA, etc. */ ma_log* pLog; ma_log log; /* Only used if the log is owned by the context. The pLog member will be set to &log in this case. */ - ma_log_proc logCallback; /* Legacy callback. Will be removed in version 0.11. */ ma_thread_priority threadPriority; size_t threadStackSize; void* pUserData; @@ -3863,6 +7006,7 @@ struct ma_context ma_proc pa_stream_set_write_callback; ma_proc pa_stream_set_read_callback; ma_proc pa_stream_set_suspended_callback; + ma_proc pa_stream_set_moved_callback; ma_proc pa_stream_is_suspended; ma_proc pa_stream_flush; ma_proc pa_stream_drain; @@ -3878,6 +7022,8 @@ struct ma_context /*pa_mainloop**/ ma_ptr pMainLoop; /*pa_context**/ ma_ptr pPulseContext; + char* pApplicationName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */ + char* pServerName; /* Set when the context is initialized. Used by devices for their local pa_context objects. */ } pulse; #endif #ifdef MA_SUPPORT_JACK @@ -4004,6 +7150,7 @@ struct ma_context ma_proc AAudioStream_getFramesPerBurst; ma_proc AAudioStream_requestStart; ma_proc AAudioStream_requestStop; + ma_device_job_thread jobThread; /* For processing operations outside of the error callback, specifically device disconnections and rerouting. */ } aaudio; #endif #ifdef MA_SUPPORT_OPENSL @@ -4087,37 +7234,39 @@ struct ma_device ma_context* pContext; ma_device_type type; ma_uint32 sampleRate; - MA_ATOMIC ma_uint32 state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ - ma_device_callback_proc onData; /* Set once at initialization time and should not be changed after. */ - ma_stop_proc onStop; /* Set once at initialization time and should not be changed after. */ - void* pUserData; /* Application defined data. */ + MA_ATOMIC(4, ma_device_state) state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ + ma_device_data_proc onData; /* Set once at initialization time and should not be changed after. */ + ma_device_notification_proc onNotification; /* Set once at initialization time and should not be changed after. */ + ma_stop_proc onStop; /* DEPRECATED. Use the notification callback instead. Set once at initialization time and should not be changed after. */ + void* pUserData; /* Application defined data. */ ma_mutex startStopLock; ma_event wakeupEvent; ma_event startEvent; ma_event stopEvent; ma_thread thread; - ma_result workResult; /* This is set by the worker thread after it's finished doing a job. */ - ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ - ma_bool8 noPreZeroedOutputBuffer; + ma_result workResult; /* This is set by the worker thread after it's finished doing a job. */ + ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ + ma_bool8 noPreSilencedOutputBuffer; ma_bool8 noClip; - MA_ATOMIC float masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ - ma_duplex_rb duplexRB; /* Intermediary buffer for duplex device on asynchronous backends. */ + ma_bool8 noDisableDenormals; + ma_bool8 noFixedSizedCallback; + MA_ATOMIC(4, float) masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ + ma_duplex_rb duplexRB; /* Intermediary buffer for duplex device on asynchronous backends. */ struct { ma_resample_algorithm algorithm; + ma_resampling_backend_vtable* pBackendVTable; + void* pBackendUserData; struct { ma_uint32 lpfOrder; } linear; - struct - { - int quality; - } speex; } resampling; struct { + ma_device_id* pID; /* Set to NULL if using default ID, otherwise set to the address of "id". */ ma_device_id id; /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* Maybe temporary. Likely to be replaced with a query API. */ ma_share_mode shareMode; /* Set to whatever was passed in when the device was initialized. */ ma_format format; ma_uint32 channels; @@ -4130,11 +7279,19 @@ struct ma_device ma_uint32 internalPeriods; ma_channel_mix_mode channelMixMode; ma_data_converter converter; + void* pIntermediaryBuffer; /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */ + ma_uint32 intermediaryBufferCap; + ma_uint32 intermediaryBufferLen; /* How many valid frames are sitting in the intermediary buffer. */ + void* pInputCache; /* In external format. Can be null. */ + ma_uint64 inputCacheCap; + ma_uint64 inputCacheConsumed; + ma_uint64 inputCacheRemaining; } playback; struct { + ma_device_id* pID; /* Set to NULL if using default ID, otherwise set to the address of "id". */ ma_device_id id; /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ - char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; /* Maybe temporary. Likely to be replaced with a query API. */ ma_share_mode shareMode; /* Set to whatever was passed in when the device was initialized. */ ma_format format; ma_uint32 channels; @@ -4147,6 +7304,9 @@ struct ma_device ma_uint32 internalPeriods; ma_channel_mix_mode channelMixMode; ma_data_converter converter; + void* pIntermediaryBuffer; /* For implementing fixed sized buffer callbacks. Will be null if using variable sized callbacks. */ + ma_uint32 intermediaryBufferCap; + ma_uint32 intermediaryBufferLen; /* How many valid frames are sitting in the intermediary buffer. */ } capture; union @@ -4162,16 +7322,22 @@ struct ma_device ma_IMMNotificationClient notificationClient; /*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */ /*HANDLE*/ ma_handle hEventCapture; /* Auto reset. Initialized to unsignaled. */ - ma_uint32 actualPeriodSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ - ma_uint32 actualPeriodSizeInFramesCapture; + ma_uint32 actualBufferSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ + ma_uint32 actualBufferSizeInFramesCapture; ma_uint32 originalPeriodSizeInFrames; ma_uint32 originalPeriodSizeInMilliseconds; ma_uint32 originalPeriods; ma_performance_profile originalPerformanceProfile; ma_uint32 periodSizeInFramesPlayback; ma_uint32 periodSizeInFramesCapture; - MA_ATOMIC ma_bool32 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ - MA_ATOMIC ma_bool32 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + void* pMappedBufferCapture; + ma_uint32 mappedBufferCaptureCap; + ma_uint32 mappedBufferCaptureLen; + void* pMappedBufferPlayback; + ma_uint32 mappedBufferPlaybackCap; + ma_uint32 mappedBufferPlaybackLen; + MA_ATOMIC(4, ma_bool32) isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + MA_ATOMIC(4, ma_bool32) isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ ma_bool8 noHardwareOffloading; @@ -4228,6 +7394,8 @@ struct ma_device #ifdef MA_SUPPORT_PULSEAUDIO struct { + /*pa_mainloop**/ ma_ptr pMainLoop; + /*pa_context**/ ma_ptr pPulseContext; /*pa_stream**/ ma_ptr pStreamPlayback; /*pa_stream**/ ma_ptr pStreamCapture; } pulse; @@ -4236,8 +7404,8 @@ struct ma_device struct { /*jack_client_t**/ ma_ptr pClient; - /*jack_port_t**/ ma_ptr pPortsPlayback[MA_MAX_CHANNELS]; - /*jack_port_t**/ ma_ptr pPortsCapture[MA_MAX_CHANNELS]; + /*jack_port_t**/ ma_ptr* ppPortsPlayback; + /*jack_port_t**/ ma_ptr* ppPortsCapture; float* pIntermediaryBufferPlayback; /* Typed as a float because JACK is always floating point. */ float* pIntermediaryBufferCapture; } jack; @@ -4260,7 +7428,7 @@ struct ma_device ma_bool32 isDefaultCaptureDevice; ma_bool32 isSwitchingPlaybackDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ ma_bool32 isSwitchingCaptureDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ - void* pRouteChangeHandler; /* Only used on mobile platforms. Obj-C object for handling route changes. */ + void* pNotificationHandler; /* Only used on mobile platforms. Obj-C object for handling route changes. */ } coreaudio; #endif #ifdef MA_SUPPORT_SNDIO @@ -4291,6 +7459,10 @@ struct ma_device { /*AAudioStream**/ ma_ptr pStreamPlayback; /*AAudioStream**/ ma_ptr pStreamCapture; + ma_aaudio_usage usage; + ma_aaudio_content_type contentType; + ma_aaudio_input_preset inputPreset; + ma_bool32 noAutoStartAfterReroute; } aaudio; #endif #ifdef MA_SUPPORT_OPENSL @@ -4334,7 +7506,7 @@ struct ma_device ma_uint32 currentPeriodFramesRemainingCapture; ma_uint64 lastProcessedFramePlayback; ma_uint64 lastProcessedFrameCapture; - MA_ATOMIC ma_bool32 isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */ + MA_ATOMIC(4, ma_bool32) isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */ } null_device; #endif }; @@ -4446,6 +7618,9 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_thread_priority_default | |--------------------------------------| + threadStackSize + The desired size of the stack for the audio thread. Defaults to the operating system's default. + pUserData A pointer to application-defined data. This can be accessed from the context object directly such as `context.pUserData`. @@ -4500,6 +7675,12 @@ can then be set directly on the structure. Below are the members of the `ma_cont | ma_ios_session_category_option_allow_air_play | AVAudioSessionCategoryOptionAllowAirPlay | |---------------------------------------------------------------------------|------------------------------------------------------------------| + coreaudio.noAudioSessionActivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:true] on initialization. + + coreaudio.noAudioSessionDeactivate + iOS only. When set to true, does not perform an explicit [[AVAudioSession sharedInstace] setActive:false] on uninitialization. + jack.pClientName The name of the client to pass to `jack_client_open()`. @@ -4543,9 +7724,12 @@ ma_backend backends[] = { ma_backend_dsound }; +ma_log log; +ma_log_init(&log); +ma_log_register_callback(&log, ma_log_callback_init(my_log_callbac, pMyLogUserData)); + ma_context_config config = ma_context_config_init(); -config.logCallback = my_log_callback; -config.pUserData = pMyUserData; +config.pLog = &log; // Specify a custom log object in the config so any logs that are posted from ma_context_init() are captured. ma_context context; ma_result result = ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), &config, &context); @@ -4555,6 +7739,9 @@ if (result != MA_SUCCESS) { // Couldn't find an appropriate backend. } } + +// You could also attach a log callback post-initialization: +ma_log_register_callback(ma_context_get_log(&context), ma_log_callback_init(my_log_callback, pMyLogUserData)); ``` @@ -4606,6 +7793,8 @@ Remarks Pass the returned pointer to `ma_log_post()`, `ma_log_postv()` or `ma_log_postf()` to post a log message. +You can attach your own logging callback to the log with `ma_log_register_callback()` + Return Value ------------ @@ -4747,10 +7936,6 @@ deviceType (in) pDeviceID (in) The ID of the device being queried. -shareMode (in) - The share mode to query for device capabilities. This should be set to whatever you're intending on using when initializing the device. If you're unsure, - set this to `ma_share_mode_shared`. - pDeviceInfo (out) A pointer to the `ma_device_info` structure that will receive the device information. @@ -4776,7 +7961,7 @@ the requested share mode is unsupported. This leaves pDeviceInfo unmodified in the result of an error. */ -MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo); +MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo); /* Determines if the given context supports loopback mode. @@ -4947,7 +8132,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c A hint to miniaudio as to the performance requirements of your program. Can be either `ma_performance_profile_low_latency` (default) or `ma_performance_profile_conservative`. This mainly affects the size of default buffers and can usually be left at it's default value. - noPreZeroedOutputBuffer + noPreSilencedOutputBuffer When set to true, the contents of the output buffer passed into the data callback will be left undefined. When set to false (default), the contents of the output buffer will be cleared the zero. You can use this to avoid the overhead of zeroing out the buffer if you can guarantee that your data callback will write to every sample in the output buffer, or if you are doing your own clearing. @@ -4957,12 +8142,19 @@ then be set directly on the structure. Below are the members of the `ma_device_c contents of the output buffer are left alone after returning and it will be left up to the backend itself to decide whether or not the clip. This only applies when the playback sample format is f32. + noDisableDenormals + By default, miniaudio will disable denormals when the data callback is called. Setting this to true will prevent the disabling of denormals. + + noFixedSizedCallback + Allows miniaudio to fire the data callback with any frame count. When this is set to true, the data callback will be fired with a consistent frame + count as specified by `periodSizeInFrames` or `periodSizeInMilliseconds`. When set to false, miniaudio will fire the callback with whatever the + backend requests, which could be anything. + dataCallback The callback to fire whenever data is ready to be delivered to or from the device. - stopCallback - The callback to fire whenever the device has stopped, either explicitly via `ma_device_stop()`, or implicitly due to things like the device being - disconnected. + notificationCallback + The callback to fire when something has changed with the device, such as whether or not it has been started or stopped. pUserData The user data pointer to use with the device. You can access this directly from the device object like `device.pUserData`. @@ -4971,6 +8163,12 @@ then be set directly on the structure. Below are the members of the `ma_device_c The resampling algorithm to use when miniaudio needs to perform resampling between the rate specified by `sampleRate` and the device's native rate. The default value is `ma_resample_algorithm_linear`, and the quality can be configured with `resampling.linear.lpfOrder`. + resampling.pBackendVTable + A pointer to an optional vtable that can be used for plugging in a custom resampler. + + resampling.pBackendUserData + A pointer that will passed to callbacks in pBackendVTable. + resampling.linear.lpfOrder The linear resampler applies a low-pass filter as part of it's procesing for anti-aliasing. This setting controls the order of the filter. The higher the value, the better the quality, in general. Setting this to 0 will disable low-pass filtering altogether. The maximum value is @@ -4988,9 +8186,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c The number of channels to use for playback. When set to 0 the device's native channel count will be used. This can be retrieved after initialization from the device object directly with `device.playback.channels`. - playback.channelMap + playback.pChannelMap The channel map to use for playback. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.playback.channelMap`. + device object direct with `device.playback.pChannelMap`. When set, the buffer should contain `channels` items. playback.shareMode The preferred share mode to use for playback. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify @@ -5009,9 +8207,9 @@ then be set directly on the structure. Below are the members of the `ma_device_c The number of channels to use for capture. When set to 0 the device's native channel count will be used. This can be retrieved after initialization from the device object directly with `device.capture.channels`. - capture.channelMap + capture.pChannelMap The channel map to use for capture. When left empty, the device's native channel map will be used. This can be retrieved after initialization from the - device object direct with `device.capture.channelMap`. + device object direct with `device.capture.pChannelMap`. When set, the buffer should contain `channels` items. capture.shareMode The preferred share mode to use for capture. Can be either `ma_share_mode_shared` (default) or `ma_share_mode_exclusive`. Note that if you specify @@ -5056,6 +8254,30 @@ then be set directly on the structure. Below are the members of the `ma_device_c find the closest match between the sample rate requested in the device config and the sample rates natively supported by the hardware. When set to false, the sample rate currently set by the operating system will always be used. + opensl.streamType + OpenSL only. Explicitly sets the stream type. If left unset (`ma_opensl_stream_type_default`), the + stream type will be left unset. Think of this as the type of audio you're playing. + + opensl.recordingPreset + OpenSL only. Explicitly sets the type of recording your program will be doing. When left + unset, the recording preset will be left unchanged. + + aaudio.usage + AAudio only. Explicitly sets the nature of the audio the program will be consuming. When + left unset, the usage will be left unchanged. + + aaudio.contentType + AAudio only. Sets the content type. When left unset, the content type will be left unchanged. + + aaudio.inputPreset + AAudio only. Explicitly sets the type of recording your program will be doing. When left + unset, the input preset will be left unchanged. + + aaudio.noAutoStartAfterReroute + AAudio only. Controls whether or not the device should be automatically restarted after a + stream reroute. When set to false (default) the device will be restarted automatically; + otherwise the device will be stopped. + Once initialized, the device's config is immutable. If you need to change the config you will need to initialize a new device. @@ -5259,6 +8481,95 @@ Helper function for retrieving the log object associated with the context that o MA_API ma_log* ma_device_get_log(ma_device* pDevice); +/* +Retrieves information about the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose information is being retrieved. + +type (in) + The device type. This parameter is required for duplex devices. When retrieving device + information, you are doing so for an individual playback or capture device. + +pDeviceInfo (out) + A pointer to the `ma_device_info` that will receive the device information. + + +Return Value +------------ +MA_SUCCESS if successful; any other error code otherwise. + + +Thread Safety +------------- +Unsafe. This should be considered unsafe because it may be calling into the backend which may or +may not be safe. + + +Callback Safety +--------------- +Unsafe. You should avoid calling this in the data callback because it may call into the backend +which may or may not be safe. +*/ +MA_API ma_result ma_device_get_info(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo); + + +/* +Retrieves the name of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose information is being retrieved. + +type (in) + The device type. This parameter is required for duplex devices. When retrieving device + information, you are doing so for an individual playback or capture device. + +pName (out) + A pointer to the buffer that will receive the name. + +nameCap (in) + The capacity of the output buffer, including space for the null terminator. + +pLengthNotIncludingNullTerminator (out, optional) + A pointer to the variable that will receive the length of the name, not including the null + terminator. + + +Return Value +------------ +MA_SUCCESS if successful; any other error code otherwise. + + +Thread Safety +------------- +Unsafe. This should be considered unsafe because it may be calling into the backend which may or +may not be safe. + + +Callback Safety +--------------- +Unsafe. You should avoid calling this in the data callback because it may call into the backend +which may or may not be safe. + + +Remarks +------- +If the name does not fully fit into the output buffer, it'll be truncated. You can pass in NULL to +`pName` if you want to first get the length of the name for the purpose of memory allocation of the +output buffer. Allocating a buffer of size `MA_MAX_DEVICE_NAME_LENGTH + 1` should be enough for +most cases and will avoid the need for the inefficiency of calling this function twice. + +This is implemented in terms of `ma_device_get_info()`. +*/ +MA_API ma_result ma_device_get_name(ma_device* pDevice, ma_device_type type, char* pName, size_t nameCap, size_t* pLengthNotIncludingNullTerminator); + + /* Starts the device. For playback devices this begins playback. For capture devices it begins recording. @@ -5398,17 +8709,17 @@ Return Value ------------ The current state of the device. The return value will be one of the following: - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_UNINITIALIZED | Will only be returned if the device is in the middle of initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPED | The device is stopped. The initial state of the device after initialization. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTED | The device started and requesting and/or delivering audio data. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STARTING | The device is in the process of starting. | - +------------------------+------------------------------------------------------------------------------+ - | MA_STATE_STOPPING | The device is in the process of stopping. | - +------------------------+------------------------------------------------------------------------------+ + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_uninitialized | Will only be returned if the device is in the middle of initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopped | The device is stopped. The initial state of the device after initialization. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_started | The device started and requesting and/or delivering audio data. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_starting | The device is in the process of starting. | + +-------------------------------+------------------------------------------------------------------------------+ + | ma_device_state_stopping | The device is in the process of stopping. | + +-------------------------------+------------------------------------------------------------------------------+ Thread Safety @@ -5427,22 +8738,71 @@ Remarks The general flow of a devices state goes like this: ``` - ma_device_init() -> MA_STATE_UNINITIALIZED -> MA_STATE_STOPPED - ma_device_start() -> MA_STATE_STARTING -> MA_STATE_STARTED - ma_device_stop() -> MA_STATE_STOPPING -> MA_STATE_STOPPED + ma_device_init() -> ma_device_state_uninitialized -> ma_device_state_stopped + ma_device_start() -> ma_device_state_starting -> ma_device_state_started + ma_device_stop() -> ma_device_state_stopping -> ma_device_state_stopped ``` When the state of the device is changed with `ma_device_start()` or `ma_device_stop()` at this same time as this function is called, the value returned by this function could potentially be out of sync. If this is significant to your program you need to implement your own synchronization. */ -MA_API ma_uint32 ma_device_get_state(const ma_device* pDevice); +MA_API ma_device_state ma_device_get_state(const ma_device* pDevice); + + +/* +Performs post backend initialization routines for setting up internal data conversion. + +This should be called whenever the backend is initialized. The only time this should be called from +outside of miniaudio is if you're implementing a custom backend, and you would only do it if you +are reinitializing the backend due to rerouting or reinitializing for some reason. + + +Parameters +---------- +pDevice [in] + A pointer to the device. + +deviceType [in] + The type of the device that was just reinitialized. + +pPlaybackDescriptor [in] + The descriptor of the playback device containing the internal data format and buffer sizes. + +pPlaybackDescriptor [in] + The descriptor of the capture device containing the internal data format and buffer sizes. + + +Return Value +------------ +MA_SUCCESS if successful; any other error otherwise. + + +Thread Safety +------------- +Unsafe. This will be reinitializing internal data converters which may be in use by another thread. + + +Callback Safety +--------------- +Unsafe. This will be reinitializing internal data converters which may be in use by the callback. + + +Remarks +------- +For a duplex device, you can call this for only one side of the system. This is why the deviceType +is specified as a parameter rather than deriving it from the device. + +You do not need to call this manually unless you are doing a custom backend, in which case you need +only do it if you're manually performing rerouting or reinitialization. +*/ +MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pPlaybackDescriptor, const ma_device_descriptor* pCaptureDescriptor); /* Sets the master volume factor for the device. -The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_gain_db()` to use decibel notation, where 0 is full volume and +The volume factor must be between 0 (silence) and 1 (full volume). Use `ma_device_set_master_volume_db()` to use decibel notation, where 0 is full volume and values less than 0 decreases the volume. @@ -5452,14 +8812,14 @@ pDevice (in) A pointer to the device whose volume is being set. volume (in) - The new volume factor. Must be within the range of [0, 1]. + The new volume factor. Must be >= 0. Return Value ------------ MA_SUCCESS if the volume was set successfully. MA_INVALID_ARGS if pDevice is NULL. -MA_INVALID_ARGS if the volume factor is not within the range of [0, 1]. +MA_INVALID_ARGS if volume is negative. Thread Safety @@ -5482,8 +8842,8 @@ This does not change the operating system's volume. It only affects the volume f See Also -------- ma_device_get_master_volume() -ma_device_set_master_volume_gain_db() -ma_device_get_master_volume_gain_db() +ma_device_set_master_volume_db() +ma_device_get_master_volume_db() */ MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume); @@ -5575,7 +8935,7 @@ ma_device_get_master_volume_gain_db() ma_device_set_master_volume() ma_device_get_master_volume() */ -MA_API ma_result ma_device_set_master_gain_db(ma_device* pDevice, float gainDB); +MA_API ma_result ma_device_set_master_volume_db(ma_device* pDevice, float gainDB); /* Retrieves the master gain in decibels. @@ -5614,11 +8974,11 @@ If an error occurs, `*pGainDB` will be set to 0. See Also -------- -ma_device_set_master_volume_gain_db() +ma_device_set_master_volume_db() ma_device_set_master_volume() ma_device_get_master_volume() */ -MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB); +MA_API ma_result ma_device_get_master_volume_db(ma_device* pDevice, float* pGainDB); /* @@ -5814,68 +9174,6 @@ MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend); #endif /* MA_NO_DEVICE_IO */ -#ifndef MA_NO_THREADING - -/* -Locks a spinlock. -*/ -MA_API ma_result ma_spinlock_lock(volatile ma_spinlock* pSpinlock); - -/* -Locks a spinlock, but does not yield() when looping. -*/ -MA_API ma_result ma_spinlock_lock_noyield(volatile ma_spinlock* pSpinlock); - -/* -Unlocks a spinlock. -*/ -MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock); - - -/* -Creates a mutex. - -A mutex must be created from a valid context. A mutex is initially unlocked. -*/ -MA_API ma_result ma_mutex_init(ma_mutex* pMutex); - -/* -Deletes a mutex. -*/ -MA_API void ma_mutex_uninit(ma_mutex* pMutex); - -/* -Locks a mutex with an infinite timeout. -*/ -MA_API void ma_mutex_lock(ma_mutex* pMutex); - -/* -Unlocks a mutex. -*/ -MA_API void ma_mutex_unlock(ma_mutex* pMutex); - - -/* -Initializes an auto-reset event. -*/ -MA_API ma_result ma_event_init(ma_event* pEvent); - -/* -Uninitializes an auto-reset event. -*/ -MA_API void ma_event_uninit(ma_event* pEvent); - -/* -Waits for the specified auto-reset event to become signalled. -*/ -MA_API ma_result ma_event_wait(ma_event* pEvent); - -/* -Signals the specified auto-reset event. -*/ -MA_API ma_result ma_event_signal(ma_event* pEvent); -#endif /* MA_NO_THREADING */ - /************************************************************************************************************************************************************ @@ -5883,13 +9181,6 @@ Utiltities ************************************************************************************************************************************************************/ -/* -Adjust buffer size based on a scaling factor. - -This just multiplies the base size by the scaling factor, making sure it's a size of at least 1. -*/ -MA_API ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale); - /* Calculates a buffer size in milliseconds from the specified number of frames and sample rate. */ @@ -5914,7 +9205,6 @@ For all formats except `ma_format_u8`, the output buffer will be filled with 0. makes more sense for the purpose of mixing to initialize it to the center point. */ MA_API void ma_silence_pcm_frames(void* p, ma_uint64 frameCount, ma_format format, ma_uint32 channels); -static MA_INLINE void ma_zero_pcm_frames(void* p, ma_uint64 frameCount, ma_format format, ma_uint32 channels) { ma_silence_pcm_frames(p, frameCount, format, channels); } /* @@ -5927,10 +9217,14 @@ static MA_INLINE const float* ma_offset_pcm_frames_const_ptr_f32(const float* p, /* -Clips f32 samples. +Clips samples. */ -MA_API void ma_clip_samples_f32(float* p, ma_uint64 sampleCount); -static MA_INLINE void ma_clip_pcm_frames_f32(float* p, ma_uint64 frameCount, ma_uint32 channels) { ma_clip_samples_f32(p, frameCount*channels); } +MA_API void ma_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count); +MA_API void ma_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count); +MA_API void ma_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels); /* Helper for applying a volume factor to samples. @@ -5949,11 +9243,11 @@ MA_API void ma_apply_volume_factor_s24(void* pSamples, ma_uint64 sampleCount, fl MA_API void ma_apply_volume_factor_s32(ma_int32* pSamples, ma_uint64 sampleCount, float factor); MA_API void ma_apply_volume_factor_f32(float* pSamples, ma_uint64 sampleCount, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFramesOut, const ma_uint8* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFramesOut, const ma_int16* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFramesOut, const ma_int32* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pPCMFramesOut, const float* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pFramesOut, const ma_uint8* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pFramesOut, const ma_int16* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pFramesOut, const ma_int32* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor); MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor); MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor); @@ -5963,36 +9257,55 @@ MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pFrames, ma_uint64 f MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor); MA_API void ma_apply_volume_factor_pcm_frames(void* pFrames, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor); +MA_API void ma_copy_and_apply_volume_factor_per_channel_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains); + + +MA_API void ma_copy_and_apply_volume_and_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume); +MA_API void ma_copy_and_apply_volume_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume); + /* Helper for converting a linear factor to gain in decibels. */ -MA_API float ma_factor_to_gain_db(float factor); +MA_API float ma_volume_linear_to_db(float factor); /* Helper for converting gain in decibels to a linear factor. */ -MA_API float ma_gain_db_to_factor(float gain); +MA_API float ma_volume_db_to_linear(float gain); + + +/************************************************************************************************** + +Data Source + +**************************************************************************************************/ typedef void ma_data_source; +#define MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT 0x00000001 + typedef struct { ma_result (* onRead)(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); ma_result (* onSeek)(ma_data_source* pDataSource, ma_uint64 frameIndex); - ma_result (* onMap)(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount); /* Returns MA_AT_END if the end has been reached. This should be considered successful. */ - ma_result (* onUnmap)(ma_data_source* pDataSource, ma_uint64 frameCount); - ma_result (* onGetDataFormat)(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); + ma_result (* onGetDataFormat)(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); ma_result (* onGetCursor)(ma_data_source* pDataSource, ma_uint64* pCursor); ma_result (* onGetLength)(ma_data_source* pDataSource, ma_uint64* pLength); -} ma_data_source_vtable, ma_data_source_callbacks; /* TODO: Remove ma_data_source_callbacks in version 0.11. */ + ma_result (* onSetLooping)(ma_data_source* pDataSource, ma_bool32 isLooping); + ma_uint32 flags; +} ma_data_source_vtable; typedef ma_data_source* (* ma_data_source_get_next_proc)(ma_data_source* pDataSource); typedef struct { - const ma_data_source_vtable* vtable; /* Can be null, which is useful for proxies. */ + const ma_data_source_vtable* vtable; } ma_data_source_config; MA_API ma_data_source_config ma_data_source_config_init(void); @@ -6000,9 +9313,6 @@ MA_API ma_data_source_config ma_data_source_config_init(void); typedef struct { - ma_data_source_callbacks cb; /* TODO: Remove this. */ - - /* Variables below are placeholder and not yet used. */ const ma_data_source_vtable* vtable; ma_uint64 rangeBegInFrames; ma_uint64 rangeEndInFrames; /* Set to -1 for unranged (default). */ @@ -6011,30 +9321,31 @@ typedef struct ma_data_source* pCurrent; /* When non-NULL, the data source being initialized will act as a proxy and will route all operations to pCurrent. Used in conjunction with pNext/onGetNext for seamless chaining. */ ma_data_source* pNext; /* When set to NULL, onGetNext will be used. */ ma_data_source_get_next_proc onGetNext; /* Will be used when pNext is NULL. If both are NULL, no next will be used. */ + MA_ATOMIC(4, ma_bool32) isLooping; } ma_data_source_base; MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_data_source* pDataSource); MA_API void ma_data_source_uninit(ma_data_source* pDataSource); -MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ -MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked, ma_bool32 loop); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount); */ +MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ +MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked); /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, &framesRead); */ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex); -MA_API ma_result ma_data_source_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount); /* Returns MA_NOT_IMPLEMENTED if mapping is not supported. */ -MA_API ma_result ma_data_source_unmap(ma_data_source* pDataSource, ma_uint64 frameCount); /* Returns MA_AT_END if the end has been reached. */ -MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate); +MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor); MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) +MA_API ma_result ma_data_source_get_cursor_in_seconds(ma_data_source* pDataSource, float* pCursor); +MA_API ma_result ma_data_source_get_length_in_seconds(ma_data_source* pDataSource, float* pLength); +MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool32 isLooping); +MA_API ma_bool32 ma_data_source_is_looping(const ma_data_source* pDataSource); MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 rangeBegInFrames, ma_uint64 rangeEndInFrames); -MA_API void ma_data_source_get_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames); +MA_API void ma_data_source_get_range_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames); MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 loopBegInFrames, ma_uint64 loopEndInFrames); -MA_API void ma_data_source_get_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames); +MA_API void ma_data_source_get_loop_point_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames); MA_API ma_result ma_data_source_set_current(ma_data_source* pDataSource, ma_data_source* pCurrentDataSource); -MA_API ma_data_source* ma_data_source_get_current(ma_data_source* pDataSource); +MA_API ma_data_source* ma_data_source_get_current(const ma_data_source* pDataSource); MA_API ma_result ma_data_source_set_next(ma_data_source* pDataSource, ma_data_source* pNextDataSource); -MA_API ma_data_source* ma_data_source_get_next(ma_data_source* pDataSource); +MA_API ma_data_source* ma_data_source_get_next(const ma_data_source* pDataSource); MA_API ma_result ma_data_source_set_next_callback(ma_data_source* pDataSource, ma_data_source_get_next_proc onGetNext); -MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_source* pDataSource); -#endif +MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(const ma_data_source* pDataSource); typedef struct @@ -6042,6 +9353,7 @@ typedef struct ma_data_source_base ds; ma_format format; ma_uint32 channels; + ma_uint32 sampleRate; ma_uint64 cursor; ma_uint64 sizeInFrames; const void* pData; @@ -6065,6 +9377,7 @@ typedef struct { ma_format format; ma_uint32 channels; + ma_uint32 sampleRate; ma_uint64 sizeInFrames; const void* pData; /* If set to NULL, will allocate a block of memory for you. */ ma_allocation_callbacks allocationCallbacks; @@ -6095,6 +9408,69 @@ MA_API ma_result ma_audio_buffer_get_length_in_pcm_frames(const ma_audio_buffer* MA_API ma_result ma_audio_buffer_get_available_frames(const ma_audio_buffer* pAudioBuffer, ma_uint64* pAvailableFrames); +/* +Paged Audio Buffer +================== +A paged audio buffer is made up of a linked list of pages. It's expandable, but not shrinkable. It +can be used for cases where audio data is streamed in asynchronously while allowing data to be read +at the same time. + +This is lock-free, but not 100% thread safe. You can append a page and read from the buffer across +simultaneously across different threads, however only one thread at a time can append, and only one +thread at a time can read and seek. +*/ +typedef struct ma_paged_audio_buffer_page ma_paged_audio_buffer_page; +struct ma_paged_audio_buffer_page +{ + MA_ATOMIC(MA_SIZEOF_PTR, ma_paged_audio_buffer_page*) pNext; + ma_uint64 sizeInFrames; + ma_uint8 pAudioData[1]; +}; + +typedef struct +{ + ma_format format; + ma_uint32 channels; + ma_paged_audio_buffer_page head; /* Dummy head for the lock-free algorithm. Always has a size of 0. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_paged_audio_buffer_page*) pTail; /* Never null. Initially set to &head. */ +} ma_paged_audio_buffer_data; + +MA_API ma_result ma_paged_audio_buffer_data_init(ma_format format, ma_uint32 channels, ma_paged_audio_buffer_data* pData); +MA_API void ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data* pData, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data* pData); +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data* pData); +MA_API ma_result ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data* pData, ma_uint64* pLength); +MA_API ma_result ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data* pData, ma_uint64 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks, ma_paged_audio_buffer_page** ppPage); +MA_API ma_result ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage); +MA_API ma_result ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data* pData, ma_uint32 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks); + + +typedef struct +{ + ma_paged_audio_buffer_data* pData; /* Must not be null. */ +} ma_paged_audio_buffer_config; + +MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data* pData); + + +typedef struct +{ + ma_data_source_base ds; + ma_paged_audio_buffer_data* pData; /* Audio data is read from here. Cannot be null. */ + ma_paged_audio_buffer_page* pCurrent; + ma_uint64 relativeCursor; /* Relative to the current page. */ + ma_uint64 absoluteCursor; +} ma_paged_audio_buffer; + +MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer); +MA_API void ma_paged_audio_buffer_uninit(ma_paged_audio_buffer* pPagedAudioBuffer); +MA_API ma_result ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); /* Returns MA_AT_END if no more pages available. */ +MA_API ma_result ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64 frameIndex); +MA_API ma_result ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pCursor); +MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pLength); + + /************************************************************************************************************************************************************ @@ -6108,8 +9484,11 @@ appropriate for a given situation. typedef void ma_vfs; typedef ma_handle ma_vfs_file; -#define MA_OPEN_MODE_READ 0x00000001 -#define MA_OPEN_MODE_WRITE 0x00000002 +typedef enum +{ + MA_OPEN_MODE_READ = 0x00000001, + MA_OPEN_MODE_WRITE = 0x00000002 +} ma_open_mode_flags; typedef enum { @@ -6162,11 +9541,6 @@ typedef ma_result (* ma_tell_proc)(void* pUserData, ma_int64* pCursor); #if !defined(MA_NO_DECODING) || !defined(MA_NO_ENCODING) -typedef enum -{ - ma_resource_format_wav -} ma_resource_format; - typedef enum { ma_encoding_format_unknown = 0, @@ -6193,25 +9567,24 @@ typedef struct ma_decoder ma_decoder; typedef struct { ma_format preferredFormat; + ma_uint32 seekPointCount; /* Set to > 0 to generate a seektable if the decoding backend supports it. */ } ma_decoding_backend_config; -MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat); +MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount); typedef struct { - ma_result (* onInit )(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); - ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - ma_result (* onInitMemory )(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ - void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); - ma_result (* onGetChannelMap)(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap); + ma_result (* onInit )(void* pUserData, ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); + ma_result (* onInitFile )(void* pUserData, const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + ma_result (* onInitFileW )(void* pUserData, const wchar_t* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + ma_result (* onInitMemory)(void* pUserData, const void* pData, size_t dataSize, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source** ppBackend); /* Optional. */ + void (* onUninit )(void* pUserData, ma_data_source* pBackend, const ma_allocation_callbacks* pAllocationCallbacks); } ma_decoding_backend_vtable; -/* TODO: Convert read and seek to be consistent with the VFS API (ma_result return value, bytes read moved to an output parameter). */ -typedef size_t (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead); /* Returns the number of bytes read. */ -typedef ma_bool32 (* ma_decoder_seek_proc)(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin); +typedef ma_result (* ma_decoder_read_proc)(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead); /* Returns the number of bytes read. */ +typedef ma_result (* ma_decoder_seek_proc)(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin); typedef ma_result (* ma_decoder_tell_proc)(ma_decoder* pDecoder, ma_int64* pCursor); typedef struct @@ -6219,23 +9592,13 @@ typedef struct ma_format format; /* Set to 0 or ma_format_unknown to use the stream's internal format. */ ma_uint32 channels; /* Set to 0 to use the stream's internal channels. */ ma_uint32 sampleRate; /* Set to 0 to use the stream's internal sample rate. */ - ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel* pChannelMap; ma_channel_mix_mode channelMixMode; ma_dither_mode ditherMode; - struct - { - ma_resample_algorithm algorithm; - struct - { - ma_uint32 lpfOrder; - } linear; - struct - { - int quality; - } speex; - } resampling; + ma_resampler_config resampling; ma_allocation_callbacks allocationCallbacks; ma_encoding_format encodingFormat; + ma_uint32 seekPointCount; /* When set to > 0, specifies the number of seek points to use for the generation of a seek table. Not all decoding backends support this. */ ma_decoding_backend_vtable** ppCustomBackendVTables; ma_uint32 customBackendCount; void* pCustomBackendUserData; @@ -6255,8 +9618,11 @@ struct ma_decoder ma_format outputFormat; ma_uint32 outputChannels; ma_uint32 outputSampleRate; - ma_channel outputChannelMap[MA_MAX_CHANNELS]; - ma_data_converter converter; /* <-- Data conversion is achieved by running frames through this. */ + ma_data_converter converter; /* Data conversion is achieved by running frames through this. */ + void* pInputCache; /* In input format. Can be null if it's not needed. */ + ma_uint64 inputCacheCap; /* The capacity of the input cache. */ + ma_uint64 inputCacheConsumed; /* The number of frames that have been consumed in the cache. Used for determining the next valid frame. */ + ma_uint64 inputCacheRemaining; /* The number of valid frames remaining in the cahce. */ ma_allocation_callbacks allocationCallbacks; union { @@ -6289,6 +9655,25 @@ Uninitializes a decoder. */ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder); +/* +Reads PCM frames from the given decoder. + +This is not thread safe without your own synchronization. +*/ +MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); + +/* +Seeks to a PCM frame based on it's absolute index. + +This is not thread safe without your own synchronization. +*/ +MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex); + +/* +Retrieves the decoder's output data format. +*/ +MA_API ma_result ma_decoder_get_data_format(ma_decoder* pDecoder, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); + /* Retrieves the current position of the read cursor in PCM frames. */ @@ -6308,21 +9693,7 @@ For MP3's, this will decode the entire file. Do not call this in time critical s This function is not thread safe without your own synchronization. */ -MA_API ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder); - -/* -Reads PCM frames from the given decoder. - -This is not thread safe without your own synchronization. -*/ -MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount); - -/* -Seeks to a PCM frame based on it's absolute index. - -This is not thread safe without your own synchronization. -*/ -MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex); +MA_API ma_result ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder, ma_uint64* pLength); /* Retrieves the number of frames that can be read before reaching the end. @@ -6343,43 +9714,6 @@ MA_API ma_result ma_decode_from_vfs(ma_vfs* pVFS, const char* pFilePath, ma_deco MA_API ma_result ma_decode_file(const char* pFilePath, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut); MA_API ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder_config* pConfig, ma_uint64* pFrameCountOut, void** ppPCMFramesOut); - - - -/* -DEPRECATED - -Set the "encodingFormat" variable in the decoder config instead: - - decoderConfig.encodingFormat = ma_encoding_format_wav; - -These functions will be removed in version 0.11. -*/ -MA_API ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_wav(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_flac(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_mp3(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_wav(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_flac(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_mp3(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_vorbis(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_wav_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_flac_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_mp3_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); -MA_API ma_result ma_decoder_init_file_vorbis_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder); - #endif /* MA_NO_DECODING */ @@ -6394,22 +9728,22 @@ Encoders do not perform any format conversion for you. If your target format doe #ifndef MA_NO_ENCODING typedef struct ma_encoder ma_encoder; -typedef size_t (* ma_encoder_write_proc) (ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite); /* Returns the number of bytes written. */ -typedef ma_bool32 (* ma_encoder_seek_proc) (ma_encoder* pEncoder, int byteOffset, ma_seek_origin origin); +typedef ma_result (* ma_encoder_write_proc) (ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite, size_t* pBytesWritten); +typedef ma_result (* ma_encoder_seek_proc) (ma_encoder* pEncoder, ma_int64 offset, ma_seek_origin origin); typedef ma_result (* ma_encoder_init_proc) (ma_encoder* pEncoder); typedef void (* ma_encoder_uninit_proc) (ma_encoder* pEncoder); -typedef ma_uint64 (* ma_encoder_write_pcm_frames_proc)(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount); +typedef ma_result (* ma_encoder_write_pcm_frames_proc)(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten); typedef struct { - ma_resource_format resourceFormat; + ma_encoding_format encodingFormat; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; ma_allocation_callbacks allocationCallbacks; } ma_encoder_config; -MA_API ma_encoder_config ma_encoder_config_init(ma_resource_format resourceFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); +MA_API ma_encoder_config ma_encoder_config_init(ma_encoding_format encodingFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); struct ma_encoder { @@ -6421,14 +9755,23 @@ struct ma_encoder ma_encoder_write_pcm_frames_proc onWritePCMFrames; void* pUserData; void* pInternalEncoder; /* <-- The drwav/drflac/stb_vorbis/etc. objects. */ - void* pFile; /* FILE*. Only used when initialized with ma_encoder_init_file(). */ + union + { + struct + { + ma_vfs* pVFS; + ma_vfs_file file; + } vfs; + } data; }; MA_API ma_result ma_encoder_init(ma_encoder_write_proc onWrite, ma_encoder_seek_proc onSeek, void* pUserData, const ma_encoder_config* pConfig, ma_encoder* pEncoder); +MA_API ma_result ma_encoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); +MA_API ma_result ma_encoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); MA_API ma_result ma_encoder_init_file(const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); MA_API ma_result ma_encoder_init_file_w(const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder); MA_API void ma_encoder_uninit(ma_encoder* pEncoder); -MA_API ma_uint64 ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount); +MA_API ma_result ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten); #endif /* MA_NO_ENCODING */ @@ -6469,7 +9812,7 @@ typedef struct MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform); MA_API void ma_waveform_uninit(ma_waveform* pWaveform); -MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount); +MA_API ma_result ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex); MA_API ma_result ma_waveform_set_amplitude(ma_waveform* pWaveform, double amplitude); MA_API ma_result ma_waveform_set_frequency(ma_waveform* pWaveform, double frequency); @@ -6483,6 +9826,7 @@ typedef enum ma_noise_type_brownian } ma_noise_type; + typedef struct { ma_format format; @@ -6504,32 +9848,1187 @@ typedef struct { struct { - double bin[MA_MAX_CHANNELS][16]; - double accumulation[MA_MAX_CHANNELS]; - ma_uint32 counter[MA_MAX_CHANNELS]; + double** bin; + double* accumulation; + ma_uint32* counter; } pink; struct { - double accumulation[MA_MAX_CHANNELS]; + double* accumulation; } brownian; } state; + + /* Memory management. */ + void* _pHeap; + ma_bool32 _ownsHeap; } ma_noise; -MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise); -MA_API void ma_noise_uninit(ma_noise* pNoise); -MA_API ma_uint64 ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount); +MA_API ma_result ma_noise_get_heap_size(const ma_noise_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void* pHeap, ma_noise* pNoise); +MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_noise* pNoise); +MA_API void ma_noise_uninit(ma_noise* pNoise, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude); MA_API ma_result ma_noise_set_seed(ma_noise* pNoise, ma_int32 seed); MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type); #endif /* MA_NO_GENERATION */ + + +/************************************************************************************************************************************************************ + +Resource Manager + +************************************************************************************************************************************************************/ +/* The resource manager cannot be enabled if there is no decoder. */ +#if !defined(MA_NO_RESOURCE_MANAGER) && defined(MA_NO_DECODING) +#define MA_NO_RESOURCE_MANAGER +#endif + +#ifndef MA_NO_RESOURCE_MANAGER +typedef struct ma_resource_manager ma_resource_manager; +typedef struct ma_resource_manager_data_buffer_node ma_resource_manager_data_buffer_node; +typedef struct ma_resource_manager_data_buffer ma_resource_manager_data_buffer; +typedef struct ma_resource_manager_data_stream ma_resource_manager_data_stream; +typedef struct ma_resource_manager_data_source ma_resource_manager_data_source; + +typedef enum +{ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM = 0x00000001, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ + MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH = 0x00000010 /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ +} ma_resource_manager_data_source_flags; + + +/* +Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional. +*/ +typedef struct +{ + ma_async_notification* pNotification; + ma_fence* pFence; +} ma_resource_manager_pipeline_stage_notification; + +typedef struct +{ + ma_resource_manager_pipeline_stage_notification init; /* Initialization of the decoder. */ + ma_resource_manager_pipeline_stage_notification done; /* Decoding fully completed. */ +} ma_resource_manager_pipeline_notifications; + +MA_API ma_resource_manager_pipeline_notifications ma_resource_manager_pipeline_notifications_init(void); + + + +/* BEGIN BACKWARDS COMPATIBILITY */ +/* TODO: Remove this block in version 0.12. */ +#if 1 +#define ma_resource_manager_job ma_job +#define ma_resource_manager_job_init ma_job_init +#define MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_FLAG_NON_BLOCKING MA_JOB_QUEUE_FLAG_NON_BLOCKING +#define ma_resource_manager_job_queue_config ma_job_queue_config +#define ma_resource_manager_job_queue_config_init ma_job_queue_config_init +#define ma_resource_manager_job_queue ma_job_queue +#define ma_resource_manager_job_queue_get_heap_size ma_job_queue_get_heap_size +#define ma_resource_manager_job_queue_init_preallocated ma_job_queue_init_preallocated +#define ma_resource_manager_job_queue_init ma_job_queue_init +#define ma_resource_manager_job_queue_uninit ma_job_queue_uninit +#define ma_resource_manager_job_queue_post ma_job_queue_post +#define ma_resource_manager_job_queue_next ma_job_queue_next +#endif +/* END BACKWARDS COMPATIBILITY */ + + + + +/* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ +#ifndef MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT +#define MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT 64 +#endif + +typedef enum +{ + /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ + MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING = 0x00000001, + + /* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */ + MA_RESOURCE_MANAGER_FLAG_NO_THREADING = 0x00000002 +} ma_resource_manager_flags; + +typedef struct +{ + const char* pFilePath; + const wchar_t* pFilePathW; + const ma_resource_manager_pipeline_notifications* pNotifications; + ma_uint64 initialSeekPointInPCMFrames; + ma_uint64 rangeBegInPCMFrames; + ma_uint64 rangeEndInPCMFrames; + ma_uint64 loopPointBegInPCMFrames; + ma_uint64 loopPointEndInPCMFrames; + ma_bool32 isLooping; + ma_uint32 flags; +} ma_resource_manager_data_source_config; + +MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void); + + +typedef enum +{ + ma_resource_manager_data_supply_type_unknown = 0, /* Used for determining whether or the data supply has been initialized. */ + ma_resource_manager_data_supply_type_encoded, /* Data supply is an encoded buffer. Connector is ma_decoder. */ + ma_resource_manager_data_supply_type_decoded, /* Data supply is a decoded buffer. Connector is ma_audio_buffer. */ + ma_resource_manager_data_supply_type_decoded_paged /* Data supply is a linked list of decoded buffers. Connector is ma_paged_audio_buffer. */ +} ma_resource_manager_data_supply_type; + +typedef struct +{ + MA_ATOMIC(4, ma_resource_manager_data_supply_type) type; /* Read and written from different threads so needs to be accessed atomically. */ + union + { + struct + { + const void* pData; + size_t sizeInBytes; + } encoded; + struct + { + const void* pData; + ma_uint64 totalFrameCount; + ma_uint64 decodedFrameCount; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + } decoded; + struct + { + ma_paged_audio_buffer_data data; + ma_uint64 decodedFrameCount; + ma_uint32 sampleRate; + } decodedPaged; + } backend; +} ma_resource_manager_data_supply; + +struct ma_resource_manager_data_buffer_node +{ + ma_uint32 hashedName32; /* The hashed name. This is the key. */ + ma_uint32 refCount; + MA_ATOMIC(4, ma_result) result; /* Result from asynchronous loading. When loading set to MA_BUSY. When fully loaded set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + ma_bool32 isDataOwnedByResourceManager; /* Set to true when the underlying data buffer was allocated the resource manager. Set to false if it is owned by the application (via ma_resource_manager_register_*()). */ + ma_resource_manager_data_supply data; + ma_resource_manager_data_buffer_node* pParent; + ma_resource_manager_data_buffer_node* pChildLo; + ma_resource_manager_data_buffer_node* pChildHi; +}; + +struct ma_resource_manager_data_buffer +{ + ma_data_source_base ds; /* Base data source. A data buffer is a data source. */ + ma_resource_manager* pResourceManager; /* A pointer to the resource manager that owns this buffer. */ + ma_resource_manager_data_buffer_node* pNode; /* The data node. This is reference counted and is what supplies the data. */ + ma_uint32 flags; /* The flags that were passed used to initialize the buffer. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + ma_uint64 seekTargetInPCMFrames; /* Only updated by the public API. Never written nor read from the job thread. */ + ma_bool32 seekToCursorOnNextRead; /* On the next read we need to seek to the frame cursor. */ + MA_ATOMIC(4, ma_result) result; /* Keeps track of a result of decoding. Set to MA_BUSY while the buffer is still loading. Set to MA_SUCCESS when loading is finished successfully. Otherwise set to some other code. */ + MA_ATOMIC(4, ma_bool32) isLooping; /* Can be read and written by different threads at the same time. Must be used atomically. */ + ma_bool32 isConnectorInitialized; /* Used for asynchronous loading to ensure we don't try to initialize the connector multiple times while waiting for the node to fully load. */ + union + { + ma_decoder decoder; /* Supply type is ma_resource_manager_data_supply_type_encoded */ + ma_audio_buffer buffer; /* Supply type is ma_resource_manager_data_supply_type_decoded */ + ma_paged_audio_buffer pagedBuffer; /* Supply type is ma_resource_manager_data_supply_type_decoded_paged */ + } connector; /* Connects this object to the node's data supply. */ +}; + +struct ma_resource_manager_data_stream +{ + ma_data_source_base ds; /* Base data source. A data stream is a data source. */ + ma_resource_manager* pResourceManager; /* A pointer to the resource manager that owns this data stream. */ + ma_uint32 flags; /* The flags that were passed used to initialize the stream. */ + ma_decoder decoder; /* Used for filling pages with data. This is only ever accessed by the job thread. The public API should never touch this. */ + ma_bool32 isDecoderInitialized; /* Required for determining whether or not the decoder should be uninitialized in MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM. */ + ma_uint64 totalLengthInPCMFrames; /* This is calculated when first loaded by the MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM. */ + ma_uint32 relativeCursor; /* The playback cursor, relative to the current page. Only ever accessed by the public API. Never accessed by the job thread. */ + MA_ATOMIC(8, ma_uint64) absoluteCursor; /* The playback cursor, in absolute position starting from the start of the file. */ + ma_uint32 currentPageIndex; /* Toggles between 0 and 1. Index 0 is the first half of pPageData. Index 1 is the second half. Only ever accessed by the public API. Never accessed by the job thread. */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ + + /* Written by the public API, read by the job thread. */ + MA_ATOMIC(4, ma_bool32) isLooping; /* Whether or not the stream is looping. It's important to set the looping flag at the data stream level for smooth loop transitions. */ + + /* Written by the job thread, read by the public API. */ + void* pPageData; /* Buffer containing the decoded data of each page. Allocated once at initialization time. */ + MA_ATOMIC(4, ma_uint32) pageFrameCount[2]; /* The number of valid PCM frames in each page. Used to determine the last valid frame. */ + + /* Written and read by both the public API and the job thread. These must be atomic. */ + MA_ATOMIC(4, ma_result) result; /* Result from asynchronous loading. When loading set to MA_BUSY. When initialized set to MA_SUCCESS. When deleting set to MA_UNAVAILABLE. If an error occurs when loading, set to an error code. */ + MA_ATOMIC(4, ma_bool32) isDecoderAtEnd; /* Whether or not the decoder has reached the end. */ + MA_ATOMIC(4, ma_bool32) isPageValid[2]; /* Booleans to indicate whether or not a page is valid. Set to false by the public API, set to true by the job thread. Set to false as the pages are consumed, true when they are filled. */ + MA_ATOMIC(4, ma_bool32) seekCounter; /* When 0, no seeking is being performed. When > 0, a seek is being performed and reading should be delayed with MA_BUSY. */ +}; + +struct ma_resource_manager_data_source +{ + union + { + ma_resource_manager_data_buffer buffer; + ma_resource_manager_data_stream stream; + } backend; /* Must be the first item because we need the first item to be the data source callbacks for the buffer or stream. */ + + ma_uint32 flags; /* The flags that were passed in to ma_resource_manager_data_source_init(). */ + MA_ATOMIC(4, ma_uint32) executionCounter; /* For allocating execution orders for jobs. */ + MA_ATOMIC(4, ma_uint32) executionPointer; /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ +}; + +typedef struct +{ + ma_allocation_callbacks allocationCallbacks; + ma_log* pLog; + ma_format decodedFormat; /* The decoded format to use. Set to ma_format_unknown (default) to use the file's native format. */ + ma_uint32 decodedChannels; /* The decoded channel count to use. Set to 0 (default) to use the file's native channel count. */ + ma_uint32 decodedSampleRate; /* the decoded sample rate to use. Set to 0 (default) to use the file's native sample rate. */ + ma_uint32 jobThreadCount; /* Set to 0 if you want to self-manage your job threads. Defaults to 1. */ + ma_uint32 jobQueueCapacity; /* The maximum number of jobs that can fit in the queue at a time. Defaults to MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY. Cannot be zero. */ + ma_uint32 flags; + ma_vfs* pVFS; /* Can be NULL in which case defaults will be used. */ + ma_decoding_backend_vtable** ppCustomDecodingBackendVTables; + ma_uint32 customDecodingBackendCount; + void* pCustomDecodingBackendUserData; +} ma_resource_manager_config; + +MA_API ma_resource_manager_config ma_resource_manager_config_init(void); + +struct ma_resource_manager +{ + ma_resource_manager_config config; + ma_resource_manager_data_buffer_node* pRootDataBufferNode; /* The root buffer in the binary tree. */ +#ifndef MA_NO_THREADING + ma_mutex dataBufferBSTLock; /* For synchronizing access to the data buffer binary tree. */ + ma_thread jobThreads[MA_RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT]; /* The threads for executing jobs. */ +#endif + ma_job_queue jobQueue; /* Multi-consumer, multi-producer job queue for managing jobs for asynchronous decoding and streaming. */ + ma_default_vfs defaultVFS; /* Only used if a custom VFS is not specified. */ + ma_log log; /* Only used if no log was specified in the config. */ +}; + +/* Init. */ +MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager); +MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager); +MA_API ma_log* ma_resource_manager_get_log(ma_resource_manager* pResourceManager); + +/* Registration. */ +MA_API ma_result ma_resource_manager_register_file(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags); +MA_API ma_result ma_resource_manager_register_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags); +MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ +MA_API ma_result ma_resource_manager_register_decoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate); +MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes); /* Does not copy. Increments the reference count if already exists and returns MA_SUCCESS. */ +MA_API ma_result ma_resource_manager_register_encoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, size_t sizeInBytes); +MA_API ma_result ma_resource_manager_unregister_file(ma_resource_manager* pResourceManager, const char* pFilePath); +MA_API ma_result ma_resource_manager_unregister_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath); +MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName); +MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName); + +/* Data Buffers. */ +MA_API ma_result ma_resource_manager_data_buffer_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer* pDataBuffer, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_buffer_is_looping(const ma_resource_manager_data_buffer* pDataBuffer); +MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames); + +/* Data Streams. */ +MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream* pDataStream, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_stream_is_looping(const ma_resource_manager_data_stream* pDataStream); +MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames); + +/* Data Sources. */ +MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex); +MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor); +MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength); +MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping); +MA_API ma_bool32 ma_resource_manager_data_source_is_looping(const ma_resource_manager_data_source* pDataSource); +MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames); + +/* Job management. */ +MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_job* pJob); +MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager); /* Helper for posting a quit job. */ +MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_job* pJob); +MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob); /* DEPRECATED. Use ma_job_process(). Will be removed in version 0.12. */ +MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager); /* Returns MA_CANCELLED if a MA_JOB_TYPE_QUIT job is found. In non-blocking mode, returns MA_NO_DATA_AVAILABLE if no jobs are available. */ +#endif /* MA_NO_RESOURCE_MANAGER */ + + + +/************************************************************************************************************************************************************ + +Node Graph + +************************************************************************************************************************************************************/ +#ifndef MA_NO_NODE_GRAPH +/* Must never exceed 254. */ +#ifndef MA_MAX_NODE_BUS_COUNT +#define MA_MAX_NODE_BUS_COUNT 254 +#endif + +/* Used internally by miniaudio for memory management. Must never exceed MA_MAX_NODE_BUS_COUNT. */ +#ifndef MA_MAX_NODE_LOCAL_BUS_COUNT +#define MA_MAX_NODE_LOCAL_BUS_COUNT 2 +#endif + +/* Use this when the bus count is determined by the node instance rather than the vtable. */ +#define MA_NODE_BUS_COUNT_UNKNOWN 255 + +typedef struct ma_node_graph ma_node_graph; +typedef void ma_node; + + +/* Node flags. */ +typedef enum +{ + MA_NODE_FLAG_PASSTHROUGH = 0x00000001, + MA_NODE_FLAG_CONTINUOUS_PROCESSING = 0x00000002, + MA_NODE_FLAG_ALLOW_NULL_INPUT = 0x00000004, + MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES = 0x00000008, + MA_NODE_FLAG_SILENT_OUTPUT = 0x00000010 +} ma_node_flags; + + +/* The playback state of a node. Either started or stopped. */ +typedef enum +{ + ma_node_state_started = 0, + ma_node_state_stopped = 1 +} ma_node_state; + + +typedef struct +{ + /* + Extended processing callback. This callback is used for effects that process input and output + at different rates (i.e. they perform resampling). This is similar to the simple version, only + they take two seperate frame counts: one for input, and one for output. + + On input, `pFrameCountOut` is equal to the capacity of the output buffer for each bus, whereas + `pFrameCountIn` will be equal to the number of PCM frames in each of the buffers in `ppFramesIn`. + + On output, set `pFrameCountOut` to the number of PCM frames that were actually output and set + `pFrameCountIn` to the number of input frames that were consumed. + */ + void (* onProcess)(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut); + + /* + A callback for retrieving the number of a input frames that are required to output the + specified number of output frames. You would only want to implement this when the node performs + resampling. This is optional, even for nodes that perform resampling, but it does offer a + small reduction in latency as it allows miniaudio to calculate the exact number of input frames + to read at a time instead of having to estimate. + */ + ma_result (* onGetRequiredInputFrameCount)(ma_node* pNode, ma_uint32 outputFrameCount, ma_uint32* pInputFrameCount); + + /* + The number of input buses. This is how many sub-buffers will be contained in the `ppFramesIn` + parameters of the callbacks above. + */ + ma_uint8 inputBusCount; + + /* + The number of output buses. This is how many sub-buffers will be contained in the `ppFramesOut` + parameters of the callbacks above. + */ + ma_uint8 outputBusCount; + + /* + Flags describing characteristics of the node. This is currently just a placeholder for some + ideas for later on. + */ + ma_uint32 flags; +} ma_node_vtable; + +typedef struct +{ + const ma_node_vtable* vtable; /* Should never be null. Initialization of the node will fail if so. */ + ma_node_state initialState; /* Defaults to ma_node_state_started. */ + ma_uint32 inputBusCount; /* Only used if the vtable specifies an input bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise must be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + ma_uint32 outputBusCount; /* Only used if the vtable specifies an output bus count of `MA_NODE_BUS_COUNT_UNKNOWN`, otherwise be set to `MA_NODE_BUS_COUNT_UNKNOWN` (default). */ + const ma_uint32* pInputChannels; /* The number of elements are determined by the input bus count as determined by the vtable, or `inputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ + const ma_uint32* pOutputChannels; /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ +} ma_node_config; + +MA_API ma_node_config ma_node_config_init(void); + + +/* +A node has multiple output buses. An output bus is attached to an input bus as an item in a linked +list. Think of the input bus as a linked list, with the output bus being an item in that list. +*/ +typedef struct ma_node_output_bus ma_node_output_bus; +struct ma_node_output_bus +{ + /* Immutable. */ + ma_node* pNode; /* The node that owns this output bus. The input node. Will be null for dummy head and tail nodes. */ + ma_uint8 outputBusIndex; /* The index of the output bus on pNode that this output bus represents. */ + ma_uint8 channels; /* The number of channels in the audio stream for this bus. */ + + /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ + MA_ATOMIC(1, ma_uint8) inputNodeInputBusIndex; /* The index of the input bus on the input. Required for detaching. */ + MA_ATOMIC(4, ma_uint32) flags; /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ + MA_ATOMIC(4, ma_uint32) refCount; /* Reference count for some thread-safety when detaching. */ + MA_ATOMIC(4, ma_bool32) isAttached; /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ + MA_ATOMIC(4, ma_spinlock) lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + MA_ATOMIC(4, float) volume; /* Linear. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node_output_bus*) pNext; /* If null, it's the tail node or detached. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node_output_bus*) pPrev; /* If null, it's the head node or detached. */ + MA_ATOMIC(MA_SIZEOF_PTR, ma_node*) pInputNode; /* The node that this output bus is attached to. Required for detaching. */ +}; + +/* +A node has multiple input buses. The output buses of a node are connecting to the input busses of +another. An input bus is essentially just a linked list of output buses. +*/ +typedef struct ma_node_input_bus ma_node_input_bus; +struct ma_node_input_bus +{ + /* Mutable via multiple threads. */ + ma_node_output_bus head; /* Dummy head node for simplifying some lock-free thread-safety stuff. */ + MA_ATOMIC(4, ma_uint32) nextCounter; /* This is used to determine whether or not the input bus is finding the next node in the list. Used for thread safety when detaching output buses. */ + MA_ATOMIC(4, ma_spinlock) lock; /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ + + /* Set once at startup. */ + ma_uint8 channels; /* The number of channels in the audio stream for this bus. */ +}; + + +typedef struct ma_node_base ma_node_base; +struct ma_node_base +{ + /* These variables are set once at startup. */ + ma_node_graph* pNodeGraph; /* The graph this node belongs to. */ + const ma_node_vtable* vtable; + float* pCachedData; /* Allocated on the heap. Fixed size. Needs to be stored on the heap because reading from output buses is done in separate function calls. */ + ma_uint16 cachedDataCapInFramesPerBus; /* The capacity of the input data cache in frames, per bus. */ + + /* These variables are read and written only from the audio thread. */ + ma_uint16 cachedFrameCountOut; + ma_uint16 cachedFrameCountIn; + ma_uint16 consumedFrameCountIn; + + /* These variables are read and written between different threads. */ + MA_ATOMIC(4, ma_node_state) state; /* When set to stopped, nothing will be read, regardless of the times in stateTimes. */ + MA_ATOMIC(8, ma_uint64) stateTimes[2]; /* Indexed by ma_node_state. Specifies the time based on the global clock that a node should be considered to be in the relevant state. */ + MA_ATOMIC(8, ma_uint64) localTime; /* The node's local clock. This is just a running sum of the number of output frames that have been processed. Can be modified by any thread with `ma_node_set_time()`. */ + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + ma_node_input_bus* pInputBuses; + ma_node_output_bus* pOutputBuses; + + /* Memory management. */ + ma_node_input_bus _inputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; + ma_node_output_bus _outputBuses[MA_MAX_NODE_LOCAL_BUS_COUNT]; + void* _pHeap; /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */ + ma_bool32 _ownsHeap; /* If set to true, the node owns the heap allocation and _pHeap will be freed in ma_node_uninit(). */ +}; + +MA_API ma_result ma_node_get_heap_size(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, void* pHeap, ma_node* pNode); +MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode); +MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_node_graph* ma_node_get_node_graph(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode); +MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inputBusIndex); +MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex, ma_node* pOtherNode, ma_uint32 otherNodeInputBusIndex); +MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_detach_all_output_buses(ma_node* pNode); +MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputBusIndex, float volume); +MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex); +MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state); +MA_API ma_node_state ma_node_get_state(const ma_node* pNode); +MA_API ma_result ma_node_set_state_time(ma_node* pNode, ma_node_state state, ma_uint64 globalTime); +MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state state); +MA_API ma_node_state ma_node_get_state_by_time(const ma_node* pNode, ma_uint64 globalTime); +MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd); +MA_API ma_uint64 ma_node_get_time(const ma_node* pNode); +MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime); + + +typedef struct +{ + ma_uint32 channels; + ma_uint16 nodeCacheCapInFrames; +} ma_node_graph_config; + +MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels); + + +struct ma_node_graph +{ + /* Immutable. */ + ma_node_base base; /* The node graph itself is a node so it can be connected as an input to different node graph. This has zero inputs and calls ma_node_graph_read_pcm_frames() to generate it's output. */ + ma_node_base endpoint; /* Special node that all nodes eventually connect to. Data is read from this node in ma_node_graph_read_pcm_frames(). */ + ma_uint16 nodeCacheCapInFrames; + + /* Read and written by multiple threads. */ + MA_ATOMIC(4, ma_bool32) isReading; +}; + +MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph); +MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph); +MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph); +MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph); +MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime); + + + +/* Data source node. 0 input buses, 1 output bus. Used for reading from a data source. */ +typedef struct +{ + ma_node_config nodeConfig; + ma_data_source* pDataSource; +} ma_data_source_node_config; + +MA_API ma_data_source_node_config ma_data_source_node_config_init(ma_data_source* pDataSource); + + +typedef struct +{ + ma_node_base base; + ma_data_source* pDataSource; +} ma_data_source_node; + +MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode); +MA_API void ma_data_source_node_uninit(ma_data_source_node* pDataSourceNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API ma_result ma_data_source_node_set_looping(ma_data_source_node* pDataSourceNode, ma_bool32 isLooping); +MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSourceNode); + + +/* Splitter Node. 1 input, 2 outputs. Used for splitting/copying a stream so it can be as input into two separate output nodes. */ +typedef struct +{ + ma_node_config nodeConfig; + ma_uint32 channels; +} ma_splitter_node_config; + +MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels); + + +typedef struct +{ + ma_node_base base; +} ma_splitter_node; + +MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_splitter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_splitter_node* pSplitterNode); +MA_API void ma_splitter_node_uninit(ma_splitter_node* pSplitterNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Biquad Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_biquad_config biquad; +} ma_biquad_node_config; + +MA_API ma_biquad_node_config ma_biquad_node_config_init(ma_uint32 channels, float b0, float b1, float b2, float a0, float a1, float a2); + + +typedef struct +{ + ma_node_base baseNode; + ma_biquad biquad; +} ma_biquad_node; + +MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad_node* pNode); +MA_API ma_result ma_biquad_node_reinit(const ma_biquad_config* pConfig, ma_biquad_node* pNode); +MA_API void ma_biquad_node_uninit(ma_biquad_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Low Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_lpf_config lpf; +} ma_lpf_node_config; + +MA_API ma_lpf_node_config ma_lpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_lpf lpf; +} ma_lpf_node; + +MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf_node* pNode); +MA_API ma_result ma_lpf_node_reinit(const ma_lpf_config* pConfig, ma_lpf_node* pNode); +MA_API void ma_lpf_node_uninit(ma_lpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +High Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_hpf_config hpf; +} ma_hpf_node_config; + +MA_API ma_hpf_node_config ma_hpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_hpf hpf; +} ma_hpf_node; + +MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf_node* pNode); +MA_API ma_result ma_hpf_node_reinit(const ma_hpf_config* pConfig, ma_hpf_node* pNode); +MA_API void ma_hpf_node_uninit(ma_hpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Band Pass Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_bpf_config bpf; +} ma_bpf_node_config; + +MA_API ma_bpf_node_config ma_bpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order); + + +typedef struct +{ + ma_node_base baseNode; + ma_bpf bpf; +} ma_bpf_node; + +MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf_node* pNode); +MA_API ma_result ma_bpf_node_reinit(const ma_bpf_config* pConfig, ma_bpf_node* pNode); +MA_API void ma_bpf_node_uninit(ma_bpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Notching Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_notch_config notch; +} ma_notch_node_config; + +MA_API ma_notch_node_config ma_notch_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_notch2 notch; +} ma_notch_node; + +MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch_node* pNode); +MA_API ma_result ma_notch_node_reinit(const ma_notch_config* pConfig, ma_notch_node* pNode); +MA_API void ma_notch_node_uninit(ma_notch_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Peaking Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_peak_config peak; +} ma_peak_node_config; + +MA_API ma_peak_node_config ma_peak_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_peak2 peak; +} ma_peak_node; + +MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak_node* pNode); +MA_API ma_result ma_peak_node_reinit(const ma_peak_config* pConfig, ma_peak_node* pNode); +MA_API void ma_peak_node_uninit(ma_peak_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +Low Shelf Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_loshelf_config loshelf; +} ma_loshelf_node_config; + +MA_API ma_loshelf_node_config ma_loshelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_loshelf2 loshelf; +} ma_loshelf_node; + +MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf_node* pNode); +MA_API ma_result ma_loshelf_node_reinit(const ma_loshelf_config* pConfig, ma_loshelf_node* pNode); +MA_API void ma_loshelf_node_uninit(ma_loshelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +/* +High Shelf Filter Node +*/ +typedef struct +{ + ma_node_config nodeConfig; + ma_hishelf_config hishelf; +} ma_hishelf_node_config; + +MA_API ma_hishelf_node_config ma_hishelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency); + + +typedef struct +{ + ma_node_base baseNode; + ma_hishelf2 hishelf; +} ma_hishelf_node; + +MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf_node* pNode); +MA_API ma_result ma_hishelf_node_reinit(const ma_hishelf_config* pConfig, ma_hishelf_node* pNode); +MA_API void ma_hishelf_node_uninit(ma_hishelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +typedef struct +{ + ma_node_config nodeConfig; + ma_delay_config delay; +} ma_delay_node_config; + +MA_API ma_delay_node_config ma_delay_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay); + + +typedef struct +{ + ma_node_base baseNode; + ma_delay delay; +} ma_delay_node; + +MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode); +MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks); +MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode); +MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode); +MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value); +MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode); +#endif /* MA_NO_NODE_GRAPH */ + + +/************************************************************************************************************************************************************ + +Engine + +************************************************************************************************************************************************************/ +#if !defined(MA_NO_ENGINE) && !defined(MA_NO_NODE_GRAPH) +typedef struct ma_engine ma_engine; +typedef struct ma_sound ma_sound; + + +/* Sound flags. */ +typedef enum +{ + MA_SOUND_FLAG_STREAM = 0x00000001, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ + MA_SOUND_FLAG_DECODE = 0x00000002, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ + MA_SOUND_FLAG_ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ + MA_SOUND_FLAG_WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ + MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT = 0x00000010, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ + MA_SOUND_FLAG_NO_PITCH = 0x00000020, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ + MA_SOUND_FLAG_NO_SPATIALIZATION = 0x00000040 /* Disable spatialization. */ +} ma_sound_flags; + +#ifndef MA_ENGINE_MAX_LISTENERS +#define MA_ENGINE_MAX_LISTENERS 4 +#endif + +#define MA_LISTENER_INDEX_CLOSEST ((ma_uint8)-1) + +typedef enum +{ + ma_engine_node_type_sound, + ma_engine_node_type_group +} ma_engine_node_type; + +typedef struct +{ + ma_engine* pEngine; + ma_engine_node_type type; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_uint32 sampleRate; /* Only used when the type is set to ma_engine_node_type_sound. */ + ma_bool8 isPitchDisabled; /* Pitching can be explicitly disable with MA_SOUND_FLAG_NO_PITCH to optimize processing. */ + ma_bool8 isSpatializationDisabled; /* Spatialization can be explicitly disabled with MA_SOUND_FLAG_NO_SPATIALIZATION. */ + ma_uint8 pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ +} ma_engine_node_config; + +MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags); + + +/* Base node object for both ma_sound and ma_sound_group. */ +typedef struct +{ + ma_node_base baseNode; /* Must be the first member for compatiblity with the ma_node API. */ + ma_engine* pEngine; /* A pointer to the engine. Set based on the value from the config. */ + ma_uint32 sampleRate; /* The sample rate of the input data. For sounds backed by a data source, this will be the data source's sample rate. Otherwise it'll be the engine's sample rate. */ + ma_fader fader; + ma_linear_resampler resampler; /* For pitch shift. */ + ma_spatializer spatializer; + ma_panner panner; + MA_ATOMIC(4, float) pitch; + float oldPitch; /* For determining whether or not the resampler needs to be updated to reflect the new pitch. The resampler will be updated on the mixing thread. */ + float oldDopplerPitch; /* For determining whether or not the resampler needs to be updated to take a new doppler pitch into account. */ + MA_ATOMIC(4, ma_bool32) isPitchDisabled; /* When set to true, pitching will be disabled which will allow the resampler to be bypassed to save some computation. */ + MA_ATOMIC(4, ma_bool32) isSpatializationDisabled; /* Set to false by default. When set to false, will not have spatialisation applied. */ + MA_ATOMIC(4, ma_uint32) pinnedListenerIndex; /* The index of the listener this node should always use for spatialization. If set to MA_LISTENER_INDEX_CLOSEST the engine will use the closest listener. */ + + /* Memory management. */ + ma_bool8 _ownsHeap; + void* _pHeap; +} ma_engine_node; + +MA_API ma_result ma_engine_node_get_heap_size(const ma_engine_node_config* pConfig, size_t* pHeapSizeInBytes); +MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* pConfig, void* pHeap, ma_engine_node* pEngineNode); +MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode); +MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks); + + +#define MA_SOUND_SOURCE_CHANNEL_COUNT 0xFFFFFFFF + +typedef struct +{ + const char* pFilePath; /* Set this to load from the resource manager. */ + const wchar_t* pFilePathW; /* Set this to load from the resource manager. */ + ma_data_source* pDataSource; /* Set this to load from an existing data source. */ + ma_node* pInitialAttachment; /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ + ma_uint32 initialAttachmentInputBusIndex; /* The index of the input bus of pInitialAttachment to attach the sound to. */ + ma_uint32 channelsIn; /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ + ma_uint32 channelsOut; /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ + ma_uint32 flags; /* A combination of MA_SOUND_FLAG_* flags. */ + ma_uint64 initialSeekPointInPCMFrames; /* Initializes the sound such that it's seeked to this location by default. */ + ma_uint64 rangeBegInPCMFrames; + ma_uint64 rangeEndInPCMFrames; + ma_uint64 loopPointBegInPCMFrames; + ma_uint64 loopPointEndInPCMFrames; + ma_bool32 isLooping; + ma_fence* pDoneFence; /* Released when the resource manager has finished decoding the entire sound. Not used with streams. */ +} ma_sound_config; + +MA_API ma_sound_config ma_sound_config_init(void); + +struct ma_sound +{ + ma_engine_node engineNode; /* Must be the first member for compatibility with the ma_node API. */ + ma_data_source* pDataSource; + MA_ATOMIC(8, ma_uint64) seekTarget; /* The PCM frame index to seek to in the mixing thread. Set to (~(ma_uint64)0) to not perform any seeking. */ + MA_ATOMIC(4, ma_bool32) atEnd; + ma_bool8 ownsDataSource; + + /* + We're declaring a resource manager data source object here to save us a malloc when loading a + sound via the resource manager, which I *think* will be the most common scenario. + */ +#ifndef MA_NO_RESOURCE_MANAGER + ma_resource_manager_data_source* pResourceManagerDataSource; +#endif +}; + +/* Structure specifically for sounds played with ma_engine_play_sound(). Making this a separate structure to reduce overhead. */ +typedef struct ma_sound_inlined ma_sound_inlined; +struct ma_sound_inlined +{ + ma_sound sound; + ma_sound_inlined* pNext; + ma_sound_inlined* pPrev; +}; + +/* A sound group is just a sound. */ +typedef ma_sound_config ma_sound_group_config; +typedef ma_sound ma_sound_group; + +MA_API ma_sound_group_config ma_sound_group_config_init(void); + + +typedef struct +{ +#if !defined(MA_NO_RESOURCE_MANAGER) + ma_resource_manager* pResourceManager; /* Can be null in which case a resource manager will be created for you. */ +#endif +#if !defined(MA_NO_DEVICE_IO) + ma_context* pContext; + ma_device* pDevice; /* If set, the caller is responsible for calling ma_engine_data_callback() in the device's data callback. */ + ma_device_id* pPlaybackDeviceID; /* The ID of the playback device to use with the default listener. */ +#endif + ma_log* pLog; /* When set to NULL, will use the context's log. */ + ma_uint32 listenerCount; /* Must be between 1 and MA_ENGINE_MAX_LISTENERS. */ + ma_uint32 channels; /* The number of channels to use when mixing and spatializing. When set to 0, will use the native channel count of the device. */ + ma_uint32 sampleRate; /* The sample rate. When set to 0 will use the native channel count of the device. */ + ma_uint32 periodSizeInFrames; /* If set to something other than 0, updates will always be exactly this size. The underlying device may be a different size, but from the perspective of the mixer that won't matter.*/ + ma_uint32 periodSizeInMilliseconds; /* Used if periodSizeInFrames is unset. */ + ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. If set to 0, will use gainSmoothTimeInMilliseconds. */ + ma_uint32 gainSmoothTimeInMilliseconds; /* When set to 0, gainSmoothTimeInFrames will be used. If both are set to 0, a default value will be used. */ + ma_allocation_callbacks allocationCallbacks; + ma_bool32 noAutoStart; /* When set to true, requires an explicit call to ma_engine_start(). This is false by default, meaning the engine will be started automatically in ma_engine_init(). */ + ma_bool32 noDevice; /* When set to true, don't create a default device. ma_engine_read_pcm_frames() can be called manually to read data. */ + ma_mono_expansion_mode monoExpansionMode; /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ + ma_vfs* pResourceManagerVFS; /* A pointer to a pre-allocated VFS object to use with the resource manager. This is ignored if pResourceManager is not NULL. */ +} ma_engine_config; + +MA_API ma_engine_config ma_engine_config_init(void); + + +struct ma_engine +{ + ma_node_graph nodeGraph; /* An engine is a node graph. It should be able to be plugged into any ma_node_graph API (with a cast) which means this must be the first member of this struct. */ +#if !defined(MA_NO_RESOURCE_MANAGER) + ma_resource_manager* pResourceManager; +#endif +#if !defined(MA_NO_DEVICE_IO) + ma_device* pDevice; /* Optionally set via the config, otherwise allocated by the engine in ma_engine_init(). */ +#endif + ma_log* pLog; + ma_uint32 sampleRate; + ma_uint32 listenerCount; + ma_spatializer_listener listeners[MA_ENGINE_MAX_LISTENERS]; + ma_allocation_callbacks allocationCallbacks; + ma_bool8 ownsResourceManager; + ma_bool8 ownsDevice; + ma_spinlock inlinedSoundLock; /* For synchronizing access so the inlined sound list. */ + ma_sound_inlined* pInlinedSoundHead; /* The first inlined sound. Inlined sounds are tracked in a linked list. */ + MA_ATOMIC(4, ma_uint32) inlinedSoundCount; /* The total number of allocated inlined sound objects. Used for debugging. */ + ma_uint32 gainSmoothTimeInFrames; /* The number of frames to interpolate the gain of spatialized sounds across. */ + ma_mono_expansion_mode monoExpansionMode; +}; + +MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine); +MA_API void ma_engine_uninit(ma_engine* pEngine); +MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead); +MA_API ma_node_graph* ma_engine_get_node_graph(ma_engine* pEngine); +#if !defined(MA_NO_RESOURCE_MANAGER) +MA_API ma_resource_manager* ma_engine_get_resource_manager(ma_engine* pEngine); +#endif +MA_API ma_device* ma_engine_get_device(ma_engine* pEngine); +MA_API ma_log* ma_engine_get_log(ma_engine* pEngine); +MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine); +MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine); +MA_API ma_result ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime); +MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine); +MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine); + +MA_API ma_result ma_engine_start(ma_engine* pEngine); +MA_API ma_result ma_engine_stop(ma_engine* pEngine); +MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume); +MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB); + +MA_API ma_uint32 ma_engine_get_listener_count(const ma_engine* pEngine); +MA_API ma_uint32 ma_engine_find_closest_listener(const ma_engine* pEngine, float absolutePosX, float absolutePosY, float absolutePosZ); +MA_API void ma_engine_listener_set_position(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_position(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_direction(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_direction(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_velocity(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_velocity(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_cone(ma_engine* pEngine, ma_uint32 listenerIndex, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 listenerIndex, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_engine_listener_set_world_up(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z); +MA_API ma_vec3f ma_engine_listener_get_world_up(const ma_engine* pEngine, ma_uint32 listenerIndex); +MA_API void ma_engine_listener_set_enabled(ma_engine* pEngine, ma_uint32 listenerIndex, ma_bool32 isEnabled); +MA_API ma_bool32 ma_engine_listener_is_enabled(const ma_engine* pEngine, ma_uint32 listenerIndex); + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_engine_play_sound_ex(ma_engine* pEngine, const char* pFilePath, ma_node* pNode, ma_uint32 nodeInputBusIndex); +MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup); /* Fire and forget. */ +#endif + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound); +MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound); +MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound); +#endif +MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound); +MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound); +MA_API void ma_sound_uninit(ma_sound* pSound); +MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound); +MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound); +MA_API ma_result ma_sound_start(ma_sound* pSound); +MA_API ma_result ma_sound_stop(ma_sound* pSound); +MA_API void ma_sound_set_volume(ma_sound* pSound, float volume); +MA_API float ma_sound_get_volume(const ma_sound* pSound); +MA_API void ma_sound_set_pan(ma_sound* pSound, float pan); +MA_API float ma_sound_get_pan(const ma_sound* pSound); +MA_API void ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode panMode); +MA_API ma_pan_mode ma_sound_get_pan_mode(const ma_sound* pSound); +MA_API void ma_sound_set_pitch(ma_sound* pSound, float pitch); +MA_API float ma_sound_get_pitch(const ma_sound* pSound); +MA_API void ma_sound_set_spatialization_enabled(ma_sound* pSound, ma_bool32 enabled); +MA_API ma_bool32 ma_sound_is_spatialization_enabled(const ma_sound* pSound); +MA_API void ma_sound_set_pinned_listener_index(ma_sound* pSound, ma_uint32 listenerIndex); +MA_API ma_uint32 ma_sound_get_pinned_listener_index(const ma_sound* pSound); +MA_API ma_uint32 ma_sound_get_listener_index(const ma_sound* pSound); +MA_API ma_vec3f ma_sound_get_direction_to_listener(const ma_sound* pSound); +MA_API void ma_sound_set_position(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_position(const ma_sound* pSound); +MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound); +MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z); +MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound); +MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound); +MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning); +MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound); +MA_API void ma_sound_set_rolloff(ma_sound* pSound, float rolloff); +MA_API float ma_sound_get_rolloff(const ma_sound* pSound); +MA_API void ma_sound_set_min_gain(ma_sound* pSound, float minGain); +MA_API float ma_sound_get_min_gain(const ma_sound* pSound); +MA_API void ma_sound_set_max_gain(ma_sound* pSound, float maxGain); +MA_API float ma_sound_get_max_gain(const ma_sound* pSound); +MA_API void ma_sound_set_min_distance(ma_sound* pSound, float minDistance); +MA_API float ma_sound_get_min_distance(const ma_sound* pSound); +MA_API void ma_sound_set_max_distance(ma_sound* pSound, float maxDistance); +MA_API float ma_sound_get_max_distance(const ma_sound* pSound); +MA_API void ma_sound_set_cone(ma_sound* pSound, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_sound_set_doppler_factor(ma_sound* pSound, float dopplerFactor); +MA_API float ma_sound_get_doppler_factor(const ma_sound* pSound); +MA_API void ma_sound_set_directional_attenuation_factor(ma_sound* pSound, float directionalAttenuationFactor); +MA_API float ma_sound_get_directional_attenuation_factor(const ma_sound* pSound); +MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames); +MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds); +MA_API float ma_sound_get_current_fade_volume(ma_sound* pSound); +MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound); +MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound); +MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping); +MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound); +MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound); +MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex); /* Just a wrapper around ma_data_source_seek_to_pcm_frame(). */ +MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap); +MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor); +MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength); +MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor); +MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength); + +MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup); +MA_API void ma_sound_group_uninit(ma_sound_group* pGroup); +MA_API ma_engine* ma_sound_group_get_engine(const ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup); +MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup); +MA_API void ma_sound_group_set_volume(ma_sound_group* pGroup, float volume); +MA_API float ma_sound_group_get_volume(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pan(ma_sound_group* pGroup, float pan); +MA_API float ma_sound_group_get_pan(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pan_mode(ma_sound_group* pGroup, ma_pan_mode panMode); +MA_API ma_pan_mode ma_sound_group_get_pan_mode(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch); +MA_API float ma_sound_group_get_pitch(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_spatialization_enabled(ma_sound_group* pGroup, ma_bool32 enabled); +MA_API ma_bool32 ma_sound_group_is_spatialization_enabled(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_pinned_listener_index(ma_sound_group* pGroup, ma_uint32 listenerIndex); +MA_API ma_uint32 ma_sound_group_get_pinned_listener_index(const ma_sound_group* pGroup); +MA_API ma_uint32 ma_sound_group_get_listener_index(const ma_sound_group* pGroup); +MA_API ma_vec3f ma_sound_group_get_direction_to_listener(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_position(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_position(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z); +MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel); +MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning); +MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_rolloff(ma_sound_group* pGroup, float rolloff); +MA_API float ma_sound_group_get_rolloff(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_min_gain(ma_sound_group* pGroup, float minGain); +MA_API float ma_sound_group_get_min_gain(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_max_gain(ma_sound_group* pGroup, float maxGain); +MA_API float ma_sound_group_get_max_gain(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_min_distance(ma_sound_group* pGroup, float minDistance); +MA_API float ma_sound_group_get_min_distance(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_max_distance(ma_sound_group* pGroup, float maxDistance); +MA_API float ma_sound_group_get_max_distance(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_cone(ma_sound_group* pGroup, float innerAngleInRadians, float outerAngleInRadians, float outerGain); +MA_API void ma_sound_group_get_cone(const ma_sound_group* pGroup, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain); +MA_API void ma_sound_group_set_doppler_factor(ma_sound_group* pGroup, float dopplerFactor); +MA_API float ma_sound_group_get_doppler_factor(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_directional_attenuation_factor(ma_sound_group* pGroup, float directionalAttenuationFactor); +MA_API float ma_sound_group_get_directional_attenuation_factor(const ma_sound_group* pGroup); +MA_API void ma_sound_group_set_fade_in_pcm_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames); +MA_API void ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds); +MA_API float ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup); +MA_API void ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_group_set_start_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API void ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames); +MA_API void ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds); +MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup); +MA_API ma_uint64 ma_sound_group_get_time_in_pcm_frames(const ma_sound_group* pGroup); +#endif /* MA_NO_ENGINE */ + #ifdef __cplusplus } #endif #endif /* miniaudio_h */ +/* +This is for preventing greying out of the implementation section. +*/ +#if defined(Q_CREATOR_RUN) || defined(__INTELLISENSE__) || defined(__CDT_PARSER__) +#define MINIAUDIO_IMPLEMENTATION +#endif /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -6552,6 +11051,9 @@ IMPLEMENTATION #include /* For strcasecmp(). */ #include /* For wcslen(), wcsrtombs() */ #endif +#ifdef _MSC_VER + #include /* For _controlfp_s constants */ +#endif #ifdef MA_WIN32 #include @@ -6560,6 +11062,7 @@ IMPLEMENTATION #include /* For memset() */ #include #include /* select() (used for ma_sleep()). */ +#include #endif #include /* For fstat(), etc. */ @@ -6602,15 +11105,10 @@ IMPLEMENTATION #define MA_X64 #elif defined(__i386) || defined(_M_IX86) #define MA_X86 -#elif defined(__arm__) || defined(_M_ARM) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) #define MA_ARM #endif -/* Cannot currently support AVX-512 if AVX is disabled. */ -#if !defined(MA_NO_AVX512) && defined(MA_NO_AVX2) -#define MA_NO_AVX512 -#endif - /* Intrinsics Support */ #if defined(MA_X64) || defined(MA_X86) #if defined(_MSC_VER) && !defined(__clang__) @@ -6624,9 +11122,6 @@ IMPLEMENTATION #if _MSC_VER >= 1700 && !defined(MA_NO_AVX2) /* 2012 */ #define MA_SUPPORT_AVX2 #endif - #if _MSC_VER >= 1910 && !defined(MA_NO_AVX512) /* 2017 */ - #define MA_SUPPORT_AVX512 - #endif #else /* Assume GNUC-style. */ #if defined(__SSE2__) && !defined(MA_NO_SSE2) @@ -6638,9 +11133,6 @@ IMPLEMENTATION #if defined(__AVX2__) && !defined(MA_NO_AVX2) #define MA_SUPPORT_AVX2 #endif - #if defined(__AVX512F__) && !defined(MA_NO_AVX512) - #define MA_SUPPORT_AVX512 - #endif #endif /* If at this point we still haven't determined compiler support for the intrinsics just fall back to __has_include. */ @@ -6654,14 +11146,9 @@ IMPLEMENTATION #if !defined(MA_SUPPORT_AVX2) && !defined(MA_NO_AVX2) && __has_include() #define MA_SUPPORT_AVX2 #endif - #if !defined(MA_SUPPORT_AVX512) && !defined(MA_NO_AVX512) && __has_include() - #define MA_SUPPORT_AVX512 - #endif #endif - #if defined(MA_SUPPORT_AVX512) - #include /* Not a mistake. Intentionally including instead of because otherwise the compiler will complain. */ - #elif defined(MA_SUPPORT_AVX2) || defined(MA_SUPPORT_AVX) + #if defined(MA_SUPPORT_AVX2) || defined(MA_SUPPORT_AVX) #include #elif defined(MA_SUPPORT_SSE2) #include @@ -6671,16 +11158,6 @@ IMPLEMENTATION #if defined(MA_ARM) #if !defined(MA_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) #define MA_SUPPORT_NEON - #endif - - /* Fall back to looking for the #include file. */ - #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) - #if !defined(MA_SUPPORT_NEON) && !defined(MA_NO_NEON) && __has_include() - #define MA_SUPPORT_NEON - #endif - #endif - - #if defined(MA_SUPPORT_NEON) #include #endif #endif @@ -6689,6 +11166,7 @@ IMPLEMENTATION #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4752) /* found Intel(R) Advanced Vector Extensions; consider using /arch:AVX */ + #pragma warning(disable:4049) /* compiler limit : terminating line number emission */ #endif #if defined(MA_X64) || defined(MA_X86) @@ -6850,41 +11328,6 @@ static MA_INLINE ma_bool32 ma_has_avx2(void) #endif } -static MA_INLINE ma_bool32 ma_has_avx512f(void) -{ -#if defined(MA_SUPPORT_AVX512) - #if (defined(MA_X64) || defined(MA_X86)) && !defined(MA_NO_AVX512) - #if defined(__AVX512F__) - return MA_TRUE; /* If the compiler is allowed to freely generate AVX-512F code we can assume support. */ - #else - /* AVX-512 requires both CPU and OS support. */ - #if defined(MA_NO_CPUID) || defined(MA_NO_XGETBV) - return MA_FALSE; - #else - int info1[4]; - int info7[4]; - ma_cpuid(info1, 1); - ma_cpuid(info7, 7); - if (((info1[2] & (1 << 27)) != 0) && ((info7[1] & (1 << 16)) != 0)) { - ma_uint64 xrc = ma_xgetbv(0); - if ((xrc & 0xE6) == 0xE6) { - return MA_TRUE; - } else { - return MA_FALSE; - } - } else { - return MA_FALSE; - } - #endif - #endif - #else - return MA_FALSE; /* AVX-512F is only supported on x86 and x64 architectures. */ - #endif -#else - return MA_FALSE; /* No compiler support. */ -#endif -} - static MA_INLINE ma_bool32 ma_has_neon(void) { #if defined(MA_SUPPORT_NEON) @@ -6934,7 +11377,7 @@ static MA_INLINE ma_bool32 ma_has_neon(void) #elif defined(_MSC_VER) #define MA_ASSUME(x) __assume(x) #else - #define MA_ASSUME(x) while(0) + #define MA_ASSUME(x) (void)(x) #endif #endif @@ -7033,7 +11476,7 @@ static void ma_sleep__posix(ma_uint32 milliseconds) (void)milliseconds; MA_ASSERT(MA_FALSE); /* The Emscripten build should never sleep. */ #else - #if _POSIX_C_SOURCE >= 199309L + #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L struct timespec ts; ts.tv_sec = milliseconds / 1000; ts.tv_nsec = milliseconds % 1000 * 1000000; @@ -7048,7 +11491,7 @@ static void ma_sleep__posix(ma_uint32 milliseconds) } #endif -static void ma_sleep(ma_uint32 milliseconds) +static MA_INLINE void ma_sleep(ma_uint32 milliseconds) { #ifdef MA_WIN32 ma_sleep__win32(milliseconds); @@ -7077,7 +11520,7 @@ static MA_INLINE void ma_yield() #else __asm__ __volatile__ ("pause"); #endif -#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || (defined(_M_ARM) && _M_ARM >= 7) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) +#elif (defined(__arm__) && defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(_M_ARM64) || (defined(_M_ARM) && _M_ARM >= 7) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6T2__) /* ARM */ #if defined(_MSC_VER) /* Apparently there is a __yield() intrinsic that's compatible with ARM, but I cannot find documentation for it nor can I find where it's declared. */ @@ -7091,6 +11534,96 @@ static MA_INLINE void ma_yield() } +#define MA_MM_DENORMALS_ZERO_MASK 0x0040 +#define MA_MM_FLUSH_ZERO_MASK 0x8000 + +static MA_INLINE unsigned int ma_disable_denormals() +{ + unsigned int prevState; + + #if defined(_MSC_VER) + { + /* + Older versions of Visual Studio don't support the "safe" versions of _controlfp_s(). I don't + know which version of Visual Studio first added support for _controlfp_s(), but I do know + that VC6 lacks support. _MSC_VER = 1200 is VC6, but if you get compilation errors on older + versions of Visual Studio, let me know and I'll make the necessary adjustment. + */ + #if _MSC_VER <= 1200 + { + prevState = _statusfp(); + _controlfp(prevState | _DN_FLUSH, _MCW_DN); + } + #else + { + unsigned int unused; + _controlfp_s(&prevState, 0, 0); + _controlfp_s(&unused, prevState | _DN_FLUSH, _MCW_DN); + } + #endif + } + #elif defined(MA_X86) || defined(MA_X64) + { + #if defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + { + prevState = _mm_getcsr(); + _mm_setcsr(prevState | MA_MM_DENORMALS_ZERO_MASK | MA_MM_FLUSH_ZERO_MASK); + } + #else + { + /* x88/64, but no support for _mm_getcsr()/_mm_setcsr(). May need to fall back to inlined assembly here. */ + prevState = 0; + } + #endif + } + #else + { + /* Unknown or unsupported architecture. No-op. */ + prevState = 0; + } + #endif + + return prevState; +} + +static MA_INLINE void ma_restore_denormals(unsigned int prevState) +{ + #if defined(_MSC_VER) + { + /* Older versions of Visual Studio do not support _controlfp_s(). See ma_disable_denormals(). */ + #if _MSC_VER <= 1200 + { + _controlfp(prevState, _MCW_DN); + } + #else + { + unsigned int unused; + _controlfp_s(&unused, prevState, _MCW_DN); + } + #endif + } + #elif defined(MA_X86) || defined(MA_X64) + { + #if defined(__SSE2__) && !(defined(__TINYC__) || defined(__WATCOMC__)) /* <-- Add compilers that lack support for _mm_getcsr() and _mm_setcsr() to this list. */ + { + _mm_setcsr(prevState); + } + #else + { + /* x88/64, but no support for _mm_getcsr()/_mm_setcsr(). May need to fall back to inlined assembly here. */ + (void)prevState; + } + #endif + } + #else + { + /* Unknown or unsupported architecture. No-op. */ + (void)prevState; + } + #endif +} + + #ifndef MA_COINIT_VALUE #define MA_COINIT_VALUE 0 /* 0 = COINIT_MULTITHREADED */ @@ -7344,11 +11877,21 @@ static MA_INLINE double ma_sqrtd(double x) } +static MA_INLINE float ma_sinf(float x) +{ + return (float)ma_sind((float)x); +} + static MA_INLINE double ma_cosd(double x) { return ma_sind((MA_PI_D*0.5) - x); } +static MA_INLINE float ma_cosf(float x) +{ + return (float)ma_cosd((float)x); +} + static MA_INLINE double ma_log10d(double x) { return ma_logd(x) * 0.43429448190325182765; @@ -7684,6 +12227,10 @@ MA_API int ma_strappend(char* dst, size_t dstSize, const char* srcA, const char* MA_API char* ma_copy_string(const char* src, const ma_allocation_callbacks* pAllocationCallbacks) { + if (src == NULL) { + return NULL; + } + size_t sz = strlen(src)+1; char* dst = (char*)ma_malloc(sz, pAllocationCallbacks); if (dst == NULL) { @@ -8367,76 +12914,6 @@ static void ma__free_default(void* p, void* pUserData) MA_FREE(p); } - -static void* ma__malloc_from_callbacks(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks == NULL) { - return NULL; - } - - if (pAllocationCallbacks->onMalloc != NULL) { - return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); - } - - /* Try using realloc(). */ - if (pAllocationCallbacks->onRealloc != NULL) { - return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); - } - - return NULL; -} - -static void* ma__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (pAllocationCallbacks == NULL) { - return NULL; - } - - if (pAllocationCallbacks->onRealloc != NULL) { - return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); - } - - /* Try emulating realloc() in terms of malloc()/free(). */ - if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { - void* p2; - - p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); - if (p2 == NULL) { - return NULL; - } - - if (p != NULL) { - MA_COPY_MEMORY(p2, p, szOld); - pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); - } - - return p2; - } - - return NULL; -} - -static MA_INLINE void* ma__calloc_from_callbacks(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) -{ - void* p = ma__malloc_from_callbacks(sz, pAllocationCallbacks); - if (p != NULL) { - MA_ZERO_MEMORY(p, sz); - } - - return p; -} - -static void ma__free_from_callbacks(void* p, const ma_allocation_callbacks* pAllocationCallbacks) -{ - if (p == NULL || pAllocationCallbacks == NULL) { - return; - } - - if (pAllocationCallbacks->onFree != NULL) { - pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); - } -} - static ma_allocation_callbacks ma_allocation_callbacks_init_default(void) { ma_allocation_callbacks callbacks; @@ -8547,7 +13024,7 @@ MA_API ma_result ma_log_init(const ma_allocation_callbacks* pAllocationCallbacks } } #endif - + /* If we're using debug output, enable it. */ #if defined(MA_DEBUG_OUTPUT) { @@ -8644,15 +13121,6 @@ MA_API ma_result ma_log_post(ma_log* pLog, ma_uint32 level, const char* pMessage return MA_INVALID_ARGS; } - /* If it's a debug log, ignore it unless MA_DEBUG_OUTPUT is enabled. */ - #if !defined(MA_DEBUG_OUTPUT) - { - if (level == MA_LOG_LEVEL_DEBUG) { - return MA_INVALID_ARGS; /* Don't post debug messages if debug output is disabled. */ - } - } - #endif - ma_log_lock(pLog); { ma_uint32 iLog; @@ -8719,18 +13187,6 @@ MA_API ma_result ma_log_postv(ma_log* pLog, ma_uint32 level, const char* pFormat return MA_INVALID_ARGS; } - /* - If it's a debug log, ignore it unless MA_DEBUG_OUTPUT is enabled. Do this before generating the - formatted message string so that we don't waste time only to have ma_log_post() reject it. - */ - #if !defined(MA_DEBUG_OUTPUT) - { - if (level == MA_LOG_LEVEL_DEBUG) { - return MA_INVALID_ARGS; /* Don't post debug messages if debug output is disabled. */ - } - } - #endif - #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || ((!defined(_MSC_VER) || _MSC_VER >= 1900) && !defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) { ma_result result; @@ -8840,18 +13296,6 @@ MA_API ma_result ma_log_postf(ma_log* pLog, ma_uint32 level, const char* pFormat return MA_INVALID_ARGS; } - /* - If it's a debug log, ignore it unless MA_DEBUG_OUTPUT is enabled. Do this before generating the - formatted message string so that we don't waste time only to have ma_log_post() reject it. - */ - #if !defined(MA_DEBUG_OUTPUT) - { - if (level == MA_LOG_LEVEL_DEBUG) { - return MA_INVALID_ARGS; /* Don't post debug messages if debug output is disabled. */ - } - } - #endif - va_start(args, pFormat); { result = ma_log_postv(pLog, level, pFormat, args); @@ -8863,8 +13307,32 @@ MA_API ma_result ma_log_postf(ma_log* pLog, ma_uint32 level, const char* pFormat +static MA_INLINE ma_uint8 ma_clip_u8(ma_int32 x) +{ + return (ma_uint8)(ma_clamp(x, -128, 127) + 128); +} + +static MA_INLINE ma_int16 ma_clip_s16(ma_int32 x) +{ + return (ma_int16)ma_clamp(x, -32768, 32767); +} + +static MA_INLINE ma_int64 ma_clip_s24(ma_int64 x) +{ + return (ma_int64)ma_clamp(x, -8388608, 8388607); +} + +static MA_INLINE ma_int32 ma_clip_s32(ma_int64 x) +{ + /* This dance is to silence warnings with -std=c89. A good compiler should be able to optimize this away. */ + ma_int64 clipMin; + ma_int64 clipMax; + clipMin = -((ma_int64)2147483647 + 1); + clipMax = (ma_int64)2147483647; + + return (ma_int32)ma_clamp(x, clipMin, clipMax); +} -/* Clamps an f32 sample to -1..1 */ static MA_INLINE float ma_clip_f32(float x) { if (x < -1) return -1; @@ -8872,6 +13340,7 @@ static MA_INLINE float ma_clip_f32(float x) return x; } + static MA_INLINE float ma_mix_f32(float x, float y, float a) { return x*(1-a) + y*a; @@ -8896,12 +13365,6 @@ static MA_INLINE __m256 ma_mix_f32_fast__avx2(__m256 x, __m256 y, __m256 a) return _mm256_add_ps(x, _mm256_mul_ps(_mm256_sub_ps(y, x), a)); } #endif -#if defined(MA_SUPPORT_AVX512) -static MA_INLINE __m512 ma_mix_f32_fast__avx512(__m512 x, __m512 y, __m512 a) -{ - return _mm512_add_ps(x, _mm512_mul_ps(_mm512_sub_ps(y, x), a)); -} -#endif #if defined(MA_SUPPORT_NEON) static MA_INLINE float32x4_t ma_mix_f32_fast__neon(float32x4_t x, float32x4_t y, float32x4_t a) { @@ -8944,6 +13407,27 @@ static MA_INLINE ma_uint32 ma_gcf_u32(ma_uint32 a, ma_uint32 b) } +static ma_uint32 ma_ffs_32(ma_uint32 x) +{ + ma_uint32 i; + + /* Just a naive implementation just to get things working for now. Will optimize this later. */ + for (i = 0; i < 32; i += 1) { + if ((x & (1 << i)) != 0) { + return i; + } + } + + return i; +} + +static MA_INLINE ma_int16 ma_float_to_fixed_16(float x) +{ + return (ma_int16)(x * (1 << 8)); +} + + + /* Random Number Generation @@ -9104,7 +13588,7 @@ typedef signed short c89atomic_int16; typedef unsigned short c89atomic_uint16; typedef signed int c89atomic_int32; typedef unsigned int c89atomic_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 c89atomic_int64; typedef unsigned __int64 c89atomic_uint64; #else @@ -9153,7 +13637,7 @@ typedef unsigned char c89atomic_bool; #define C89ATOMIC_X64 #elif defined(__i386) || defined(_M_IX86) #define C89ATOMIC_X86 -#elif defined(__arm__) || defined(_M_ARM) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) #define C89ATOMIC_ARM #endif #if defined(_MSC_VER) @@ -9262,7 +13746,7 @@ typedef unsigned char c89atomic_bool; #define c89atomic_compare_and_swap_32(dst, expected, desired) (c89atomic_uint32)_InterlockedCompareExchange((volatile long*)dst, (long)desired, (long)expected) #endif #if defined(C89ATOMIC_HAS_64) - #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile long long*)dst, (long long)desired, (long long)expected) + #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile c89atomic_int64*)dst, (c89atomic_int64)desired, (c89atomic_int64)expected) #endif #endif #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY) @@ -10449,11 +14933,11 @@ typedef unsigned char c89atomic_bool; { return (void*)c89atomic_exchange_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64)src, order); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_strong_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_weak_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder); } @@ -10482,7 +14966,7 @@ typedef unsigned char c89atomic_bool; { return c89atomic_compare_exchange_strong_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder); } - static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) { return c89atomic_compare_exchange_weak_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder); } @@ -10498,8 +14982,8 @@ typedef unsigned char c89atomic_bool; #define c89atomic_store_ptr(dst, src) c89atomic_store_explicit_ptr((volatile void**)dst, (void*)src, c89atomic_memory_order_seq_cst) #define c89atomic_load_ptr(ptr) c89atomic_load_explicit_ptr((volatile void**)ptr, c89atomic_memory_order_seq_cst) #define c89atomic_exchange_ptr(dst, src) c89atomic_exchange_explicit_ptr((volatile void**)dst, (void*)src, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_strong_ptr(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (volatile void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_weak_ptr(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (volatile void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_ptr(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_ptr(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (void**)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_8( ptr) c89atomic_test_and_set_explicit_8( ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_16(ptr) c89atomic_test_and_set_explicit_16(ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_32(ptr) c89atomic_test_and_set_explicit_32(ptr, c89atomic_memory_order_seq_cst) @@ -10672,16 +15156,16 @@ static C89ATOMIC_INLINE void c89atomic_store_explicit_f64(volatile double* dst, x.f = src; c89atomic_store_explicit_64((volatile c89atomic_uint64*)dst, x.i, order); } -static C89ATOMIC_INLINE float c89atomic_load_explicit_f32(volatile float* ptr, c89atomic_memory_order order) +static C89ATOMIC_INLINE float c89atomic_load_explicit_f32(volatile const float* ptr, c89atomic_memory_order order) { c89atomic_if32 r; - r.i = c89atomic_load_explicit_32((volatile c89atomic_uint32*)ptr, order); + r.i = c89atomic_load_explicit_32((volatile const c89atomic_uint32*)ptr, order); return r.f; } -static C89ATOMIC_INLINE double c89atomic_load_explicit_f64(volatile double* ptr, c89atomic_memory_order order) +static C89ATOMIC_INLINE double c89atomic_load_explicit_f64(volatile const double* ptr, c89atomic_memory_order order) { c89atomic_if64 r; - r.i = c89atomic_load_explicit_64((volatile c89atomic_uint64*)ptr, order); + r.i = c89atomic_load_explicit_64((volatile const c89atomic_uint64*)ptr, order); return r.f; } static C89ATOMIC_INLINE float c89atomic_exchange_explicit_f32(volatile float* dst, float src, c89atomic_memory_order order) @@ -10733,26 +15217,29 @@ static C89ATOMIC_INLINE void c89atomic_spinlock_unlock(volatile c89atomic_spinlo MA_API ma_uint64 ma_calculate_frame_count_after_resampling(ma_uint32 sampleRateOut, ma_uint32 sampleRateIn, ma_uint64 frameCountIn) { - /* For robustness we're going to use a resampler object to calculate this since that already has a way of calculating this. */ - ma_result result; - ma_uint64 frameCountOut; - ma_resampler_config config; - ma_resampler resampler; + /* This is based on the calculation in ma_linear_resampler_get_expected_output_frame_count(). */ + ma_uint64 outputFrameCount; + ma_uint64 preliminaryInputFrameCountFromFrac; + ma_uint64 preliminaryInputFrameCount; + + if (sampleRateIn == 0 || sampleRateOut == 0 || frameCountIn == 0) { + return 0; + } if (sampleRateOut == sampleRateIn) { return frameCountIn; } - config = ma_resampler_config_init(ma_format_s16, 1, sampleRateIn, sampleRateOut, ma_resample_algorithm_linear); - result = ma_resampler_init(&config, &resampler); - if (result != MA_SUCCESS) { - return 0; + outputFrameCount = (frameCountIn * sampleRateOut) / sampleRateIn; + + preliminaryInputFrameCountFromFrac = (outputFrameCount * (sampleRateIn / sampleRateOut)) / sampleRateOut; + preliminaryInputFrameCount = (outputFrameCount * (sampleRateIn % sampleRateOut)) + preliminaryInputFrameCountFromFrac; + + if (preliminaryInputFrameCount <= frameCountIn) { + outputFrameCount += 1; } - frameCountOut = ma_resampler_get_expected_output_frame_count(&resampler, frameCountIn); - - ma_resampler_uninit(&resampler); - return frameCountOut; + return outputFrameCount; } #ifndef MA_DATA_CONVERTER_STACK_BUFFER_SIZE @@ -10790,16 +15277,6 @@ static ma_result ma_result_from_GetLastError(DWORD error) Threading *******************************************************************************/ -#ifndef MA_NO_THREADING -#ifdef MA_WIN32 - #define MA_THREADCALL WINAPI - typedef unsigned long ma_thread_result; -#else - #define MA_THREADCALL - typedef void* ma_thread_result; -#endif -typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); - static MA_INLINE ma_result ma_spinlock_lock_ex(volatile ma_spinlock* pSpinlock, ma_bool32 yield) { if (pSpinlock == NULL) { @@ -10841,6 +15318,17 @@ MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock) return MA_SUCCESS; } + +#ifndef MA_NO_THREADING +#ifdef MA_WIN32 + #define MA_THREADCALL WINAPI + typedef unsigned long ma_thread_result; +#else + #define MA_THREADCALL + typedef void* ma_thread_result; +#endif +typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); + #ifdef MA_WIN32 static int ma_thread_priority_to_win32(ma_thread_priority priority) { @@ -11048,7 +15536,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority (void)stackSize; #endif - result = pthread_create(pThread, pAttr, entryProc, pData); + result = pthread_create((pthread_t*)pThread, pAttr, entryProc, pData); /* The thread attributes object is no longer required. */ if (pAttr != NULL) { @@ -11064,8 +15552,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority static void ma_thread_wait__posix(ma_thread* pThread) { - pthread_join(*pThread, NULL); - pthread_detach(*pThread); + pthread_join((pthread_t)*pThread, NULL); } @@ -11099,14 +15586,14 @@ static ma_result ma_event_init__posix(ma_event* pEvent) { int result; - result = pthread_mutex_init(&pEvent->lock, NULL); + result = pthread_mutex_init((pthread_mutex_t*)&pEvent->lock, NULL); if (result != 0) { return ma_result_from_errno(result); } - result = pthread_cond_init(&pEvent->cond, NULL); + result = pthread_cond_init((pthread_cond_t*)&pEvent->cond, NULL); if (result != 0) { - pthread_mutex_destroy(&pEvent->lock); + pthread_mutex_destroy((pthread_mutex_t*)&pEvent->lock); return ma_result_from_errno(result); } @@ -11116,32 +15603,32 @@ static ma_result ma_event_init__posix(ma_event* pEvent) static void ma_event_uninit__posix(ma_event* pEvent) { - pthread_cond_destroy(&pEvent->cond); - pthread_mutex_destroy(&pEvent->lock); + pthread_cond_destroy((pthread_cond_t*)&pEvent->cond); + pthread_mutex_destroy((pthread_mutex_t*)&pEvent->lock); } static ma_result ma_event_wait__posix(ma_event* pEvent) { - pthread_mutex_lock(&pEvent->lock); + pthread_mutex_lock((pthread_mutex_t*)&pEvent->lock); { while (pEvent->value == 0) { - pthread_cond_wait(&pEvent->cond, &pEvent->lock); + pthread_cond_wait((pthread_cond_t*)&pEvent->cond, (pthread_mutex_t*)&pEvent->lock); } pEvent->value = 0; /* Auto-reset. */ } - pthread_mutex_unlock(&pEvent->lock); + pthread_mutex_unlock((pthread_mutex_t*)&pEvent->lock); return MA_SUCCESS; } static ma_result ma_event_signal__posix(ma_event* pEvent) { - pthread_mutex_lock(&pEvent->lock); + pthread_mutex_lock((pthread_mutex_t*)&pEvent->lock); { pEvent->value = 1; - pthread_cond_signal(&pEvent->cond); + pthread_cond_signal((pthread_cond_t*)&pEvent->cond); } - pthread_mutex_unlock(&pEvent->lock); + pthread_mutex_unlock((pthread_mutex_t*)&pEvent->lock); return MA_SUCCESS; } @@ -11157,14 +15644,14 @@ static ma_result ma_semaphore_init__posix(int initialValue, ma_semaphore* pSemap pSemaphore->value = initialValue; - result = pthread_mutex_init(&pSemaphore->lock, NULL); + result = pthread_mutex_init((pthread_mutex_t*)&pSemaphore->lock, NULL); if (result != 0) { return ma_result_from_errno(result); /* Failed to create mutex. */ } - result = pthread_cond_init(&pSemaphore->cond, NULL); + result = pthread_cond_init((pthread_cond_t*)&pSemaphore->cond, NULL); if (result != 0) { - pthread_mutex_destroy(&pSemaphore->lock); + pthread_mutex_destroy((pthread_mutex_t*)&pSemaphore->lock); return ma_result_from_errno(result); /* Failed to create condition variable. */ } @@ -11177,8 +15664,8 @@ static void ma_semaphore_uninit__posix(ma_semaphore* pSemaphore) return; } - pthread_cond_destroy(&pSemaphore->cond); - pthread_mutex_destroy(&pSemaphore->lock); + pthread_cond_destroy((pthread_cond_t*)&pSemaphore->cond); + pthread_mutex_destroy((pthread_mutex_t*)&pSemaphore->lock); } static ma_result ma_semaphore_wait__posix(ma_semaphore* pSemaphore) @@ -11187,16 +15674,16 @@ static ma_result ma_semaphore_wait__posix(ma_semaphore* pSemaphore) return MA_INVALID_ARGS; } - pthread_mutex_lock(&pSemaphore->lock); + pthread_mutex_lock((pthread_mutex_t*)&pSemaphore->lock); { /* We need to wait on a condition variable before escaping. We can't return from this function until the semaphore has been signaled. */ while (pSemaphore->value == 0) { - pthread_cond_wait(&pSemaphore->cond, &pSemaphore->lock); + pthread_cond_wait((pthread_cond_t*)&pSemaphore->cond, (pthread_mutex_t*)&pSemaphore->lock); } pSemaphore->value -= 1; } - pthread_mutex_unlock(&pSemaphore->lock); + pthread_mutex_unlock((pthread_mutex_t*)&pSemaphore->lock); return MA_SUCCESS; } @@ -11207,12 +15694,12 @@ static ma_result ma_semaphore_release__posix(ma_semaphore* pSemaphore) return MA_INVALID_ARGS; } - pthread_mutex_lock(&pSemaphore->lock); + pthread_mutex_lock((pthread_mutex_t*)&pSemaphore->lock); { pSemaphore->value += 1; - pthread_cond_signal(&pSemaphore->cond); + pthread_cond_signal((pthread_cond_t*)&pSemaphore->cond); } - pthread_mutex_unlock(&pSemaphore->lock); + pthread_mutex_unlock((pthread_mutex_t*)&pSemaphore->lock); return MA_SUCCESS; } @@ -11257,7 +15744,7 @@ static ma_result ma_thread_create(ma_thread* pThread, ma_thread_priority priorit ma_thread_proxy_data* pProxyData; if (pThread == NULL || entryProc == NULL) { - return MA_FALSE; + return MA_INVALID_ARGS; } pProxyData = (ma_thread_proxy_data*)ma_malloc(sizeof(*pProxyData), pAllocationCallbacks); /* Will be freed by the proxy entry proc. */ @@ -11386,14 +15873,14 @@ static ma_result ma_event_alloc_and_init(ma_event** ppEvent, ma_allocation_callb *ppEvent = NULL; - pEvent = ma_malloc(sizeof(*pEvent), pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + pEvent = ma_malloc(sizeof(*pEvent), pAllocationCallbacks); if (pEvent == NULL) { return MA_OUT_OF_MEMORY; } result = ma_event_init(pEvent); if (result != MA_SUCCESS) { - ma_free(pEvent, pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + ma_free(pEvent, pAllocationCallbacks); return result; } @@ -11424,7 +15911,7 @@ static void ma_event_uninit_and_free(ma_event* pEvent, ma_allocation_callbacks* } ma_event_uninit(pEvent); - ma_free(pEvent, pAllocationCallbacks/*, MA_ALLOCATION_TYPE_EVENT*/); + ma_free(pEvent, pAllocationCallbacks); } #endif @@ -11527,6 +16014,1025 @@ MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore) +#define MA_FENCE_COUNTER_MAX 0x7FFFFFFF + +MA_API ma_result ma_fence_init(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pFence); + pFence->counter = 0; + + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_init(&pFence->e); + if (result != MA_SUCCESS) { + return result; + } + } + #endif + + return MA_SUCCESS; +} + +MA_API void ma_fence_uninit(ma_fence* pFence) +{ + if (pFence == NULL) { + return; + } + + #ifndef MA_NO_THREADING + { + ma_event_uninit(&pFence->e); + } + #endif + + MA_ZERO_OBJECT(pFence); +} + +MA_API ma_result ma_fence_acquire(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 oldCounter = c89atomic_load_32(&pFence->counter); + ma_uint32 newCounter = oldCounter + 1; + + /* Make sure we're not about to exceed our maximum value. */ + if (newCounter > MA_FENCE_COUNTER_MAX) { + MA_ASSERT(MA_FALSE); + return MA_OUT_OF_RANGE; + } + + if (c89atomic_compare_exchange_weak_32(&pFence->counter, &oldCounter, newCounter)) { + return MA_SUCCESS; + } else { + if (oldCounter == MA_FENCE_COUNTER_MAX) { + MA_ASSERT(MA_FALSE); + return MA_OUT_OF_RANGE; /* The other thread took the last available slot. Abort. */ + } + } + } + + /* Should never get here. */ + /*return MA_SUCCESS;*/ +} + +MA_API ma_result ma_fence_release(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 oldCounter = c89atomic_load_32(&pFence->counter); + ma_uint32 newCounter = oldCounter - 1; + + if (oldCounter == 0) { + MA_ASSERT(MA_FALSE); + return MA_INVALID_OPERATION; /* Acquire/release mismatch. */ + } + + if (c89atomic_compare_exchange_weak_32(&pFence->counter, &oldCounter, newCounter)) { + #ifndef MA_NO_THREADING + { + if (newCounter == 0) { + ma_event_signal(&pFence->e); /* <-- ma_fence_wait() will be waiting on this. */ + } + } + #endif + + return MA_SUCCESS; + } else { + if (oldCounter == 0) { + MA_ASSERT(MA_FALSE); + return MA_INVALID_OPERATION; /* Another thread has taken the 0 slot. Acquire/release mismatch. */ + } + } + } + + /* Should never get here. */ + /*return MA_SUCCESS;*/ +} + +MA_API ma_result ma_fence_wait(ma_fence* pFence) +{ + if (pFence == NULL) { + return MA_INVALID_ARGS; + } + + for (;;) { + ma_uint32 counter; + + counter = c89atomic_load_32(&pFence->counter); + if (counter == 0) { + /* + Counter has hit zero. By the time we get here some other thread may have acquired the + fence again, but that is where the caller needs to take care with how they se the fence. + */ + return MA_SUCCESS; + } + + /* Getting here means the counter is > 0. We'll need to wait for something to happen. */ + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_wait(&pFence->e); + if (result != MA_SUCCESS) { + return result; + } + } + #endif + } + + /* Should never get here. */ + /*return MA_INVALID_OPERATION;*/ +} + + +MA_API ma_result ma_async_notification_signal(ma_async_notification* pNotification) +{ + ma_async_notification_callbacks* pNotificationCallbacks = (ma_async_notification_callbacks*)pNotification; + + if (pNotification == NULL) { + return MA_INVALID_ARGS; + } + + if (pNotificationCallbacks->onSignal == NULL) { + return MA_NOT_IMPLEMENTED; + } + + pNotificationCallbacks->onSignal(pNotification); + return MA_INVALID_ARGS; +} + + +static void ma_async_notification_poll__on_signal(ma_async_notification* pNotification) +{ + ((ma_async_notification_poll*)pNotification)->signalled = MA_TRUE; +} + +MA_API ma_result ma_async_notification_poll_init(ma_async_notification_poll* pNotificationPoll) +{ + if (pNotificationPoll == NULL) { + return MA_INVALID_ARGS; + } + + pNotificationPoll->cb.onSignal = ma_async_notification_poll__on_signal; + pNotificationPoll->signalled = MA_FALSE; + + return MA_SUCCESS; +} + +MA_API ma_bool32 ma_async_notification_poll_is_signalled(const ma_async_notification_poll* pNotificationPoll) +{ + if (pNotificationPoll == NULL) { + return MA_FALSE; + } + + return pNotificationPoll->signalled; +} + + +static void ma_async_notification_event__on_signal(ma_async_notification* pNotification) +{ + ma_async_notification_event_signal((ma_async_notification_event*)pNotification); +} + +MA_API ma_result ma_async_notification_event_init(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + pNotificationEvent->cb.onSignal = ma_async_notification_event__on_signal; + + #ifndef MA_NO_THREADING + { + ma_result result; + + result = ma_event_init(&pNotificationEvent->e); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_uninit(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + ma_event_uninit(&pNotificationEvent->e); + return MA_SUCCESS; + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_wait(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + return ma_event_wait(&pNotificationEvent->e); + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + +MA_API ma_result ma_async_notification_event_signal(ma_async_notification_event* pNotificationEvent) +{ + if (pNotificationEvent == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + return ma_event_signal(&pNotificationEvent->e); + } + #else + { + return MA_NOT_IMPLEMENTED; /* Threading is disabled. */ + } + #endif +} + + + +/************************************************************************************************************************************************************ + +Job Queue + +************************************************************************************************************************************************************/ +MA_API ma_slot_allocator_config ma_slot_allocator_config_init(ma_uint32 capacity) +{ + ma_slot_allocator_config config; + + MA_ZERO_OBJECT(&config); + config.capacity = capacity; + + return config; +} + + +static MA_INLINE ma_uint32 ma_slot_allocator_calculate_group_capacity(ma_uint32 slotCapacity) +{ + ma_uint32 cap = slotCapacity / 32; + if ((slotCapacity % 32) != 0) { + cap += 1; + } + + return cap; +} + +static MA_INLINE ma_uint32 ma_slot_allocator_group_capacity(const ma_slot_allocator* pAllocator) +{ + return ma_slot_allocator_calculate_group_capacity(pAllocator->capacity); +} + + +typedef struct +{ + size_t sizeInBytes; + size_t groupsOffset; + size_t slotsOffset; +} ma_slot_allocator_heap_layout; + +static ma_result ma_slot_allocator_get_heap_layout(const ma_slot_allocator_config* pConfig, ma_slot_allocator_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->capacity == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Groups. */ + pHeapLayout->groupsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(ma_slot_allocator_calculate_group_capacity(pConfig->capacity) * sizeof(ma_slot_allocator_group)); + + /* Slots. */ + pHeapLayout->slotsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(pConfig->capacity * sizeof(ma_uint32)); + + return MA_SUCCESS; +} + +MA_API ma_result ma_slot_allocator_get_heap_size(const ma_slot_allocator_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_slot_allocator_heap_layout layout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_slot_allocator_get_heap_layout(pConfig, &layout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = layout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_slot_allocator_init_preallocated(const ma_slot_allocator_config* pConfig, void* pHeap, ma_slot_allocator* pAllocator) +{ + ma_result result; + ma_slot_allocator_heap_layout heapLayout; + + if (pAllocator == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pAllocator); + + if (pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_slot_allocator_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pAllocator->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pAllocator->pGroups = (ma_slot_allocator_group*)ma_offset_ptr(pHeap, heapLayout.groupsOffset); + pAllocator->pSlots = (ma_uint32*)ma_offset_ptr(pHeap, heapLayout.slotsOffset); + pAllocator->capacity = pConfig->capacity; + + return MA_SUCCESS; +} + +MA_API ma_result ma_slot_allocator_init(const ma_slot_allocator_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_slot_allocator* pAllocator) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_slot_allocator_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap allocation. */ + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_slot_allocator_init_preallocated(pConfig, pHeap, pAllocator); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pAllocator->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_slot_allocator_uninit(ma_slot_allocator* pAllocator, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocator == NULL) { + return; + } + + if (pAllocator->_ownsHeap) { + ma_free(pAllocator->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_result ma_slot_allocator_alloc(ma_slot_allocator* pAllocator, ma_uint64* pSlot) +{ + ma_uint32 iAttempt; + const ma_uint32 maxAttempts = 2; /* The number of iterations to perform until returning MA_OUT_OF_MEMORY if no slots can be found. */ + + if (pAllocator == NULL || pSlot == NULL) { + return MA_INVALID_ARGS; + } + + for (iAttempt = 0; iAttempt < maxAttempts; iAttempt += 1) { + /* We need to acquire a suitable bitfield first. This is a bitfield that's got an available slot within it. */ + ma_uint32 iGroup; + for (iGroup = 0; iGroup < ma_slot_allocator_group_capacity(pAllocator); iGroup += 1) { + /* CAS */ + for (;;) { + ma_uint32 oldBitfield; + ma_uint32 newBitfield; + ma_uint32 bitOffset; + + oldBitfield = c89atomic_load_32(&pAllocator->pGroups[iGroup].bitfield); /* <-- This copy must happen. The compiler must not optimize this away. */ + + /* Fast check to see if anything is available. */ + if (oldBitfield == 0xFFFFFFFF) { + break; /* No available bits in this bitfield. */ + } + + bitOffset = ma_ffs_32(~oldBitfield); + MA_ASSERT(bitOffset < 32); + + newBitfield = oldBitfield | (1 << bitOffset); + + if (c89atomic_compare_and_swap_32(&pAllocator->pGroups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) { + ma_uint32 slotIndex; + + /* Increment the counter as soon as possible to have other threads report out-of-memory sooner than later. */ + c89atomic_fetch_add_32(&pAllocator->count, 1); + + /* The slot index is required for constructing the output value. */ + slotIndex = (iGroup << 5) + bitOffset; /* iGroup << 5 = iGroup * 32 */ + if (slotIndex >= pAllocator->capacity) { + return MA_OUT_OF_MEMORY; + } + + /* Increment the reference count before constructing the output value. */ + pAllocator->pSlots[slotIndex] += 1; + + /* Construct the output value. */ + *pSlot = (((ma_uint64)pAllocator->pSlots[slotIndex] << 32) | slotIndex); + + return MA_SUCCESS; + } + } + } + + /* We weren't able to find a slot. If it's because we've reached our capacity we need to return MA_OUT_OF_MEMORY. Otherwise we need to do another iteration and try again. */ + if (pAllocator->count < pAllocator->capacity) { + ma_yield(); + } else { + return MA_OUT_OF_MEMORY; + } + } + + /* We couldn't find a slot within the maximum number of attempts. */ + return MA_OUT_OF_MEMORY; +} + +MA_API ma_result ma_slot_allocator_free(ma_slot_allocator* pAllocator, ma_uint64 slot) +{ + ma_uint32 iGroup; + ma_uint32 iBit; + + if (pAllocator == NULL) { + return MA_INVALID_ARGS; + } + + iGroup = (ma_uint32)((slot & 0xFFFFFFFF) >> 5); /* slot / 32 */ + iBit = (ma_uint32)((slot & 0xFFFFFFFF) & 31); /* slot % 32 */ + + if (iGroup >= ma_slot_allocator_group_capacity(pAllocator)) { + return MA_INVALID_ARGS; + } + + MA_ASSERT(iBit < 32); /* This must be true due to the logic we used to actually calculate it. */ + + while (c89atomic_load_32(&pAllocator->count) > 0) { + /* CAS */ + ma_uint32 oldBitfield; + ma_uint32 newBitfield; + + oldBitfield = c89atomic_load_32(&pAllocator->pGroups[iGroup].bitfield); /* <-- This copy must happen. The compiler must not optimize this away. */ + newBitfield = oldBitfield & ~(1 << iBit); + + /* Debugging for checking for double-frees. */ + #if defined(MA_DEBUG_OUTPUT) + { + if ((oldBitfield & (1 << iBit)) == 0) { + MA_ASSERT(MA_FALSE); /* Double free detected.*/ + } + } + #endif + + if (c89atomic_compare_and_swap_32(&pAllocator->pGroups[iGroup].bitfield, oldBitfield, newBitfield) == oldBitfield) { + c89atomic_fetch_sub_32(&pAllocator->count, 1); + return MA_SUCCESS; + } + } + + /* Getting here means there are no allocations available for freeing. */ + return MA_INVALID_OPERATION; +} + + +#define MA_JOB_ID_NONE ~((ma_uint64)0) +#define MA_JOB_SLOT_NONE (ma_uint16)(~0) + +static MA_INLINE ma_uint32 ma_job_extract_refcount(ma_uint64 toc) +{ + return (ma_uint32)(toc >> 32); +} + +static MA_INLINE ma_uint16 ma_job_extract_slot(ma_uint64 toc) +{ + return (ma_uint16)(toc & 0x0000FFFF); +} + +static MA_INLINE ma_uint16 ma_job_extract_code(ma_uint64 toc) +{ + return (ma_uint16)((toc & 0xFFFF0000) >> 16); +} + +static MA_INLINE ma_uint64 ma_job_toc_to_allocation(ma_uint64 toc) +{ + return ((ma_uint64)ma_job_extract_refcount(toc) << 32) | (ma_uint64)ma_job_extract_slot(toc); +} + +static MA_INLINE ma_uint64 ma_job_set_refcount(ma_uint64 toc, ma_uint32 refcount) +{ + /* Clear the reference count first. */ + toc = toc & ~((ma_uint64)0xFFFFFFFF << 32); + toc = toc | ((ma_uint64)refcount << 32); + + return toc; +} + + +MA_API ma_job ma_job_init(ma_uint16 code) +{ + ma_job job; + + MA_ZERO_OBJECT(&job); + job.toc.breakup.code = code; + job.toc.breakup.slot = MA_JOB_SLOT_NONE; /* Temp value. Will be allocated when posted to a queue. */ + job.next = MA_JOB_ID_NONE; + + return job; +} + + +static ma_result ma_job_process__noop(ma_job* pJob); +static ma_result ma_job_process__quit(ma_job* pJob); +static ma_result ma_job_process__custom(ma_job* pJob); +static ma_result ma_job_process__resource_manager__load_data_buffer_node(ma_job* pJob); +static ma_result ma_job_process__resource_manager__free_data_buffer_node(ma_job* pJob); +static ma_result ma_job_process__resource_manager__page_data_buffer_node(ma_job* pJob); +static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob); +static ma_result ma_job_process__resource_manager__free_data_buffer(ma_job* pJob); +static ma_result ma_job_process__resource_manager__load_data_stream(ma_job* pJob); +static ma_result ma_job_process__resource_manager__free_data_stream(ma_job* pJob); +static ma_result ma_job_process__resource_manager__page_data_stream(ma_job* pJob); +static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob); + +#if !defined(MA_NO_DEVICE_IO) +static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob); +#endif + +static ma_job_proc g_jobVTable[MA_JOB_TYPE_COUNT] = +{ + /* Miscellaneous. */ + ma_job_process__quit, /* MA_JOB_TYPE_QUIT */ + ma_job_process__custom, /* MA_JOB_TYPE_CUSTOM */ + + /* Resource Manager. */ + ma_job_process__resource_manager__load_data_buffer_node, /* MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE */ + ma_job_process__resource_manager__free_data_buffer_node, /* MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE */ + ma_job_process__resource_manager__page_data_buffer_node, /* MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE */ + ma_job_process__resource_manager__load_data_buffer, /* MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER */ + ma_job_process__resource_manager__free_data_buffer, /* MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER */ + ma_job_process__resource_manager__load_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM */ + ma_job_process__resource_manager__free_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM */ + ma_job_process__resource_manager__page_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_STREAM */ + ma_job_process__resource_manager__seek_data_stream, /* MA_JOB_TYPE_RESOURCE_MANAGER_SEEK_DATA_STREAM */ + + /* Device. */ +#if !defined(MA_NO_DEVICE_IO) + ma_job_process__device__aaudio_reroute /*MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE*/ +#endif +}; + +MA_API ma_result ma_job_process(ma_job* pJob) +{ + if (pJob == NULL) { + return MA_INVALID_ARGS; + } + + if (pJob->toc.breakup.code > MA_JOB_TYPE_COUNT) { + return MA_INVALID_OPERATION; + } + + return g_jobVTable[pJob->toc.breakup.code](pJob); +} + +static ma_result ma_job_process__noop(ma_job* pJob) +{ + MA_ASSERT(pJob != NULL); + + /* No-op. */ + (void)pJob; + + return MA_SUCCESS; +} + +static ma_result ma_job_process__quit(ma_job* pJob) +{ + return ma_job_process__noop(pJob); +} + +static ma_result ma_job_process__custom(ma_job* pJob) +{ + MA_ASSERT(pJob != NULL); + + /* No-op if there's no callback. */ + if (pJob->data.custom.proc == NULL) { + return MA_SUCCESS; + } + + return pJob->data.custom.proc(pJob); +} + + + +MA_API ma_job_queue_config ma_job_queue_config_init(ma_uint32 flags, ma_uint32 capacity) +{ + ma_job_queue_config config; + + config.flags = flags; + config.capacity = capacity; + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t allocatorOffset; + size_t jobsOffset; +} ma_job_queue_heap_layout; + +static ma_result ma_job_queue_get_heap_layout(const ma_job_queue_config* pConfig, ma_job_queue_heap_layout* pHeapLayout) +{ + ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->capacity == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Allocator. */ + { + ma_slot_allocator_config allocatorConfig; + size_t allocatorHeapSizeInBytes; + + allocatorConfig = ma_slot_allocator_config_init(pConfig->capacity); + result = ma_slot_allocator_get_heap_size(&allocatorConfig, &allocatorHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->allocatorOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += allocatorHeapSizeInBytes; + } + + /* Jobs. */ + pHeapLayout->jobsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(pConfig->capacity * sizeof(ma_job)); + + return MA_SUCCESS; +} + +MA_API ma_result ma_job_queue_get_heap_size(const ma_job_queue_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_job_queue_heap_layout layout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_job_queue_get_heap_layout(pConfig, &layout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = layout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_job_queue_init_preallocated(const ma_job_queue_config* pConfig, void* pHeap, ma_job_queue* pQueue) +{ + ma_result result; + ma_job_queue_heap_layout heapLayout; + ma_slot_allocator_config allocatorConfig; + + if (pQueue == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pQueue); + + result = ma_job_queue_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pQueue->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pQueue->flags = pConfig->flags; + pQueue->capacity = pConfig->capacity; + pQueue->pJobs = (ma_job*)ma_offset_ptr(pHeap, heapLayout.jobsOffset); + + allocatorConfig = ma_slot_allocator_config_init(pConfig->capacity); + result = ma_slot_allocator_init_preallocated(&allocatorConfig, ma_offset_ptr(pHeap, heapLayout.allocatorOffset), &pQueue->allocator); + if (result != MA_SUCCESS) { + return result; + } + + /* We need a semaphore if we're running in non-blocking mode. If threading is disabled we need to return an error. */ + if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_init(0, &pQueue->sem); + } + #else + { + /* Threading is disabled and we've requested non-blocking mode. */ + return MA_INVALID_OPERATION; + } + #endif + } + + /* + Our queue needs to be initialized with a free standing node. This should always be slot 0. Required for the lock free algorithm. The first job in the queue is + just a dummy item for giving us the first item in the list which is stored in the "next" member. + */ + ma_slot_allocator_alloc(&pQueue->allocator, &pQueue->head); /* Will never fail. */ + pQueue->pJobs[ma_job_extract_slot(pQueue->head)].next = MA_JOB_ID_NONE; + pQueue->tail = pQueue->head; + + return MA_SUCCESS; +} + +MA_API ma_result ma_job_queue_init(const ma_job_queue_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_job_queue* pQueue) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_job_queue_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_job_queue_init_preallocated(pConfig, pHeap, pQueue); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pQueue->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_job_queue_uninit(ma_job_queue* pQueue, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pQueue == NULL) { + return; + } + + /* All we need to do is uninitialize the semaphore. */ + if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_uninit(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + ma_slot_allocator_uninit(&pQueue->allocator, pAllocationCallbacks); + + if (pQueue->_ownsHeap) { + ma_free(pQueue->_pHeap, pAllocationCallbacks); + } +} + +static ma_bool32 ma_job_queue_cas(volatile ma_uint64* dst, ma_uint64 expected, ma_uint64 desired) +{ + /* The new counter is taken from the expected value. */ + return c89atomic_compare_and_swap_64(dst, expected, ma_job_set_refcount(desired, ma_job_extract_refcount(expected) + 1)) == expected; +} + +MA_API ma_result ma_job_queue_post(ma_job_queue* pQueue, const ma_job* pJob) +{ + /* + Lock free queue implementation based on the paper by Michael and Scott: Nonblocking Algorithms and Preemption-Safe Locking on Multiprogrammed Shared Memory Multiprocessors + */ + ma_result result; + ma_uint64 slot; + ma_uint64 tail; + ma_uint64 next; + + if (pQueue == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + /* We need a new slot. */ + result = ma_slot_allocator_alloc(&pQueue->allocator, &slot); + if (result != MA_SUCCESS) { + return result; /* Probably ran out of slots. If so, MA_OUT_OF_MEMORY will be returned. */ + } + + /* At this point we should have a slot to place the job. */ + MA_ASSERT(ma_job_extract_slot(slot) < pQueue->capacity); + + /* We need to put the job into memory before we do anything. */ + pQueue->pJobs[ma_job_extract_slot(slot)] = *pJob; + pQueue->pJobs[ma_job_extract_slot(slot)].toc.allocation = slot; /* This will overwrite the job code. */ + pQueue->pJobs[ma_job_extract_slot(slot)].toc.breakup.code = pJob->toc.breakup.code; /* The job code needs to be applied again because the line above overwrote it. */ + pQueue->pJobs[ma_job_extract_slot(slot)].next = MA_JOB_ID_NONE; /* Reset for safety. */ + + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_lock(&pQueue->lock); + #endif + { + /* The job is stored in memory so now we need to add it to our linked list. We only ever add items to the end of the list. */ + for (;;) { + tail = c89atomic_load_64(&pQueue->tail); + next = c89atomic_load_64(&pQueue->pJobs[ma_job_extract_slot(tail)].next); + + if (ma_job_toc_to_allocation(tail) == ma_job_toc_to_allocation(c89atomic_load_64(&pQueue->tail))) { + if (ma_job_extract_slot(next) == 0xFFFF) { + if (ma_job_queue_cas(&pQueue->pJobs[ma_job_extract_slot(tail)].next, next, slot)) { + break; + } + } else { + ma_job_queue_cas(&pQueue->tail, tail, ma_job_extract_slot(next)); + } + } + } + ma_job_queue_cas(&pQueue->tail, tail, slot); + } + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + + + /* Signal the semaphore as the last step if we're using synchronous mode. */ + if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_release(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_job_queue_next(ma_job_queue* pQueue, ma_job* pJob) +{ + ma_uint64 head; + ma_uint64 tail; + ma_uint64 next; + + if (pQueue == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + /* If we're running in synchronous mode we'll need to wait on a semaphore. */ + if ((pQueue->flags & MA_JOB_QUEUE_FLAG_NON_BLOCKING) == 0) { + #ifndef MA_NO_THREADING + { + ma_semaphore_wait(&pQueue->sem); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never get here. Should have been checked at initialization time. */ + } + #endif + } + + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_lock(&pQueue->lock); + #endif + { + /* + BUG: In lock-free mode, multiple threads can be in this section of code. The "head" variable in the loop below + is stored. One thread can fall through to the freeing of this item while another is still using "head" for the + retrieval of the "next" variable. + + The slot allocator might need to make use of some reference counting to ensure it's only truely freed when + there are no more references to the item. This must be fixed before removing these locks. + */ + + /* Now we need to remove the root item from the list. */ + for (;;) { + head = c89atomic_load_64(&pQueue->head); + tail = c89atomic_load_64(&pQueue->tail); + next = c89atomic_load_64(&pQueue->pJobs[ma_job_extract_slot(head)].next); + + if (ma_job_toc_to_allocation(head) == ma_job_toc_to_allocation(c89atomic_load_64(&pQueue->head))) { + if (ma_job_extract_slot(head) == ma_job_extract_slot(tail)) { + if (ma_job_extract_slot(next) == 0xFFFF) { + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + return MA_NO_DATA_AVAILABLE; + } + ma_job_queue_cas(&pQueue->tail, tail, ma_job_extract_slot(next)); + } else { + *pJob = pQueue->pJobs[ma_job_extract_slot(next)]; + if (ma_job_queue_cas(&pQueue->head, head, ma_job_extract_slot(next))) { + break; + } + } + } + } + } + #ifndef MA_USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE + ma_spinlock_unlock(&pQueue->lock); + #endif + + ma_slot_allocator_free(&pQueue->allocator, head); + + /* + If it's a quit job make sure it's put back on the queue to ensure other threads have an opportunity to detect it and terminate naturally. We + could instead just leave it on the queue, but that would involve fiddling with the lock-free code above and I want to keep that as simple as + possible. + */ + if (pJob->toc.breakup.code == MA_JOB_TYPE_QUIT) { + ma_job_queue_post(pQueue, pJob); + return MA_CANCELLED; /* Return a cancelled status just in case the thread is checking return codes and not properly checking for a quit job. */ + } + + return MA_SUCCESS; +} + + + + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -11940,58 +17446,19 @@ typedef int (WINAPI * MA_PFN_StringFromGUID2)(const GUID* const rguid, LPOLE typedef HWND (WINAPI * MA_PFN_GetForegroundWindow)(void); typedef HWND (WINAPI * MA_PFN_GetDesktopWindow)(void); +#if defined(MA_WIN32_DESKTOP) /* Microsoft documents these APIs as returning LSTATUS, but the Win32 API shipping with some compilers do not define it. It's just a LONG. */ typedef LONG (WINAPI * MA_PFN_RegOpenKeyExA)(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult); typedef LONG (WINAPI * MA_PFN_RegCloseKey)(HKEY hKey); typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData); -#endif +#endif /* MA_WIN32_DESKTOP */ +#endif /* MA_WIN32 */ #define MA_DEFAULT_PLAYBACK_DEVICE_NAME "Default Playback Device" #define MA_DEFAULT_CAPTURE_DEVICE_NAME "Default Capture Device" -/* Posts a log message. */ -static void ma_post_log_message(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) -{ - if (pContext == NULL) { - if (pDevice != NULL) { - pContext = pDevice->pContext; - } - } - - if (pContext == NULL) { - return; - } - - ma_log_post(ma_context_get_log(pContext), logLevel, message); /* <-- This will deal with MA_DEBUG_OUTPUT. */ - - /* Legacy. */ -#if defined(MA_LOG_LEVEL) - if (logLevel <= MA_LOG_LEVEL) { - ma_log_proc onLog; - - onLog = pContext->logCallback; - if (onLog) { - onLog(pContext, pDevice, logLevel, message); - } - } -#endif -} - -/* Posts an log message. Throw a breakpoint in here if you're needing to debug. The return value is always "resultCode". */ -static ma_result ma_context_post_error(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message, ma_result resultCode) -{ - ma_post_log_message(pContext, pDevice, logLevel, message); - return resultCode; -} - -static ma_result ma_post_error(ma_device* pDevice, ma_uint32 logLevel, const char* message, ma_result resultCode) -{ - return ma_context_post_error(ma_device_get_context(pDevice), pDevice, logLevel, message, resultCode); -} - - /******************************************************************************* @@ -12001,7 +17468,7 @@ Timing *******************************************************************************/ #ifdef MA_WIN32 static LARGE_INTEGER g_ma_TimerFrequency; /* <-- Initialized to zero since it's static. */ - static void ma_timer_init(ma_timer* pTimer) + void ma_timer_init(ma_timer* pTimer) { LARGE_INTEGER counter; @@ -12013,7 +17480,7 @@ Timing pTimer->counter = counter.QuadPart; } - static double ma_timer_get_time_in_seconds(ma_timer* pTimer) + double ma_timer_get_time_in_seconds(ma_timer* pTimer) { LARGE_INTEGER counter; if (!QueryPerformanceCounter(&counter)) { @@ -12051,7 +17518,7 @@ Timing return (emscripten_get_now() - pTimer->counterD) / 1000; /* Emscripten is in milliseconds. */ } #else - #if _POSIX_C_SOURCE >= 199309L + #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L #if defined(CLOCK_MONOTONIC) #define MA_CLOCK_ID CLOCK_MONOTONIC #else @@ -12215,51 +17682,234 @@ static ma_uint32 ma_get_closest_standard_sample_rate(ma_uint32 sampleRateIn) #endif +static MA_INLINE unsigned int ma_device_disable_denormals(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + if (!pDevice->noDisableDenormals) { + return ma_disable_denormals(); + } else { + return 0; + } +} + +static MA_INLINE void ma_device_restore_denormals(ma_device* pDevice, unsigned int prevState) +{ + MA_ASSERT(pDevice != NULL); + + if (!pDevice->noDisableDenormals) { + ma_restore_denormals(prevState); + } else { + /* Do nothing. */ + (void)prevState; + } +} + +static ma_device_notification ma_device_notification_init(ma_device* pDevice, ma_device_notification_type type) +{ + ma_device_notification notification; + + MA_ZERO_OBJECT(¬ification); + notification.pDevice = pDevice; + notification.type = type; + + return notification; +} + +static void ma_device__on_notification(ma_device_notification notification) +{ + MA_ASSERT(notification.pDevice != NULL); + + if (notification.pDevice->onNotification != NULL) { + notification.pDevice->onNotification(¬ification); + } + + /* TEMP FOR COMPATIBILITY: If it's a stopped notification, fire the onStop callback as well. This is only for backwards compatibility and will be removed. */ + if (notification.pDevice->onStop != NULL && notification.type == ma_device_notification_type_stopped) { + notification.pDevice->onStop(notification.pDevice); + } +} + +void ma_device__on_notification_started(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_started)); +} + +void ma_device__on_notification_stopped(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_stopped)); +} + +void ma_device__on_notification_rerouted(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_rerouted)); +} + +void ma_device__on_notification_interruption_began(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_began)); +} + +void ma_device__on_notification_interruption_ended(ma_device* pDevice) +{ + ma_device__on_notification(ma_device_notification_init(pDevice, ma_device_notification_type_interruption_ended)); +} + + +static void ma_device__on_data_inner(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pDevice->onData != NULL); + + if (!pDevice->noPreSilencedOutputBuffer && pFramesOut != NULL) { + ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); + } + + pDevice->onData(pDevice, pFramesOut, pFramesIn, frameCount); +} + static void ma_device__on_data(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + MA_ASSERT(pDevice != NULL); + + if (pDevice->noFixedSizedCallback) { + /* Fast path. Not using a fixed sized callback. Process directly from the specified buffers. */ + ma_device__on_data_inner(pDevice, pFramesOut, pFramesIn, frameCount); + } else { + /* Slow path. Using a fixed sized callback. Need to use the intermediary buffer. */ + ma_uint32 totalFramesProcessed = 0; + + while (totalFramesProcessed < frameCount) { + ma_uint32 totalFramesRemaining = frameCount - totalFramesProcessed; + ma_uint32 framesToProcessThisIteration = 0; + + if (pFramesIn != NULL) { + /* Capturing. Write to the intermediary buffer. If there's no room, fire the callback to empty it. */ + if (pDevice->capture.intermediaryBufferLen < pDevice->capture.intermediaryBufferCap) { + /* There's some room left in the intermediary buffer. Write to it without firing the callback. */ + framesToProcessThisIteration = totalFramesRemaining; + if (framesToProcessThisIteration > pDevice->capture.intermediaryBufferCap - pDevice->capture.intermediaryBufferLen) { + framesToProcessThisIteration = pDevice->capture.intermediaryBufferCap - pDevice->capture.intermediaryBufferLen; + } + + ma_copy_pcm_frames( + ma_offset_pcm_frames_ptr(pDevice->capture.pIntermediaryBuffer, pDevice->capture.intermediaryBufferLen, pDevice->capture.format, pDevice->capture.channels), + ma_offset_pcm_frames_const_ptr(pFramesIn, totalFramesProcessed, pDevice->capture.format, pDevice->capture.channels), + framesToProcessThisIteration, + pDevice->capture.format, pDevice->capture.channels); + + pDevice->capture.intermediaryBufferLen += framesToProcessThisIteration; + } + + if (pDevice->capture.intermediaryBufferLen == pDevice->capture.intermediaryBufferCap) { + /* No room left in the intermediary buffer. Fire the data callback. */ + if (pDevice->type == ma_device_type_duplex) { + /* We'll do the duplex data callback later after we've processed the playback data. */ + } else { + ma_device__on_data_inner(pDevice, NULL, pDevice->capture.pIntermediaryBuffer, pDevice->capture.intermediaryBufferCap); + + /* The intermediary buffer has just been drained. */ + pDevice->capture.intermediaryBufferLen = 0; + } + } + } + + if (pFramesOut != NULL) { + /* Playing back. Read from the intermediary buffer. If there's nothing in it, fire the callback to fill it. */ + if (pDevice->playback.intermediaryBufferLen > 0) { + /* There's some content in the intermediary buffer. Read from that without firing the callback. */ + if (pDevice->type == ma_device_type_duplex) { + /* The frames processed this iteration for a duplex device will always be based on the capture side. Leave it unmodified. */ + } else { + framesToProcessThisIteration = totalFramesRemaining; + if (framesToProcessThisIteration > pDevice->playback.intermediaryBufferLen) { + framesToProcessThisIteration = pDevice->playback.intermediaryBufferLen; + } + } + + ma_copy_pcm_frames( + ma_offset_pcm_frames_ptr(pFramesOut, totalFramesProcessed, pDevice->playback.format, pDevice->playback.channels), + ma_offset_pcm_frames_ptr(pDevice->playback.pIntermediaryBuffer, pDevice->playback.intermediaryBufferCap - pDevice->playback.intermediaryBufferLen, pDevice->playback.format, pDevice->playback.channels), + framesToProcessThisIteration, + pDevice->playback.format, pDevice->playback.channels); + + pDevice->playback.intermediaryBufferLen -= framesToProcessThisIteration; + } + + if (pDevice->playback.intermediaryBufferLen == 0) { + /* There's nothing in the intermediary buffer. Fire the data callback to fill it. */ + if (pDevice->type == ma_device_type_duplex) { + /* In duplex mode, the data callback will be fired later. Nothing to do here. */ + } else { + ma_device__on_data_inner(pDevice, pDevice->playback.pIntermediaryBuffer, NULL, pDevice->playback.intermediaryBufferCap); + + /* The intermediary buffer has just been filled. */ + pDevice->playback.intermediaryBufferLen = pDevice->playback.intermediaryBufferCap; + } + } + } + + /* If we're in duplex mode we might need to do a refill of the data. */ + if (pDevice->type == ma_device_type_duplex) { + if (pDevice->capture.intermediaryBufferLen == pDevice->capture.intermediaryBufferCap) { + ma_device__on_data_inner(pDevice, pDevice->playback.pIntermediaryBuffer, pDevice->capture.pIntermediaryBuffer, pDevice->capture.intermediaryBufferCap); + + pDevice->playback.intermediaryBufferLen = pDevice->playback.intermediaryBufferCap; /* The playback buffer will have just been filled. */ + pDevice->capture.intermediaryBufferLen = 0; /* The intermediary buffer has just been drained. */ + } + } + + /* Make sure this is only incremented once in the duplex case. */ + totalFramesProcessed += framesToProcessThisIteration; + } + } +} + +static void ma_device__handle_data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) { float masterVolumeFactor; ma_device_get_master_volume(pDevice, &masterVolumeFactor); /* Use ma_device_get_master_volume() to ensure the volume is loaded atomically. */ if (pDevice->onData) { - if (!pDevice->noPreZeroedOutputBuffer && pFramesOut != NULL) { - ma_silence_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels); - } + unsigned int prevDenormalState = ma_device_disable_denormals(pDevice); + { + /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */ + if (pFramesIn != NULL && masterVolumeFactor < 1) { + ma_uint8 tempFramesIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + ma_uint32 totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + ma_uint32 framesToProcessThisIteration = frameCount - totalFramesProcessed; + if (framesToProcessThisIteration > sizeof(tempFramesIn)/bpfCapture) { + framesToProcessThisIteration = sizeof(tempFramesIn)/bpfCapture; + } - /* Volume control of input makes things a bit awkward because the input buffer is read-only. We'll need to use a temp buffer and loop in this case. */ - if (pFramesIn != NULL && masterVolumeFactor < 1) { - ma_uint8 tempFramesIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 bpfCapture = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 bpfPlayback = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint32 totalFramesProcessed = 0; - while (totalFramesProcessed < frameCount) { - ma_uint32 framesToProcessThisIteration = frameCount - totalFramesProcessed; - if (framesToProcessThisIteration > sizeof(tempFramesIn)/bpfCapture) { - framesToProcessThisIteration = sizeof(tempFramesIn)/bpfCapture; + ma_copy_and_apply_volume_factor_pcm_frames(tempFramesIn, ma_offset_ptr(pFramesIn, totalFramesProcessed*bpfCapture), framesToProcessThisIteration, pDevice->capture.format, pDevice->capture.channels, masterVolumeFactor); + + ma_device__on_data(pDevice, ma_offset_ptr(pFramesOut, totalFramesProcessed*bpfPlayback), tempFramesIn, framesToProcessThisIteration); + + totalFramesProcessed += framesToProcessThisIteration; + } + } else { + ma_device__on_data(pDevice, pFramesOut, pFramesIn, frameCount); + } + + /* Volume control and clipping for playback devices. */ + if (pFramesOut != NULL) { + if (masterVolumeFactor < 1) { + if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */ + ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, masterVolumeFactor); + } } - ma_copy_and_apply_volume_factor_pcm_frames(tempFramesIn, ma_offset_ptr(pFramesIn, totalFramesProcessed*bpfCapture), framesToProcessThisIteration, pDevice->capture.format, pDevice->capture.channels, masterVolumeFactor); - - pDevice->onData(pDevice, ma_offset_ptr(pFramesOut, totalFramesProcessed*bpfPlayback), tempFramesIn, framesToProcessThisIteration); - - totalFramesProcessed += framesToProcessThisIteration; - } - } else { - pDevice->onData(pDevice, pFramesOut, pFramesIn, frameCount); - } - - /* Volume control and clipping for playback devices. */ - if (pFramesOut != NULL) { - if (masterVolumeFactor < 1) { - if (pFramesIn == NULL) { /* <-- In full-duplex situations, the volume will have been applied to the input samples before the data callback. Applying it again post-callback will incorrectly compound it. */ - ma_apply_volume_factor_pcm_frames(pFramesOut, frameCount, pDevice->playback.format, pDevice->playback.channels, masterVolumeFactor); + if (!pDevice->noClip && pDevice->playback.format == ma_format_f32) { + ma_clip_samples_f32((float*)pFramesOut, (const float*)pFramesOut, frameCount * pDevice->playback.channels); /* Intentionally specifying the same pointer for both input and output for in-place processing. */ } } - - if (!pDevice->noClip && pDevice->playback.format == ma_format_f32) { - ma_clip_pcm_frames_f32((float*)pFramesOut, frameCount, pDevice->playback.channels); - } } + ma_device_restore_denormals(pDevice, prevDenormalState); } } @@ -12273,58 +17923,99 @@ static void ma_device__read_frames_from_client(ma_device* pDevice, ma_uint32 fra MA_ASSERT(pFramesOut != NULL); if (pDevice->playback.converter.isPassthrough) { - ma_device__on_data(pDevice, pFramesOut, NULL, frameCount); + ma_device__handle_data_callback(pDevice, pFramesOut, NULL, frameCount); } else { ma_result result; ma_uint64 totalFramesReadOut; - ma_uint64 totalFramesReadIn; void* pRunningFramesOut; totalFramesReadOut = 0; - totalFramesReadIn = 0; pRunningFramesOut = pFramesOut; - while (totalFramesReadOut < frameCount) { - ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In client format. */ - ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 framesToReadThisIterationIn; - ma_uint64 framesReadThisIterationIn; - ma_uint64 framesToReadThisIterationOut; - ma_uint64 framesReadThisIterationOut; - ma_uint64 requiredInputFrameCount; + /* + We run slightly different logic depending on whether or not we're using a heap-allocated + buffer for caching input data. This will be the case if the data converter does not have + the ability to retrieve the required input frame count for a given output frame count. + */ + if (pDevice->playback.pInputCache != NULL) { + while (totalFramesReadOut < frameCount) { + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; - framesToReadThisIterationOut = (frameCount - totalFramesReadOut); - framesToReadThisIterationIn = framesToReadThisIterationOut; - if (framesToReadThisIterationIn > intermediaryBufferCap) { - framesToReadThisIterationIn = intermediaryBufferCap; + /* If there's any data available in the cache, that needs to get processed first. */ + if (pDevice->playback.inputCacheRemaining > 0) { + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > pDevice->playback.inputCacheRemaining) { + framesToReadThisIterationIn = pDevice->playback.inputCacheRemaining; + } + + result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesToReadThisIterationIn, pRunningFramesOut, &framesToReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } + + pDevice->playback.inputCacheConsumed += framesToReadThisIterationIn; + pDevice->playback.inputCacheRemaining -= framesToReadThisIterationIn; + + totalFramesReadOut += framesToReadThisIterationOut; + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesToReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + + if (framesToReadThisIterationIn == 0 && framesToReadThisIterationOut == 0) { + break; /* We're done. */ + } + } + + /* Getting here means there's no data in the cache and we need to fill it up with data from the client. */ + if (pDevice->playback.inputCacheRemaining == 0) { + ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, NULL, (ma_uint32)pDevice->playback.inputCacheCap); + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = pDevice->playback.inputCacheCap; + } } + } else { + while (totalFramesReadOut < frameCount) { + ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In client format. */ + ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; + ma_uint64 framesReadThisIterationOut; + ma_uint64 requiredInputFrameCount; - requiredInputFrameCount = ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, framesToReadThisIterationOut); - if (framesToReadThisIterationIn > requiredInputFrameCount) { - framesToReadThisIterationIn = requiredInputFrameCount; - } + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > intermediaryBufferCap) { + framesToReadThisIterationIn = intermediaryBufferCap; + } - if (framesToReadThisIterationIn > 0) { - ma_device__on_data(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); - totalFramesReadIn += framesToReadThisIterationIn; - } + ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, framesToReadThisIterationOut, &requiredInputFrameCount); + if (framesToReadThisIterationIn > requiredInputFrameCount) { + framesToReadThisIterationIn = requiredInputFrameCount; + } - /* - At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any - input frames, we still want to try processing frames because there may some output frames generated from cached input data. - */ - framesReadThisIterationIn = framesToReadThisIterationIn; - framesReadThisIterationOut = framesToReadThisIterationOut; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); - if (result != MA_SUCCESS) { - break; - } + if (framesToReadThisIterationIn > 0) { + ma_device__handle_data_callback(pDevice, pIntermediaryBuffer, NULL, (ma_uint32)framesToReadThisIterationIn); + } - totalFramesReadOut += framesReadThisIterationOut; - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + /* + At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any + input frames, we still want to try processing frames because there may some output frames generated from cached input data. + */ + framesReadThisIterationIn = framesToReadThisIterationIn; + framesReadThisIterationOut = framesToReadThisIterationOut; + result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } - if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { - break; /* We're done. */ + totalFramesReadOut += framesReadThisIterationOut; + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + + if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { + break; /* We're done. */ + } } } } @@ -12338,7 +18029,7 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame MA_ASSERT(pFramesInDeviceFormat != NULL); if (pDevice->capture.converter.isPassthrough) { - ma_device__on_data(pDevice, NULL, pFramesInDeviceFormat, frameCountInDeviceFormat); + ma_device__handle_data_callback(pDevice, NULL, pFramesInDeviceFormat, frameCountInDeviceFormat); } else { ma_result result; ma_uint8 pFramesInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; @@ -12361,7 +18052,7 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame } if (clientFramesProcessedThisIteration > 0) { - ma_device__on_data(pDevice, NULL, pFramesInClientFormat, (ma_uint32)clientFramesProcessedThisIteration); /* Safe cast. */ + ma_device__handle_data_callback(pDevice, NULL, pFramesInClientFormat, (ma_uint32)clientFramesProcessedThisIteration); /* Safe cast. */ } pRunningFramesInDeviceFormat = ma_offset_ptr(pRunningFramesInDeviceFormat, deviceFramesProcessedThisIteration * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); @@ -12396,7 +18087,7 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m result = ma_pcm_rb_acquire_write(pRB, &framesToProcessInClientFormat, &pFramesInClientFormat); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to acquire capture PCM frames from ring buffer.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to acquire capture PCM frames from ring buffer."); break; } @@ -12414,9 +18105,9 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m break; } - result = ma_pcm_rb_commit_write(pRB, (ma_uint32)framesProcessedInClientFormat, pFramesInClientFormat); /* Safe cast. */ + result = ma_pcm_rb_commit_write(pRB, (ma_uint32)framesProcessedInClientFormat); /* Safe cast. */ if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "Failed to commit capture PCM frames to ring buffer.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "Failed to commit capture PCM frames to ring buffer."); break; } @@ -12435,16 +18126,14 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, ma_uint32 frameCount, void* pFramesInInternalFormat, ma_pcm_rb* pRB) { ma_result result; - ma_uint8 playbackFramesInExternalFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; ma_uint8 silentInputFrames[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 totalFramesToReadFromClient; - ma_uint32 totalFramesReadFromClient; ma_uint32 totalFramesReadOut = 0; MA_ASSERT(pDevice != NULL); MA_ASSERT(frameCount > 0); MA_ASSERT(pFramesInInternalFormat != NULL); MA_ASSERT(pRB != NULL); + MA_ASSERT(pDevice->playback.pInputCache != NULL); /* Sitting in the ring buffer should be captured data from the capture callback in external format. If there's not enough data in there for @@ -12452,68 +18141,61 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, */ MA_ZERO_MEMORY(silentInputFrames, sizeof(silentInputFrames)); - /* We need to calculate how many output frames are required to be read from the client to completely fill frameCount internal frames. */ - totalFramesToReadFromClient = (ma_uint32)ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, frameCount); - totalFramesReadFromClient = 0; - while (totalFramesReadFromClient < totalFramesToReadFromClient && ma_device_is_started(pDevice)) { - ma_uint32 framesRemainingFromClient; - ma_uint32 framesToProcessFromClient; - ma_uint32 inputFrameCount; - void* pInputFrames; - - framesRemainingFromClient = (totalFramesToReadFromClient - totalFramesReadFromClient); - framesToProcessFromClient = sizeof(playbackFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - if (framesToProcessFromClient > framesRemainingFromClient) { - framesToProcessFromClient = framesRemainingFromClient; - } - - /* We need to grab captured samples before firing the callback. If there's not enough input samples we just pass silence. */ - inputFrameCount = framesToProcessFromClient; - result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames); - if (result == MA_SUCCESS) { - if (inputFrameCount > 0) { - /* Use actual input frames. */ - ma_device__on_data(pDevice, playbackFramesInExternalFormat, pInputFrames, inputFrameCount); - } else { - if (ma_pcm_rb_pointer_distance(pRB) == 0) { - break; /* Underrun. */ - } - } - - /* We're done with the captured samples. */ - result = ma_pcm_rb_commit_read(pRB, inputFrameCount, pInputFrames); - if (result != MA_SUCCESS) { - break; /* Don't know what to do here... Just abandon ship. */ - } - } else { - /* Use silent input frames. */ - inputFrameCount = ma_min( - sizeof(playbackFramesInExternalFormat) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels), - sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels) - ); - - ma_device__on_data(pDevice, playbackFramesInExternalFormat, silentInputFrames, inputFrameCount); - } - - /* We have samples in external format so now we need to convert to internal format and output to the device. */ - { - ma_uint64 framesConvertedIn = inputFrameCount; + while (totalFramesReadOut < frameCount && ma_device_is_started(pDevice)) { + /* + We should have a buffer allocated on the heap. Any playback frames still sitting in there + need to be sent to the internal device before we process any more data from the client. + */ + if (pDevice->playback.inputCacheRemaining > 0) { + ma_uint64 framesConvertedIn = pDevice->playback.inputCacheRemaining; ma_uint64 framesConvertedOut = (frameCount - totalFramesReadOut); - ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackFramesInExternalFormat, &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut); + ma_data_converter_process_pcm_frames(&pDevice->playback.converter, ma_offset_pcm_frames_ptr(pDevice->playback.pInputCache, pDevice->playback.inputCacheConsumed, pDevice->playback.format, pDevice->playback.channels), &framesConvertedIn, pFramesInInternalFormat, &framesConvertedOut); + + pDevice->playback.inputCacheConsumed += framesConvertedIn; + pDevice->playback.inputCacheRemaining -= framesConvertedIn; - totalFramesReadFromClient += (ma_uint32)framesConvertedIn; /* Safe cast. */ totalFramesReadOut += (ma_uint32)framesConvertedOut; /* Safe cast. */ pFramesInInternalFormat = ma_offset_ptr(pFramesInInternalFormat, framesConvertedOut * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); } + + /* If there's no more data in the cache we'll need to fill it with some. */ + if (totalFramesReadOut < frameCount && pDevice->playback.inputCacheRemaining == 0) { + ma_uint32 inputFrameCount; + void* pInputFrames; + + inputFrameCount = (ma_uint32)pDevice->playback.inputCacheCap; + result = ma_pcm_rb_acquire_read(pRB, &inputFrameCount, &pInputFrames); + if (result == MA_SUCCESS) { + if (inputFrameCount > 0) { + ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, pInputFrames, inputFrameCount); + } else { + if (ma_pcm_rb_pointer_distance(pRB) == 0) { + break; /* Underrun. */ + } + } + } else { + /* No capture data available. Feed in silence. */ + inputFrameCount = (ma_uint32)ma_min(pDevice->playback.inputCacheCap, sizeof(silentInputFrames) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); + ma_device__handle_data_callback(pDevice, pDevice->playback.pInputCache, silentInputFrames, inputFrameCount); + } + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = inputFrameCount; + + result = ma_pcm_rb_commit_read(pRB, inputFrameCount); + if (result != MA_SUCCESS) { + return result; /* Should never happen. */ + } + } } return MA_SUCCESS; } /* A helper for changing the state of the device. */ -static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_uint32 newState) +static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_device_state newState) { - c89atomic_exchange_32(&pDevice->state, newState); + c89atomic_exchange_i32((ma_int32*)&pDevice->state, (ma_int32)newState); } @@ -12541,7 +18223,6 @@ MA_API ma_uint32 ma_get_format_priority_index(ma_format format) /* Lower = bette static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type deviceType); - static ma_bool32 ma_device_descriptor_is_valid(const ma_device_descriptor* pDeviceDescriptor) { if (pDeviceDescriptor == NULL) { @@ -12552,7 +18233,7 @@ static ma_bool32 ma_device_descriptor_is_valid(const ma_device_descriptor* pDevi return MA_FALSE; } - if (pDeviceDescriptor->channels < MA_MIN_CHANNELS || pDeviceDescriptor->channels > MA_MAX_CHANNELS) { + if (pDeviceDescriptor->channels == 0 || pDeviceDescriptor->channels > MA_MAX_CHANNELS) { return MA_FALSE; } @@ -12594,7 +18275,7 @@ static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) /* NOTE: The device was started outside of this function, in the worker thread. */ - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { + while (ma_device_get_state(pDevice) == ma_device_state_started && !exitLoop) { switch (pDevice->type) { case ma_device_type_duplex: { @@ -12644,7 +18325,7 @@ static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) break; } - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ + ma_device__handle_data_callback(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ @@ -12974,7 +18655,7 @@ static ma_result ma_device_init__null(ma_device* pDevice, const ma_device_config pDescriptorCapture->sampleRate = (pDescriptorCapture->sampleRate != 0) ? pDescriptorCapture->sampleRate : MA_DEFAULT_SAMPLE_RATE; if (pDescriptorCapture->channelMap[0] == MA_CHANNEL_NONE) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); } pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); @@ -12986,7 +18667,7 @@ static ma_result ma_device_init__null(ma_device* pDevice, const ma_device_config pDescriptorPlayback->sampleRate = (pDescriptorPlayback->sampleRate != 0) ? pDescriptorPlayback->sampleRate : MA_DEFAULT_SAMPLE_RATE; if (pDescriptorPlayback->channelMap[0] == MA_CHANNEL_NONE) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptorPlayback->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorPlayback->channels); } pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); @@ -13555,7 +19236,7 @@ static const PROPERTYKEY MA_PKEY_Device_FriendlyName = {{0xA45C254E, static const PROPERTYKEY MA_PKEY_AudioEngine_DeviceFormat = {{0xF19F064D, 0x82C, 0x4E27, {0xBC, 0x73, 0x68, 0x82, 0xA1, 0xBB, 0x8E, 0x4C}}, 0}; static const IID MA_IID_IUnknown = {0x00000000, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; /* 00000000-0000-0000-C000-000000000046 */ -#ifndef MA_WIN32_DESKTOP +#if !defined(MA_WIN32_DESKTOP) && !defined(MA_WIN32_GDK) static const IID MA_IID_IAgileObject = {0x94EA2B94, 0xE9CC, 0x49E0, {0xC0, 0xFF, 0xEE, 0x64, 0xCA, 0x8F, 0x5B, 0x90}}; /* 94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90 */ #endif @@ -13565,7 +19246,7 @@ static const IID MA_IID_IAudioClient3 = {0x7ED4EE07, static const IID MA_IID_IAudioRenderClient = {0xF294ACFC, 0x3146, 0x4483, {0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2}}; /* F294ACFC-3146-4483-A7BF-ADDCA7C260E2 = __uuidof(IAudioRenderClient) */ static const IID MA_IID_IAudioCaptureClient = {0xC8ADBD64, 0xE71E, 0x48A0, {0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17}}; /* C8ADBD64-E71E-48A0-A4DE-185C395CD317 = __uuidof(IAudioCaptureClient) */ static const IID MA_IID_IMMNotificationClient = {0x7991EEC9, 0x7E89, 0x4D85, {0x83, 0x90, 0x6C, 0x70, 0x3C, 0xEC, 0x60, 0xC0}}; /* 7991EEC9-7E89-4D85-8390-6C703CEC60C0 = __uuidof(IMMNotificationClient) */ -#ifndef MA_WIN32_DESKTOP +#if !defined(MA_WIN32_DESKTOP) && !defined(MA_WIN32_GDK) static const IID MA_IID_DEVINTERFACE_AUDIO_RENDER = {0xE6327CAD, 0xDCEC, 0x4949, {0xAE, 0x8A, 0x99, 0x1E, 0x97, 0x6A, 0x79, 0xD2}}; /* E6327CAD-DCEC-4949-AE8A-991E976A79D2 */ static const IID MA_IID_DEVINTERFACE_AUDIO_CAPTURE = {0x2EEF81BE, 0x33FA, 0x4800, {0x96, 0x70, 0x1C, 0xD4, 0x74, 0x97, 0x2C, 0x3F}}; /* 2EEF81BE-33FA-4800-9670-1CD474972C3F */ static const IID MA_IID_IActivateAudioInterfaceCompletionHandler = {0x41D949AB, 0x9862, 0x444A, {0x80, 0xF6, 0xC2, 0x61, 0x33, 0x4D, 0xA5, 0xEB}}; /* 41D949AB-9862-444A-80F6-C261334DA5EB */ @@ -13582,7 +19263,7 @@ static const IID MA_IID_IMMDeviceEnumerator_Instance = {0xA95664D2, #endif typedef struct ma_IUnknown ma_IUnknown; -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) #define MA_MM_DEVICE_STATE_ACTIVE 1 #define MA_MM_DEVICE_STATE_DISABLED 2 #define MA_MM_DEVICE_STATE_NOTPRESENT 4 @@ -13668,7 +19349,7 @@ static MA_INLINE HRESULT ma_IUnknown_QueryInterface(ma_IUnknown* pThis, const II static MA_INLINE ULONG ma_IUnknown_AddRef(ma_IUnknown* pThis) { return pThis->lpVtbl->AddRef(pThis); } static MA_INLINE ULONG ma_IUnknown_Release(ma_IUnknown* pThis) { return pThis->lpVtbl->Release(pThis); } -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) /* IMMNotificationClient */ typedef struct { @@ -14012,7 +19693,7 @@ static MA_INLINE HRESULT ma_IAudioCaptureClient_GetBuffer(ma_IAudioCaptureClient static MA_INLINE HRESULT ma_IAudioCaptureClient_ReleaseBuffer(ma_IAudioCaptureClient* pThis, ma_uint32 numFramesRead) { return pThis->lpVtbl->ReleaseBuffer(pThis, numFramesRead); } static MA_INLINE HRESULT ma_IAudioCaptureClient_GetNextPacketSize(ma_IAudioCaptureClient* pThis, ma_uint32* pNumFramesInNextPacket) { return pThis->lpVtbl->GetNextPacketSize(pThis, pNumFramesInNextPacket); } -#ifndef MA_WIN32_DESKTOP +#if !defined(MA_WIN32_DESKTOP) && !defined(MA_WIN32_GDK) #include typedef struct ma_completion_handler_uwp ma_completion_handler_uwp; @@ -14029,7 +19710,7 @@ typedef struct struct ma_completion_handler_uwp { ma_completion_handler_uwp_vtbl* lpVtbl; - MA_ATOMIC ma_uint32 counter; + MA_ATOMIC(4, ma_uint32) counter; HANDLE hEvent; }; @@ -14109,7 +19790,7 @@ static void ma_completion_handler_uwp_wait(ma_completion_handler_uwp* pHandler) #endif /* !MA_WIN32_DESKTOP */ /* We need a virtual table for our notification client object that's used for detecting changes to the default device. */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_QueryInterface(ma_IMMNotificationClient* pThis, const IID* const riid, void** ppObject) { /* @@ -14183,7 +19864,7 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceStateChanged(m use this to determine whether or not we need to automatically start the device when it's plugged back in again. */ - if (ma_device_get_state(pThis->pDevice) == MA_STATE_STARTED) { + if (ma_device_get_state(pThis->pDevice) == ma_device_state_started) { if (isPlayback) { pThis->pDevice->wasapi.isDetachedPlayback = MA_TRUE; } @@ -14293,13 +19974,13 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged /* Second attempt at device rerouting. We're going to retrieve the device's state at the time of the route change. We're then going to stop the device, reinitialize the device, and then start - it again if the state before stopping was MA_STATE_STARTED. + it again if the state before stopping was ma_device_state_started. */ { ma_uint32 previousState = ma_device_get_state(pThis->pDevice); ma_bool8 restartDevice = MA_FALSE; - if (previousState == MA_STATE_STARTED) { + if (previousState == ma_device_state_started) { ma_device_stop(pThis->pDevice); restartDevice = MA_TRUE; } @@ -14364,7 +20045,7 @@ static ma_IMMNotificationClientVtbl g_maNotificationCientVtbl = { }; #endif /* MA_WIN32_DESKTOP */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) typedef ma_IMMDevice ma_WASAPIDeviceInterface; #else typedef ma_IUnknown ma_WASAPIDeviceInterface; @@ -14584,7 +20265,8 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context if (SUCCEEDED(hr)) { ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(pWF, ma_share_mode_shared, pInfo); } else { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve mix format for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve mix format for device info retrieval."); + return ma_result_from_HRESULT(hr); } /* @@ -14592,7 +20274,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context UWP. Failure to retrieve the exclusive mode format is not considered an error, so from here on out, MA_SUCCESS is guaranteed to be returned. */ - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) { ma_IPropertyStore *pProperties; @@ -14622,7 +20304,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context The format returned by PKEY_AudioEngine_DeviceFormat is not supported, so fall back to a search. We assume the channel count returned by MA_PKEY_AudioEngine_DeviceFormat is valid and correct. For simplicity we're only returning one format. */ - ma_uint32 channels = pInfo->minChannels; + ma_uint32 channels = pWF->nChannels; ma_channel defaultChannelMap[MA_MAX_CHANNELS]; WAVEFORMATEXTENSIBLE wf; ma_bool32 found; @@ -14633,7 +20315,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context channels = MA_MAX_CHANNELS; } - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, channels, defaultChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, defaultChannelMap, ma_countof(defaultChannelMap), channels); MA_ZERO_OBJECT(&wf); wf.Format.cbSize = sizeof(wf); @@ -14675,16 +20357,16 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context ma_PropVariantClear(pContext, &var); if (!found) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to find suitable device format for device info retrieval.", MA_FORMAT_NOT_SUPPORTED); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to find suitable device format for device info retrieval."); } } } else { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to retrieve device format for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to retrieve device format for device info retrieval."); } ma_IPropertyStore_Release(pProperties); } else { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to open property store for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to open property store for device info retrieval."); } } #endif @@ -14692,7 +20374,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context return MA_SUCCESS; } -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) static ma_EDataFlow ma_device_type_to_EDataFlow(ma_device_type deviceType) { if (deviceType == ma_device_type_playback) { @@ -14717,7 +20399,8 @@ static ma_result ma_context_create_IMMDeviceEnumerator__wasapi(ma_context* pCont hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } *ppDeviceEnumerator = pDeviceEnumerator; @@ -14790,7 +20473,8 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create IMMDeviceEnumerator."); + return ma_result_from_HRESULT(hr); } if (pDeviceID == NULL) { @@ -14801,7 +20485,8 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve IMMDevice."); + return ma_result_from_HRESULT(hr); } return MA_SUCCESS; @@ -14881,7 +20566,8 @@ static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pC ma_IAudioClient_Release(pAudioClient); return result; } else { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate audio client for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate audio client for device info retrieval."); + return ma_result_from_HRESULT(hr); } } @@ -14908,7 +20594,8 @@ static ma_result ma_context_enumerate_devices_by_type__wasapi(ma_context* pConte if (SUCCEEDED(hr)) { hr = ma_IMMDeviceCollection_GetCount(pDeviceCollection, &deviceCount); if (FAILED(hr)) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get device count.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to get device count."); + result = ma_result_from_HRESULT(hr); goto done; } @@ -14999,13 +20686,15 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m hr = StringFromIID(&iid, &iidStr); #endif if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory."); + return ma_result_from_HRESULT(hr); } result = ma_completion_handler_uwp_init(&completionHandler); if (result != MA_SUCCESS) { ma_CoTaskMemFree(pContext, iidStr); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync().", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync()."); + return result; } #if defined(__cplusplus) @@ -15016,7 +20705,8 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m if (FAILED(hr)) { ma_completion_handler_uwp_uninit(&completionHandler); ma_CoTaskMemFree(pContext, iidStr); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] ActivateAudioInterfaceAsync() failed."); + return ma_result_from_HRESULT(hr); } ma_CoTaskMemFree(pContext, iidStr); @@ -15029,13 +20719,15 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m ma_IActivateAudioInterfaceAsyncOperation_Release(pAsyncOp); if (FAILED(hr) || FAILED(activateResult)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device.", FAILED(hr) ? ma_result_from_HRESULT(hr) : ma_result_from_HRESULT(activateResult)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to activate device."); + return FAILED(hr) ? ma_result_from_HRESULT(hr) : ma_result_from_HRESULT(activateResult); } /* Here is where we grab the IAudioClient interface. */ hr = ma_IUnknown_QueryInterface(pActivatedInterface, &MA_IID_IAudioClient, (void**)ppAudioClient); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to query IAudioClient interface."); + return ma_result_from_HRESULT(hr); } if (ppActivatedInterface) { @@ -15050,7 +20742,7 @@ static ma_result ma_context_get_IAudioClient_UWP__wasapi(ma_context* pContext, m static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_IAudioClient** ppAudioClient, ma_WASAPIDeviceInterface** ppDeviceInterface) { -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) return ma_context_get_IAudioClient_Desktop__wasapi(pContext, deviceType, pDeviceID, ppAudioClient, ppDeviceInterface); #else return ma_context_get_IAudioClient_UWP__wasapi(pContext, deviceType, pDeviceID, ppAudioClient, ppDeviceInterface); @@ -15061,14 +20753,15 @@ static ma_result ma_context_get_IAudioClient__wasapi(ma_context* pContext, ma_de static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { /* Different enumeration for desktop and UWP. */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) /* Desktop */ HRESULT hr; ma_IMMDeviceEnumerator* pDeviceEnumerator; hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } ma_context_enumerate_devices_by_type__wasapi(pContext, pDeviceEnumerator, ma_device_type_playback, callback, pUserData); @@ -15112,7 +20805,7 @@ static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_e static ma_result ma_context_get_device_info__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) ma_result result; ma_IMMDevice* pMMDevice = NULL; LPWSTR pDefaultDeviceID = NULL; @@ -15164,7 +20857,7 @@ static ma_result ma_device_uninit__wasapi(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) if (pDevice->wasapi.pDeviceEnumerator) { ((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator)->lpVtbl->UnregisterEndpointNotificationCallback((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator, &pDevice->wasapi.notificationClient); ma_IMMDeviceEnumerator_Release((ma_IMMDeviceEnumerator*)pDevice->wasapi.pDeviceEnumerator); @@ -15172,9 +20865,23 @@ static ma_result ma_device_uninit__wasapi(ma_device* pDevice) #endif if (pDevice->wasapi.pRenderClient) { + if (pDevice->wasapi.pMappedBufferPlayback != NULL) { + ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; + } + ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); } if (pDevice->wasapi.pCaptureClient) { + if (pDevice->wasapi.pMappedBufferCapture != NULL) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; + } + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); } @@ -15293,7 +21000,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Here is where we try to determine the best format to use with the device. If the client if wanting exclusive mode, first try finding the best format for that. If this fails, fall back to shared mode. */ result = MA_FORMAT_NOT_SUPPORTED; if (pData->shareMode == ma_share_mode_exclusive) { - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) /* In exclusive mode on desktop we always use the backend's native format. */ ma_IPropertyStore* pStore = NULL; hr = ma_IMMDevice_OpenPropertyStore(pDeviceInterface, STGM_READ, &pStore); @@ -15405,7 +21112,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Slightly different initialization for shared and exclusive modes. We try exclusive mode first, and if it fails, fall back to shared mode. */ if (shareMode == MA_AUDCLNT_SHAREMODE_EXCLUSIVE) { - MA_REFERENCE_TIME bufferDuration = periodDurationInMicroseconds * 10; + MA_REFERENCE_TIME bufferDuration = periodDurationInMicroseconds * pData->periodsOut * 10; /* If the periodicy is too small, Initialize() will fail with AUDCLNT_E_INVALID_DEVICE_PERIOD. In this case we should just keep increasing @@ -15439,7 +21146,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Unfortunately we need to release and re-acquire the audio client according to MSDN. Seems silly - why not just call IAudioClient_Initialize() again?! */ ma_IAudioClient_Release((ma_IAudioClient*)pData->pAudioClient); - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) hr = ma_IMMDevice_Activate(pDeviceInterface, &MA_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pData->pAudioClient); #else hr = ma_IUnknown_QueryInterface(pDeviceInterface, &MA_IID_IAudioClient, (void**)&pData->pAudioClient); @@ -15497,15 +21204,11 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* The period needs to be clamped between minPeriodInFrames and maxPeriodInFrames. */ actualPeriodInFrames = ma_clamp(actualPeriodInFrames, minPeriodInFrames, maxPeriodInFrames); - #if defined(MA_DEBUG_OUTPUT) - { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=%d)\n", actualPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " defaultPeriodInFrames=%d\n", defaultPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " fundamentalPeriodInFrames=%d\n", fundamentalPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " minPeriodInFrames=%d\n", minPeriodInFrames); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " maxPeriodInFrames=%d\n", maxPeriodInFrames); - } - #endif + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Trying IAudioClient3_InitializeSharedAudioStream(actualPeriodInFrames=%d)\n", actualPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " defaultPeriodInFrames=%d\n", defaultPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " fundamentalPeriodInFrames=%d\n", fundamentalPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " minPeriodInFrames=%d\n", minPeriodInFrames); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " maxPeriodInFrames=%d\n", maxPeriodInFrames); /* If the client requested a largish buffer than we don't actually want to use low latency shared mode because it forces small buffers. */ if (actualPeriodInFrames >= desiredPeriodInFrames) { @@ -15517,12 +21220,9 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device if (SUCCEEDED(hr)) { wasInitializedUsingIAudioClient3 = MA_TRUE; pData->periodSizeInFramesOut = actualPeriodInFrames; - #if defined(MA_DEBUG_OUTPUT) - { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Using IAudioClient3\n"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " periodSizeInFramesOut=%d\n", pData->periodSizeInFramesOut); - } - #endif + + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] Using IAudioClient3\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " periodSizeInFramesOut=%d\n", pData->periodSizeInFramesOut); } else { ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[WASAPI] IAudioClient3_InitializeSharedAudioStream failed. Falling back to IAudioClient.\n"); } @@ -15590,7 +21290,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device /* Grab the name of the device. */ - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) { ma_IPropertyStore *pProperties; hr = ma_IMMDevice_OpenPropertyStore(pDeviceInterface, STGM_READ, &pProperties); @@ -15613,7 +21313,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device stream routing so that IDs can be compared and we can determine which device has been detached and whether or not it matches with our ma_device. */ - #ifdef MA_WIN32_DESKTOP + #if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) { /* Desktop */ ma_context_get_device_id_from_MMDevice__wasapi(pContext, pDeviceInterface, &pData->id); @@ -15627,7 +21327,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device done: /* Clean up. */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) if (pDeviceInterface != NULL) { ma_IMMDevice_Release(pDeviceInterface); } @@ -15652,7 +21352,7 @@ done: } if (errorMsg != NULL && errorMsg[0] != '\0') { - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_ERROR, errorMsg); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "%s", errorMsg); } return result; @@ -15750,7 +21450,7 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualPeriodSizeInFramesCapture); + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualBufferSizeInFramesCapture); /* We must always have a valid ID. */ ma_wcscpy_s(pDevice->capture.id.wasapi, sizeof(pDevice->capture.id.wasapi), data.id.wasapi); @@ -15771,9 +21471,9 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualPeriodSizeInFramesPlayback); + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualBufferSizeInFramesPlayback); - /* We must always have a valid ID. */ + /* We must always have a valid ID because rerouting will look at it. */ ma_wcscpy_s(pDevice->playback.id.wasapi, sizeof(pDevice->playback.id.wasapi), data.id.wasapi); } @@ -15784,7 +21484,7 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf { ma_result result = MA_SUCCESS; -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) HRESULT hr; ma_IMMDeviceEnumerator* pDeviceEnumerator; #endif @@ -15845,12 +21545,13 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf pDevice->wasapi.pAudioClientCapture = NULL; } - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for capture."); + return result; } ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, pDevice->wasapi.hEventCapture); pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualPeriodSizeInFramesCapture); + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualBufferSizeInFramesCapture); /* We must always have a valid ID. */ ma_wcscpy_s(pDevice->capture.id.wasapi, sizeof(pDevice->capture.id.wasapi), data.id.wasapi); @@ -15938,14 +21639,15 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf pDevice->wasapi.pAudioClientPlayback = NULL; } - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create event for playback."); + return result; } ma_IAudioClient_SetEventHandle((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, pDevice->wasapi.hEventPlayback); pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; - ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualPeriodSizeInFramesPlayback); + ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualBufferSizeInFramesPlayback); - /* We must always have a valid ID. */ + /* We must always have a valid ID because rerouting will look at it. */ ma_wcscpy_s(pDevice->playback.id.wasapi, sizeof(pDevice->playback.id.wasapi), data.id.wasapi); /* The descriptor needs to be updated with actual values. */ @@ -15962,7 +21664,7 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf we are connecting to the default device we want to do automatic stream routing when the device is disabled or unplugged. Otherwise we want to just stop the device outright and let the application handle it. */ -#ifdef MA_WIN32_DESKTOP +#if defined(MA_WIN32_DESKTOP) || defined(MA_WIN32_GDK) if (pConfig->wasapi.noAutoStreamRouting == MA_FALSE) { if ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.pDeviceID == NULL) { pDevice->wasapi.allowCaptureAutoStreamRouting = MA_TRUE; @@ -15975,7 +21677,8 @@ static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_conf hr = ma_CoCreateInstance(pDevice->pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { ma_device_uninit__wasapi(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator."); + return ma_result_from_HRESULT(hr); } pDevice->wasapi.notificationClient.lpVtbl = (void*)&g_maNotificationCientVtbl; @@ -16036,16 +21739,16 @@ static ma_result ma_device__get_available_frames__wasapi(ma_device* pDevice, ma_ } if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { - *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesPlayback - paddingFramesCount; + *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesPlayback - paddingFramesCount; } else { *pFrameCount = paddingFramesCount; } } else { /* Exclusive mode. */ if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { - *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesPlayback; + *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesPlayback; } else { - *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesCapture; + *pFrameCount = pDevice->wasapi.actualBufferSizeInFramesCapture; } } @@ -16065,12 +21768,14 @@ static ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type de result = ma_device_reinit__wasapi(pDevice, deviceType); if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Reinitializing device after route change failed.\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WASAPI] Reinitializing device after route change failed.\n"); return result; } ma_device__post_init_setup(pDevice, deviceType); + ma_device__on_notification_rerouted(pDevice); + return MA_SUCCESS; } @@ -16083,14 +21788,21 @@ static ma_result ma_device_start__wasapi(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device."); + return ma_result_from_HRESULT(hr); } c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_TRUE); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* No need to do anything for playback as that'll be started automatically in the data loop. */ + hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device."); + return ma_result_from_HRESULT(hr); + } + + c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); } return MA_SUCCESS; @@ -16106,13 +21818,23 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device."); + return ma_result_from_HRESULT(hr); } /* The audio client needs to be reset otherwise restarting will fail. */ hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal capture device."); + return ma_result_from_HRESULT(hr); + } + + /* If we have a mapped buffer we need to release it. */ + if (pDevice->wasapi.pMappedBufferCapture != NULL) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; } c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_FALSE); @@ -16125,7 +21847,7 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) */ if (c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { /* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */ - DWORD waitTime = pDevice->wasapi.actualPeriodSizeInFramesPlayback / pDevice->playback.internalSampleRate; + DWORD waitTime = pDevice->wasapi.actualBufferSizeInFramesPlayback / pDevice->playback.internalSampleRate; if (pDevice->playback.shareMode == ma_share_mode_exclusive) { WaitForSingleObject(pDevice->wasapi.hEventPlayback, waitTime); @@ -16138,7 +21860,7 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) break; } - if (framesAvailablePlayback >= pDevice->wasapi.actualPeriodSizeInFramesPlayback) { + if (framesAvailablePlayback >= pDevice->wasapi.actualBufferSizeInFramesPlayback) { break; } @@ -16159,13 +21881,22 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal playback device."); + return ma_result_from_HRESULT(hr); } /* The audio client needs to be reset otherwise restarting will fail. */ hr = ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to reset internal playback device."); + return ma_result_from_HRESULT(hr); + } + + if (pDevice->wasapi.pMappedBufferPlayback != NULL) { + ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; } c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_FALSE); @@ -16179,508 +21910,237 @@ static ma_result ma_device_stop__wasapi(ma_device* pDevice) #define MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS 5000 #endif -static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) +static ma_result ma_device_read__wasapi(ma_device* pDevice, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) { - ma_result result; - HRESULT hr; - ma_bool32 exitLoop = MA_FALSE; - ma_uint32 framesWrittenToPlaybackDevice = 0; - ma_uint32 mappedDeviceBufferSizeInFramesCapture = 0; - ma_uint32 mappedDeviceBufferSizeInFramesPlayback = 0; - ma_uint32 mappedDeviceBufferFramesRemainingCapture = 0; - ma_uint32 mappedDeviceBufferFramesRemainingPlayback = 0; - BYTE* pMappedDeviceBufferCapture = NULL; - BYTE* pMappedDeviceBufferPlayback = NULL; - ma_uint32 bpfCaptureDevice = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 bpfPlaybackDevice = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 bpfCaptureClient = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 bpfPlaybackClient = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint8 inputDataInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 inputDataInClientFormatCap = 0; - ma_uint8 outputDataInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 outputDataInClientFormatCap = 0; - ma_uint32 outputDataInClientFormatCount = 0; - ma_uint32 outputDataInClientFormatConsumed = 0; - ma_uint32 periodSizeInFramesCapture = 0; + ma_result result = MA_SUCCESS; + ma_uint32 totalFramesProcessed = 0; - MA_ASSERT(pDevice != NULL); + /* + When reading, we need to get a buffer and process all of it before releasing it. Because the + frame count (frameCount) can be different to the size of the buffer, we'll need to cache the + pointer to the buffer. + */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - periodSizeInFramesCapture = pDevice->capture.internalPeriodSizeInFrames; - inputDataInClientFormatCap = sizeof(inputDataInClientFormat) / bpfCaptureClient; - } + /* Keep running until we've processed the requested number of frames. */ + while (ma_device_get_state(pDevice) == ma_device_state_started && totalFramesProcessed < frameCount) { + ma_uint32 framesRemaining = frameCount - totalFramesProcessed; - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - outputDataInClientFormatCap = sizeof(outputDataInClientFormat) / bpfPlaybackClient; - } + /* If we have a mapped data buffer, consume that first. */ + if (pDevice->wasapi.pMappedBufferCapture != NULL) { + /* We have a cached data pointer so consume that before grabbing another one from WASAPI. */ + ma_uint32 framesToProcessNow = framesRemaining; + if (framesToProcessNow > pDevice->wasapi.mappedBufferCaptureLen) { + framesToProcessNow = pDevice->wasapi.mappedBufferCaptureLen; + } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - ma_uint32 framesAvailableCapture; - ma_uint32 framesAvailablePlayback; - DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ + /* Now just copy the data over to the output buffer. */ + ma_copy_pcm_frames( + ma_offset_pcm_frames_ptr(pFrames, totalFramesProcessed, pDevice->capture.internalFormat, pDevice->capture.internalChannels), + ma_offset_pcm_frames_const_ptr(pDevice->wasapi.pMappedBufferCapture, pDevice->wasapi.mappedBufferCaptureCap - pDevice->wasapi.mappedBufferCaptureLen, pDevice->capture.internalFormat, pDevice->capture.internalChannels), + framesToProcessNow, + pDevice->capture.internalFormat, pDevice->capture.internalChannels + ); - /* The process is to map the playback buffer and fill it as quickly as possible from input data. */ - if (pMappedDeviceBufferPlayback == NULL) { - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); - if (result != MA_SUCCESS) { - return result; - } + totalFramesProcessed += framesToProcessNow; + pDevice->wasapi.mappedBufferCaptureLen -= framesToProcessNow; - /* In exclusive mode, the frame count needs to exactly match the value returned by GetCurrentPadding(). */ - if (pDevice->playback.shareMode != ma_share_mode_exclusive) { - if (framesAvailablePlayback > pDevice->wasapi.periodSizeInFramesPlayback) { - framesAvailablePlayback = pDevice->wasapi.periodSizeInFramesPlayback; - } - } + /* If the data buffer has been fully consumed we need to release it. */ + if (pDevice->wasapi.mappedBufferCaptureLen == 0) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + } + } else { + /* We don't have any cached data pointer, so grab another one. */ + HRESULT hr; + DWORD flags; - /* We're ready to map the playback device's buffer. We don't release this until it's been entirely filled. */ - hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - mappedDeviceBufferSizeInFramesPlayback = framesAvailablePlayback; - mappedDeviceBufferFramesRemainingPlayback = framesAvailablePlayback; - } - - if (mappedDeviceBufferFramesRemainingPlayback > 0) { - /* At this point we should have a buffer available for output. We need to keep writing input samples to it. */ - for (;;) { - /* Try grabbing some captured data if we haven't already got a mapped buffer. */ - if (pMappedDeviceBufferCapture == NULL) { - if (pDevice->capture.shareMode == ma_share_mode_shared) { - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } - - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailableCapture); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - /* Wait for more if nothing is available. */ - if (framesAvailableCapture == 0) { - /* In exclusive mode we waited at the top. */ - if (pDevice->capture.shareMode != ma_share_mode_shared) { - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } - - continue; - } - - /* Getting here means there's data available for writing to the output device. */ - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - - /* Overrun detection. */ - if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { - /* Glitched. Probably due to an overrun. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); - - /* - Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment - by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the - last period. - */ - if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Synchronizing capture stream. "); - do - { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - if (FAILED(hr)) { - break; - } - - framesAvailableCapture -= mappedDeviceBufferSizeInFramesCapture; - - if (framesAvailableCapture > 0) { - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - } else { - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferSizeInFramesCapture = 0; - } - } while (framesAvailableCapture > periodSizeInFramesCapture); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); - } - } else { - #ifdef MA_DEBUG_OUTPUT - if (flagsCapture != 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld\n", flagsCapture); - } - #endif - } - - mappedDeviceBufferFramesRemainingCapture = mappedDeviceBufferSizeInFramesCapture; - } - - - /* At this point we should have both input and output data available. We now need to convert the data and post it to the client. */ - for (;;) { - BYTE* pRunningDeviceBufferCapture; - BYTE* pRunningDeviceBufferPlayback; - ma_uint32 framesToProcess; - ma_uint32 framesProcessed; - - pRunningDeviceBufferCapture = pMappedDeviceBufferCapture + ((mappedDeviceBufferSizeInFramesCapture - mappedDeviceBufferFramesRemainingCapture ) * bpfCaptureDevice); - pRunningDeviceBufferPlayback = pMappedDeviceBufferPlayback + ((mappedDeviceBufferSizeInFramesPlayback - mappedDeviceBufferFramesRemainingPlayback) * bpfPlaybackDevice); - - /* There may be some data sitting in the converter that needs to be processed first. Once this is exhaused, run the data callback again. */ - if (!pDevice->playback.converter.isPassthrough && outputDataInClientFormatConsumed < outputDataInClientFormatCount) { - ma_uint64 convertedFrameCountClient = (outputDataInClientFormatCount - outputDataInClientFormatConsumed); - ma_uint64 convertedFrameCountDevice = mappedDeviceBufferFramesRemainingPlayback; - void* pConvertedFramesClient = outputDataInClientFormat + (outputDataInClientFormatConsumed * bpfPlaybackClient); - void* pConvertedFramesDevice = pRunningDeviceBufferPlayback; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pConvertedFramesClient, &convertedFrameCountClient, pConvertedFramesDevice, &convertedFrameCountDevice); - if (result != MA_SUCCESS) { - break; - } - - outputDataInClientFormatConsumed += (ma_uint32)convertedFrameCountClient; /* Safe cast. */ - mappedDeviceBufferFramesRemainingPlayback -= (ma_uint32)convertedFrameCountDevice; /* Safe cast. */ - - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; - } - } - - /* - Getting here means we need to fire the callback. If format conversion is unnecessary, we can optimize this by passing the pointers to the internal - buffers directly to the callback. - */ - if (pDevice->capture.converter.isPassthrough && pDevice->playback.converter.isPassthrough) { - /* Optimal path. We can pass mapped pointers directly to the callback. */ - framesToProcess = ma_min(mappedDeviceBufferFramesRemainingCapture, mappedDeviceBufferFramesRemainingPlayback); - framesProcessed = framesToProcess; - - ma_device__on_data(pDevice, pRunningDeviceBufferPlayback, pRunningDeviceBufferCapture, framesToProcess); - - mappedDeviceBufferFramesRemainingCapture -= framesProcessed; - mappedDeviceBufferFramesRemainingPlayback -= framesProcessed; - - if (mappedDeviceBufferFramesRemainingCapture == 0) { - break; /* Exhausted input data. */ - } - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; /* Exhausted output data. */ - } - } else if (pDevice->capture.converter.isPassthrough) { - /* The input buffer is a passthrough, but the playback buffer requires a conversion. */ - framesToProcess = ma_min(mappedDeviceBufferFramesRemainingCapture, outputDataInClientFormatCap); - framesProcessed = framesToProcess; - - ma_device__on_data(pDevice, outputDataInClientFormat, pRunningDeviceBufferCapture, framesToProcess); - outputDataInClientFormatCount = framesProcessed; - outputDataInClientFormatConsumed = 0; - - mappedDeviceBufferFramesRemainingCapture -= framesProcessed; - if (mappedDeviceBufferFramesRemainingCapture == 0) { - break; /* Exhausted input data. */ - } - } else if (pDevice->playback.converter.isPassthrough) { - /* The input buffer requires conversion, the playback buffer is passthrough. */ - ma_uint64 capturedDeviceFramesToProcess = mappedDeviceBufferFramesRemainingCapture; - ma_uint64 capturedClientFramesToProcess = ma_min(inputDataInClientFormatCap, mappedDeviceBufferFramesRemainingPlayback); - - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningDeviceBufferCapture, &capturedDeviceFramesToProcess, inputDataInClientFormat, &capturedClientFramesToProcess); - if (result != MA_SUCCESS) { - break; - } - - if (capturedClientFramesToProcess == 0) { - break; - } - - ma_device__on_data(pDevice, pRunningDeviceBufferPlayback, inputDataInClientFormat, (ma_uint32)capturedClientFramesToProcess); /* Safe cast. */ - - mappedDeviceBufferFramesRemainingCapture -= (ma_uint32)capturedDeviceFramesToProcess; - mappedDeviceBufferFramesRemainingPlayback -= (ma_uint32)capturedClientFramesToProcess; - } else { - ma_uint64 capturedDeviceFramesToProcess = mappedDeviceBufferFramesRemainingCapture; - ma_uint64 capturedClientFramesToProcess = ma_min(inputDataInClientFormatCap, outputDataInClientFormatCap); - - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningDeviceBufferCapture, &capturedDeviceFramesToProcess, inputDataInClientFormat, &capturedClientFramesToProcess); - if (result != MA_SUCCESS) { - break; - } - - if (capturedClientFramesToProcess == 0) { - break; - } - - ma_device__on_data(pDevice, outputDataInClientFormat, inputDataInClientFormat, (ma_uint32)capturedClientFramesToProcess); - - mappedDeviceBufferFramesRemainingCapture -= (ma_uint32)capturedDeviceFramesToProcess; - outputDataInClientFormatCount = (ma_uint32)capturedClientFramesToProcess; - outputDataInClientFormatConsumed = 0; - } - } - - - /* If at this point we've run out of capture data we need to release the buffer. */ - if (mappedDeviceBufferFramesRemainingCapture == 0 && pMappedDeviceBufferCapture != NULL) { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferFramesRemainingCapture = 0; - mappedDeviceBufferSizeInFramesCapture = 0; - } - - /* Get out of this loop if we're run out of room in the playback buffer. */ - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; - } - } - } - - - /* If at this point we've run out of data we need to release the buffer. */ - if (mappedDeviceBufferFramesRemainingPlayback == 0 && pMappedDeviceBufferPlayback != NULL) { - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - framesWrittenToPlaybackDevice += mappedDeviceBufferSizeInFramesPlayback; - - pMappedDeviceBufferPlayback = NULL; - mappedDeviceBufferFramesRemainingPlayback = 0; - mappedDeviceBufferSizeInFramesPlayback = 0; - } - - if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { - ma_uint32 startThreshold = pDevice->playback.internalPeriodSizeInFrames * 1; - - /* Prevent a deadlock. If we don't clamp against the actual buffer size we'll never end up starting the playback device which will result in a deadlock. */ - if (startThreshold > pDevice->wasapi.actualPeriodSizeInFramesPlayback) { - startThreshold = pDevice->wasapi.actualPeriodSizeInFramesPlayback; - } - - if (pDevice->playback.shareMode == ma_share_mode_exclusive || framesWrittenToPlaybackDevice >= startThreshold) { - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - if (FAILED(hr)) { - ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); - } - - c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); - } - } - - /* Make sure the device has started before waiting. */ - if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } break; - - - - case ma_device_type_capture: - case ma_device_type_loopback: - { - ma_uint32 framesAvailableCapture; - DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ - - /* Wait for data to become available first. */ - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - /* - For capture we can terminate here because it probably means the microphone just isn't delivering data for whatever reason, but - for loopback is most likely means nothing is actually playing. We want to keep trying in this situation. - */ - if (pDevice->type == ma_device_type_loopback) { - continue; /* Keep waiting in loopback mode. */ - } else { - exitLoop = MA_TRUE; - break; /* Wait failed. */ - } - } - - /* See how many frames are available. Since we waited at the top, I don't think this should ever return 0. I'm checking for this anyway. */ - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailableCapture); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - if (framesAvailableCapture < pDevice->wasapi.periodSizeInFramesCapture) { - continue; /* Nothing available. Keep waiting. */ - } - - /* Map the data buffer in preparation for sending to the client. */ - mappedDeviceBufferSizeInFramesCapture = framesAvailableCapture; - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } + /* First just ask WASAPI for a data buffer. If it's not available, we'll wait for more. */ + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pDevice->wasapi.pMappedBufferCapture, &pDevice->wasapi.mappedBufferCaptureCap, &flags, NULL, NULL); + if (hr == S_OK) { + /* We got a data buffer. Continue to the next loop iteration which will then read from the mapped pointer. */ /* Overrun detection. */ - if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { + if ((flags & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { /* Glitched. Probably due to an overrun. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Data discontinuity (possible overrun). Attempting recovery. mappedBufferCaptureCap=%d\n", pDevice->wasapi.mappedBufferCaptureCap); /* - Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment - by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the - last period. + If we got an overrun it probably means we're straddling the end of the buffer. In order to prevent + a never-ending sequence of glitches we're going to recover by completely clearing out the capture + buffer. */ - if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Synchronizing capture stream. "); - do - { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + { + ma_uint32 iterationCount = 4; /* Safety to prevent an infinite loop. */ + ma_uint32 i; + + for (i = 0; i < iterationCount; i += 1) { + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); if (FAILED(hr)) { break; } - framesAvailableCapture -= mappedDeviceBufferSizeInFramesCapture; - - if (framesAvailableCapture > 0) { - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - } else { - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferSizeInFramesCapture = 0; + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pDevice->wasapi.pMappedBufferCapture, &pDevice->wasapi.mappedBufferCaptureCap, &flags, NULL, NULL); + if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || FAILED(hr)) { + break; } - } while (framesAvailableCapture > periodSizeInFramesCapture); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); + } } + + /* We should not have a valid buffer at this point so make sure everything is empty. */ + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; } else { - #ifdef MA_DEBUG_OUTPUT - if (flagsCapture != 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld\n", flagsCapture); - } - #endif - } + /* The data is clean. */ + pDevice->wasapi.mappedBufferCaptureLen = pDevice->wasapi.mappedBufferCaptureCap; - /* We should have a buffer at this point, but let's just do a sanity check anyway. */ - if (mappedDeviceBufferSizeInFramesCapture > 0 && pMappedDeviceBufferCapture != NULL) { - ma_device__send_frames_to_client(pDevice, mappedDeviceBufferSizeInFramesCapture, pMappedDeviceBufferCapture); - - /* At this point we're done with the buffer. */ - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - pMappedDeviceBufferCapture = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ - mappedDeviceBufferSizeInFramesCapture = 0; - if (FAILED(hr)) { - ma_post_log_message(ma_device_get_context(pDevice), pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device."); - exitLoop = MA_TRUE; - break; + if (flags != 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[WASAPI] Capture Flags: %ld\n", flags); } } - } break; + continue; + } else { + if (hr == MA_AUDCLNT_S_BUFFER_EMPTY || hr == MA_AUDCLNT_E_BUFFER_ERROR) { + /* + No data is available. We need to wait for more. There's two situations to consider + here. The first is normal capture mode. If this times out it probably means the + microphone isn't delivering data for whatever reason. In this case we'll just + abort the read and return whatever we were able to get. The other situations is + loopback mode, in which case a timeout probably just means the nothing is playing + through the speakers. + */ + if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + if (pDevice->type == ma_device_type_loopback) { + continue; /* Keep waiting in loopback mode. */ + } else { + result = MA_ERROR; + break; /* Wait failed. */ + } + } - - case ma_device_type_playback: - { - ma_uint32 framesAvailablePlayback; - - /* Check how much space is available. If this returns 0 we just keep waiting. */ - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; + /* At this point we should be able to loop back to the start of the loop and try retrieving a data buffer again. */ + } else { + /* An error occured and we need to abort. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for reading from the device. HRESULT = %d. Stopping device.\n", (int)hr); + result = ma_result_from_HRESULT(hr); break; } - - if (framesAvailablePlayback >= pDevice->wasapi.periodSizeInFramesPlayback) { - /* Map a the data buffer in preparation for the callback. */ - hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - /* We should have a buffer at this point. */ - ma_device__read_frames_from_client(pDevice, framesAvailablePlayback, pMappedDeviceBufferPlayback); - - /* At this point we're done writing to the device and we just need to release the buffer. */ - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, 0); - pMappedDeviceBufferPlayback = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ - mappedDeviceBufferSizeInFramesPlayback = 0; - - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - framesWrittenToPlaybackDevice += framesAvailablePlayback; - } - - if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); - } - - /* Make sure we don't wait on the event before we've started the device or we may end up deadlocking. */ - if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { - exitLoop = MA_TRUE; - break; /* Wait failed. Probably timed out. */ - } - } break; - - default: return MA_INVALID_ARGS; + } } } - /* Here is where the device needs to be stopped. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - /* Any mapped buffers need to be released. */ - if (pMappedDeviceBufferCapture != NULL) { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + /* + If we were unable to process the entire requested frame count, but we still have a mapped buffer, + there's a good chance either an error occurred or the device was stopped mid-read. In this case + we'll need to make sure the buffer is released. + */ + if (totalFramesProcessed < frameCount && pDevice->wasapi.pMappedBufferCapture != NULL) { + ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, pDevice->wasapi.mappedBufferCaptureCap); + pDevice->wasapi.pMappedBufferCapture = NULL; + pDevice->wasapi.mappedBufferCaptureCap = 0; + pDevice->wasapi.mappedBufferCaptureLen = 0; + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesProcessed; + } + + return result; +} + +static ma_result ma_device_write__wasapi(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +{ + ma_result result = MA_SUCCESS; + ma_uint32 totalFramesProcessed = 0; + + /* Keep writing to the device until it's stopped or we've consumed all of our input. */ + while (ma_device_get_state(pDevice) == ma_device_state_started && totalFramesProcessed < frameCount) { + ma_uint32 framesRemaining = frameCount - totalFramesProcessed; + + /* + We're going to do this in a similar way to capture. We'll first check if the cached data pointer + is valid, and if so, read from that. Otherwise We will call IAudioRenderClient_GetBuffer() with + a requested buffer size equal to our actual period size. If it returns AUDCLNT_E_BUFFER_TOO_LARGE + it means we need to wait for some data to become available. + */ + if (pDevice->wasapi.pMappedBufferPlayback != NULL) { + /* We still have some space available in the mapped data buffer. Write to it. */ + ma_uint32 framesToProcessNow = framesRemaining; + if (framesToProcessNow > (pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen)) { + framesToProcessNow = (pDevice->wasapi.mappedBufferPlaybackCap - pDevice->wasapi.mappedBufferPlaybackLen); + } + + /* Now just copy the data over to the output buffer. */ + ma_copy_pcm_frames( + ma_offset_pcm_frames_ptr(pDevice->wasapi.pMappedBufferPlayback, pDevice->wasapi.mappedBufferPlaybackLen, pDevice->playback.internalFormat, pDevice->playback.internalChannels), + ma_offset_pcm_frames_const_ptr(pFrames, totalFramesProcessed, pDevice->playback.internalFormat, pDevice->playback.internalChannels), + framesToProcessNow, + pDevice->playback.internalFormat, pDevice->playback.internalChannels + ); + + totalFramesProcessed += framesToProcessNow; + pDevice->wasapi.mappedBufferPlaybackLen += framesToProcessNow; + + /* If the data buffer has been fully consumed we need to release it. */ + if (pDevice->wasapi.mappedBufferPlaybackLen == pDevice->wasapi.mappedBufferPlaybackCap) { + ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, pDevice->wasapi.mappedBufferPlaybackCap, 0); + pDevice->wasapi.pMappedBufferPlayback = NULL; + pDevice->wasapi.mappedBufferPlaybackCap = 0; + pDevice->wasapi.mappedBufferPlaybackLen = 0; + + /* + In exclusive mode we need to wait here. Exclusive mode is weird because GetBuffer() never + seems to return AUDCLNT_E_BUFFER_TOO_LARGE, which is what we normally use to determine + whether or not we need to wait for more data. + */ + if (pDevice->playback.shareMode == ma_share_mode_exclusive) { + if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + result = MA_ERROR; + break; /* Wait failed. Probably timed out. */ + } + } + } + } else { + /* We don't have a mapped data buffer so we'll need to get one. */ + HRESULT hr; + ma_uint32 bufferSizeInFrames; + + /* Special rules for exclusive mode. */ + if (pDevice->playback.shareMode == ma_share_mode_exclusive) { + bufferSizeInFrames = pDevice->wasapi.actualBufferSizeInFramesPlayback; + } else { + bufferSizeInFrames = pDevice->wasapi.periodSizeInFramesPlayback; + } + + hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, bufferSizeInFrames, (BYTE**)&pDevice->wasapi.pMappedBufferPlayback); + if (hr == S_OK) { + /* We have data available. */ + pDevice->wasapi.mappedBufferPlaybackCap = bufferSizeInFrames; + pDevice->wasapi.mappedBufferPlaybackLen = 0; + } else { + if (hr == MA_AUDCLNT_E_BUFFER_TOO_LARGE || hr == MA_AUDCLNT_E_BUFFER_ERROR) { + /* Not enough data available. We need to wait for more. */ + if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + result = MA_ERROR; + break; /* Wait failed. Probably timed out. */ + } + } else { + /* Some error occurred. We'll need to abort. */ + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device. HRESULT = %d. Stopping device.\n", (int)hr); + result = ma_result_from_HRESULT(hr); + break; + } + } } } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* Any mapped buffers need to be released. */ - if (pMappedDeviceBufferPlayback != NULL) { - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); - } + if (pFramesWritten != NULL) { + *pFramesWritten = totalFramesProcessed; } - return MA_SUCCESS; + return result; } static ma_result ma_device_data_loop_wakeup__wasapi(ma_device* pDevice) @@ -16825,9 +22285,9 @@ static ma_result ma_context_init__wasapi(ma_context* pContext, const ma_context_ pCallbacks->onDeviceUninit = ma_device_uninit__wasapi; pCallbacks->onDeviceStart = ma_device_start__wasapi; pCallbacks->onDeviceStop = ma_device_stop__wasapi; - pCallbacks->onDeviceRead = NULL; /* Not used. Reading is done manually in the audio thread. */ - pCallbacks->onDeviceWrite = NULL; /* Not used. Writing is done manually in the audio thread. */ - pCallbacks->onDeviceDataLoop = ma_device_data_loop__wasapi; + pCallbacks->onDeviceRead = ma_device_read__wasapi; + pCallbacks->onDeviceWrite = ma_device_write__wasapi; + pCallbacks->onDeviceDataLoop = NULL; pCallbacks->onDeviceDataLoopWakeup = ma_device_data_loop_wakeup__wasapi; return MA_SUCCESS; @@ -17089,9 +22549,9 @@ struct ma_IDirectSoundCapture { ma_IDirectSoundCaptureVtbl* lpVtbl; }; -static MA_INLINE HRESULT ma_IDirectSoundCapture_QueryInterface(ma_IDirectSoundCapture* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } -static MA_INLINE ULONG ma_IDirectSoundCapture_AddRef(ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->AddRef(pThis); } -static MA_INLINE ULONG ma_IDirectSoundCapture_Release(ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->Release(pThis); } +static MA_INLINE HRESULT ma_IDirectSoundCapture_QueryInterface (ma_IDirectSoundCapture* pThis, const IID* const riid, void** ppObject) { return pThis->lpVtbl->QueryInterface(pThis, riid, ppObject); } +static MA_INLINE ULONG ma_IDirectSoundCapture_AddRef (ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->AddRef(pThis); } +static MA_INLINE ULONG ma_IDirectSoundCapture_Release (ma_IDirectSoundCapture* pThis) { return pThis->lpVtbl->Release(pThis); } static MA_INLINE HRESULT ma_IDirectSoundCapture_CreateCaptureBuffer(ma_IDirectSoundCapture* pThis, const MA_DSCBUFFERDESC* pDSCBufferDesc, ma_IDirectSoundCaptureBuffer** ppDSCBuffer, void* pUnkOuter) { return pThis->lpVtbl->CreateCaptureBuffer(pThis, pDSCBufferDesc, ppDSCBuffer, pUnkOuter); } static MA_INLINE HRESULT ma_IDirectSoundCapture_GetCaps (ma_IDirectSoundCapture* pThis, MA_DSCCAPS* pDSCCaps) { return pThis->lpVtbl->GetCaps(pThis, pDSCCaps); } static MA_INLINE HRESULT ma_IDirectSoundCapture_Initialize (ma_IDirectSoundCapture* pThis, const GUID* pGuidDevice) { return pThis->lpVtbl->Initialize(pThis, pGuidDevice); } @@ -17250,7 +22710,8 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma pDirectSound = NULL; if (FAILED(((ma_DirectSoundCreateProc)pContext->dsound.DirectSoundCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSound, NULL))) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCreate() failed for playback device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCreate() failed for playback device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } /* The cooperative level must be set before doing anything else. */ @@ -17261,7 +22722,8 @@ static ma_result ma_context_create_IDirectSound__dsound(ma_context* pContext, ma hr = ma_IDirectSound_SetCooperativeLevel(pDirectSound, hWnd, (shareMode == ma_share_mode_exclusive) ? MA_DSSCL_EXCLUSIVE : MA_DSSCL_PRIORITY); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device."); + return ma_result_from_HRESULT(hr); } *ppDirectSound = pDirectSound; @@ -17286,7 +22748,8 @@ static ma_result ma_context_create_IDirectSoundCapture__dsound(ma_context* pCont hr = ((ma_DirectSoundCaptureCreateProc)pContext->dsound.DirectSoundCaptureCreate)((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, &pDirectSoundCapture, NULL); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCaptureCreate() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] DirectSoundCaptureCreate() failed for capture device."); + return ma_result_from_HRESULT(hr); } *ppDirectSoundCapture = pDirectSoundCapture; @@ -17317,7 +22780,8 @@ static ma_result ma_context_get_format_info_for_IDirectSoundCapture__dsound(ma_c caps.dwSize = sizeof(caps); hr = ma_IDirectSoundCapture_GetCaps(pDirectSoundCapture, &caps); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_GetCaps() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_GetCaps() failed for capture device."); + return ma_result_from_HRESULT(hr); } if (pChannels) { @@ -17552,7 +23016,8 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev caps.dwSize = sizeof(caps); hr = ma_IDirectSound_GetCaps(pDirectSound, &caps); if (FAILED(hr)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); + return ma_result_from_HRESULT(hr); } @@ -17628,13 +23093,13 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev /* The format is always an integer format and is based on the bits per sample. */ if (bitsPerSample == 8) { - pDeviceInfo->formats[0] = ma_format_u8; + pDeviceInfo->nativeDataFormats[0].format = ma_format_u8; } else if (bitsPerSample == 16) { - pDeviceInfo->formats[0] = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; } else if (bitsPerSample == 24) { - pDeviceInfo->formats[0] = ma_format_s24; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s24; } else if (bitsPerSample == 32) { - pDeviceInfo->formats[0] = ma_format_s32; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s32; } else { return MA_FORMAT_NOT_SUPPORTED; } @@ -17727,8 +23192,11 @@ static ma_result ma_config_to_WAVEFORMATEXTENSIBLE(ma_format format, ma_uint32 c static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__dsound(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) { - /* DirectSound has a minimum period size of 20ms. */ - ma_uint32 minPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(20, nativeSampleRate); + /* + DirectSound has a minimum period size of 20ms. In practice, this doesn't seem to be enough for + reliable glitch-free processing so going to use 30ms instead. + */ + ma_uint32 minPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(30, nativeSampleRate); ma_uint32 periodSizeInFrames; periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, nativeSampleRate, performanceProfile); @@ -17799,7 +23267,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device."); + return ma_result_from_HRESULT(hr); } /* Get the _actual_ properties of the buffer. */ @@ -17807,7 +23276,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCaptureBuffer_GetFormat((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer."); + return ma_result_from_HRESULT(hr); } /* We can now start setting the output data formats. */ @@ -17833,7 +23303,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Second attempt at IDirectSoundCapture_CreateCaptureBuffer() failed for capture device."); + return ma_result_from_HRESULT(hr); } } @@ -17869,7 +23340,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDSPrimary, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } @@ -17879,7 +23351,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_GetCaps((ma_IDirectSound*)pDevice->dsound.pPlayback, &caps); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device."); + return ma_result_from_HRESULT(hr); } if (pDescriptorPlayback->channels == 0) { @@ -17921,7 +23394,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundBuffer_SetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)&wf); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to set format of playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to set format of playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } /* Get the _actual_ properties of the buffer. */ @@ -17929,7 +23403,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSoundBuffer_GetFormat((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)pActualFormat, sizeof(rawdata), NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer."); + return ma_result_from_HRESULT(hr); } /* We now have enough information to start setting some output properties. */ @@ -17971,7 +23446,8 @@ static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_conf hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDS, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer."); + return ma_result_from_HRESULT(hr); } /* DirectSound should give us a buffer exactly the size we asked for. */ @@ -18011,12 +23487,14 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) /* The first thing to do is start the capture device. The playback device is only started after the first period is written. */ if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - if (FAILED(ma_IDirectSoundCaptureBuffer_Start((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MA_DSCBSTART_LOOPING))) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed.", MA_FAILED_TO_START_BACKEND_DEVICE); + hr = ma_IDirectSoundCaptureBuffer_Start((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, MA_DSCBSTART_LOOPING); + if (FAILED(hr)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed."); + return ma_result_from_HRESULT(hr); } } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { switch (pDevice->type) { case ma_device_type_duplex: @@ -18065,7 +23543,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); + return ma_result_from_HRESULT(hr); } @@ -18091,7 +23570,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) outputFramesInClientFormatCount = (ma_uint32)clientCapturedFramesToProcess; mappedDeviceFramesProcessedCapture += (ma_uint32)deviceCapturedFramesToProcess; - ma_device__on_data(pDevice, outputFramesInClientFormat, inputFramesInClientFormat, (ma_uint32)clientCapturedFramesToProcess); + ma_device__handle_data_callback(pDevice, outputFramesInClientFormat, inputFramesInClientFormat, (ma_uint32)clientCapturedFramesToProcess); /* At this point we have input and output data in client format. All we need to do now is convert it to the output device format. This may take a few passes. */ for (;;) { @@ -18119,7 +23598,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Duplex/Playback) WARNING: Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback): Play cursor has moved in front of the write cursor (same loop iteration). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } else { @@ -18128,7 +23607,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Duplex/Playback) WARNING: Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback): Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } @@ -18140,7 +23619,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } else { @@ -18162,7 +23642,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -18178,7 +23659,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) silentPaddingInBytes = lockSizeInBytesPlayback; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Duplex/Playback) Playback buffer starved. availableBytesPlayback=%ld, silentPaddingInBytes=%ld\n", availableBytesPlayback, silentPaddingInBytes); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Duplex/Playback) Playback buffer starved. availableBytesPlayback=%ld, silentPaddingInBytes=%ld\n", availableBytesPlayback, silentPaddingInBytes); } } @@ -18204,7 +23685,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, framesWrittenThisIteration*bpfDevicePlayback, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -18223,7 +23705,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } @@ -18242,7 +23725,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) /* At this point we're done with the mapped portion of the capture buffer. */ hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); + return ma_result_from_HRESULT(hr); } prevReadCursorInBytesCapture = (lockOffsetInBytesCapture + mappedSizeInBytesCapture); } break; @@ -18292,20 +23776,20 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundCaptureBuffer_Lock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, lockOffsetInBytesCapture, lockSizeInBytesCapture, &pMappedDeviceBufferCapture, &mappedSizeInBytesCapture, NULL, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from capture device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); } - #ifdef MA_DEBUG_OUTPUT if (lockSizeInBytesCapture != mappedSizeInBytesCapture) { ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Capture) lockSizeInBytesCapture=%ld != mappedSizeInBytesCapture=%ld\n", lockSizeInBytesCapture, mappedSizeInBytesCapture); } - #endif ma_device__send_frames_to_client(pDevice, mappedSizeInBytesCapture/bpfDeviceCapture, pMappedDeviceBufferCapture); hr = ma_IDirectSoundCaptureBuffer_Unlock((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer, pMappedDeviceBufferCapture, mappedSizeInBytesCapture, NULL, 0); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from capture device after reading from the device."); + return ma_result_from_HRESULT(hr); } prevReadCursorInBytesCapture = lockOffsetInBytesCapture + mappedSizeInBytesCapture; @@ -18339,7 +23823,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) availableBytesPlayback += physicalPlayCursorInBytes; /* Wrap around. */ } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Playback) WARNING: Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Playback): Play cursor has moved in front of the write cursor (same loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } else { @@ -18348,7 +23832,7 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) availableBytesPlayback = physicalPlayCursorInBytes - virtualWriteCursorInBytesPlayback; } else { /* This is an error. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[DirectSound] (Playback) WARNING: Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[DirectSound] (Playback): Write cursor has moved behind the play cursor (different loop iterations). physicalPlayCursorInBytes=%ld, virtualWriteCursorInBytes=%ld.\n", physicalPlayCursorInBytes, virtualWriteCursorInBytesPlayback); availableBytesPlayback = 0; } } @@ -18359,7 +23843,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (availableBytesPlayback == 0 && !isPlaybackDeviceStarted) { hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } else { @@ -18380,7 +23865,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Lock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, lockOffsetInBytesPlayback, lockSizeInBytesPlayback, &pMappedDeviceBufferPlayback, &mappedSizeInBytesPlayback, NULL, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to map buffer from playback device in preparation for writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -18389,7 +23875,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, mappedSizeInBytesPlayback, NULL, 0); if (FAILED(hr)) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to unlock internal buffer from playback device after writing to the device."); + result = ma_result_from_HRESULT(hr); break; } @@ -18407,7 +23894,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (!isPlaybackDeviceStarted && framesWrittenToPlaybackDevice >= pDevice->playback.internalPeriodSizeInFrames) { hr = ma_IDirectSoundBuffer_Play((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0, 0, MA_DSBPLAY_LOOPING); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Play() failed."); + return ma_result_from_HRESULT(hr); } isPlaybackDeviceStarted = MA_TRUE; } @@ -18426,7 +23914,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { hr = ma_IDirectSoundCaptureBuffer_Stop((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed."); + return ma_result_from_HRESULT(hr); } } @@ -18474,7 +23963,8 @@ static ma_result ma_device_data_loop__dsound(ma_device* pDevice) hr = ma_IDirectSoundBuffer_Stop((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer); if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed.", ma_result_from_HRESULT(hr)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundBuffer_Stop() failed."); + return ma_result_from_HRESULT(hr); } ma_IDirectSoundBuffer_SetCurrentPosition((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, 0); @@ -18789,10 +24279,10 @@ static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, if (((MA_PFN_RegOpenKeyExA)pContext->win32.RegOpenKeyExA)(HKEY_LOCAL_MACHINE, keyStr, 0, KEY_READ, &hKey) == ERROR_SUCCESS) { BYTE nameFromReg[512]; DWORD nameFromRegSize = sizeof(nameFromReg); - result = ((MA_PFN_RegQueryValueExA)pContext->win32.RegQueryValueExA)(hKey, "Name", 0, NULL, (LPBYTE)nameFromReg, (LPDWORD)&nameFromRegSize); + LONG resultWin32 = ((MA_PFN_RegQueryValueExA)pContext->win32.RegQueryValueExA)(hKey, "Name", 0, NULL, (LPBYTE)nameFromReg, (LPDWORD)&nameFromRegSize); ((MA_PFN_RegCloseKey)pContext->win32.RegCloseKey)(hKey); - if (result == ERROR_SUCCESS) { + if (resultWin32 == ERROR_SUCCESS) { /* We have the value from the registry, so now we need to construct the name string. */ char name[1024]; if (ma_strcpy_s(name, sizeof(name), pDeviceInfo->name) == 0) { @@ -19000,7 +24490,7 @@ static ma_result ma_device_uninit__winmm(ma_device* pDevice) CloseHandle((HANDLE)pDevice->winmm.hEventPlayback); } - ma__free_from_callbacks(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); MA_ZERO_OBJECT(&pDevice->winmm); /* Safety. */ @@ -19085,7 +24575,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi pDescriptorCapture->format = ma_format_from_WAVEFORMATEX(&wf); pDescriptorCapture->channels = wf.nChannels; pDescriptorCapture->sampleRate = wf.nSamplesPerSec; - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); pDescriptorCapture->periodCount = pDescriptorCapture->periodCount; pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); } @@ -19096,7 +24586,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi MMRESULT resultMM; /* We use an event to know when a new fragment needs to be enqueued. */ - pDevice->winmm.hEventPlayback = (ma_handle)CreateEvent(NULL, TRUE, TRUE, NULL); + pDevice->winmm.hEventPlayback = (ma_handle)CreateEventW(NULL, TRUE, TRUE, NULL); if (pDevice->winmm.hEventPlayback == NULL) { errorMsg = "[WinMM] Failed to create event for fragment enqueing for the playback device.", errorCode = ma_result_from_GetLastError(GetLastError()); goto on_error; @@ -19123,7 +24613,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi pDescriptorPlayback->format = ma_format_from_WAVEFORMATEX(&wf); pDescriptorPlayback->channels = wf.nChannels; pDescriptorPlayback->sampleRate = wf.nSamplesPerSec; - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); pDescriptorPlayback->periodCount = pDescriptorPlayback->periodCount; pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); } @@ -19141,7 +24631,7 @@ static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_confi heapSize += sizeof(WAVEHDR)*pDescriptorPlayback->periodCount + (pDescriptorPlayback->periodSizeInFrames * pDescriptorPlayback->periodCount * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels)); } - pDevice->winmm._pHeapData = (ma_uint8*)ma__calloc_from_callbacks(heapSize, &pDevice->pContext->allocationCallbacks); + pDevice->winmm._pHeapData = (ma_uint8*)ma_calloc(heapSize, &pDevice->pContext->allocationCallbacks); if (pDevice->winmm._pHeapData == NULL) { errorMsg = "[WinMM] Failed to allocate memory for the intermediary buffer.", errorCode = MA_OUT_OF_MEMORY; goto on_error; @@ -19232,8 +24722,13 @@ on_error: ((MA_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevicePlayback); } - ma__free_from_callbacks(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, errorMsg, errorCode); + ma_free(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); + + if (errorMsg != NULL && errorMsg[0] != '\0') { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "%s", errorMsg); + } + + return errorCode; } static ma_result ma_device_start__winmm(ma_device* pDevice) @@ -19254,7 +24749,8 @@ static ma_result ma_device_start__winmm(ma_device* pDevice) for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture."); + return ma_result_from_MMRESULT(resultMM); } /* Make sure all of the buffers start out locked. We don't want to access them until the backend tells us we can. */ @@ -19264,7 +24760,8 @@ static ma_result ma_device_start__winmm(ma_device* pDevice) /* Capture devices need to be explicitly started, unlike playback devices. */ resultMM = ((MA_PFN_waveInStart)pDevice->pContext->winmm.waveInStart)((HWAVEIN)pDevice->winmm.hDeviceCapture); if (resultMM != MMSYSERR_NOERROR) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device."); + return ma_result_from_MMRESULT(resultMM); } } @@ -19288,7 +24785,7 @@ static ma_result ma_device_stop__winmm(ma_device* pDevice) resultMM = ((MA_PFN_waveInReset)pDevice->pContext->winmm.waveInReset)((HWAVEIN)pDevice->winmm.hDeviceCapture); if (resultMM != MMSYSERR_NOERROR) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] WARNING: Failed to reset capture device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset capture device."); } } @@ -19314,7 +24811,7 @@ static ma_result ma_device_stop__winmm(ma_device* pDevice) resultMM = ((MA_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevicePlayback); if (resultMM != MMSYSERR_NOERROR) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] WARNING: Failed to reset playback device.", ma_result_from_MMRESULT(resultMM)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_WARNING, "[WinMM] WARNING: Failed to reset playback device."); } } @@ -19369,7 +24866,7 @@ static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFram resultMM = ((MA_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &pWAVEHDR[pDevice->winmm.iNextHeaderPlayback], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { result = ma_result_from_MMRESULT(resultMM); - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveOutWrite() failed."); break; } @@ -19401,7 +24898,7 @@ static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFram } /* If the device has been stopped we need to break. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { break; } } @@ -19458,7 +24955,7 @@ static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_ resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[pDevice->winmm.iNextHeaderCapture], sizeof(WAVEHDR)); if (resultMM != MMSYSERR_NOERROR) { result = ma_result_from_MMRESULT(resultMM); - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[WinMM] waveInAddBuffer() failed."); break; } @@ -19490,7 +24987,7 @@ static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_ } /* If the device has been stopped we need to break. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { break; } } @@ -20206,7 +25703,8 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s } if (!isDeviceOpen) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } } else { /* @@ -20254,7 +25752,8 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s } if (resultALSA < 0) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed.", ma_result_from_errno(-resultALSA)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_open() failed."); + return ma_result_from_errno(-resultALSA); } } @@ -20325,9 +25824,8 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu goto next_device; /* The device has already been enumerated. Move on to the next one. */ } else { /* The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again. */ - size_t oldCapacity = sizeof(*pUniqueIDs) * uniqueIDCount; size_t newCapacity = sizeof(*pUniqueIDs) * (uniqueIDCount + 1); - ma_device_id* pNewUniqueIDs = (ma_device_id*)ma__realloc_from_callbacks(pUniqueIDs, newCapacity, oldCapacity, &pContext->allocationCallbacks); + ma_device_id* pNewUniqueIDs = (ma_device_id*)ma_realloc(pUniqueIDs, newCapacity, &pContext->allocationCallbacks); if (pNewUniqueIDs == NULL) { goto next_device; /* Failed to allocate memory. */ } @@ -20392,10 +25890,11 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu /* Some devices are both playback and capture, but they are only enumerated by ALSA once. We need to fire the callback - again for the other device type in this case. We do this for known devices. + again for the other device type in this case. We do this for known devices and where the IOID hint is NULL, which + means both Input and Output. */ if (cbResult) { - if (ma_is_common_device_name__alsa(NAME)) { + if (ma_is_common_device_name__alsa(NAME) || IOID == NULL) { if (deviceType == ma_device_type_playback) { if (!ma_is_capture_device_blacklisted__alsa(NAME)) { cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); @@ -20424,7 +25923,7 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu } } - ma__free_from_callbacks(pUniqueIDs, &pContext->allocationCallbacks); + ma_free(pUniqueIDs, &pContext->allocationCallbacks); ((ma_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); ma_mutex_unlock(&pContext->alsa.internalDeviceEnumLock); @@ -20548,7 +26047,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic } /* We need to initialize a HW parameters object in order to know what formats are supported. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); + pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); if (pHWParams == NULL) { ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_OUT_OF_MEMORY; @@ -20556,9 +26055,10 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic resultALSA = ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma_free(pHWParams, &pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); + return ma_result_from_errno(-resultALSA); } /* @@ -20642,7 +26142,7 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic } } - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma_free(pHWParams, &pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_SUCCESS; @@ -20712,16 +26212,19 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* Hardware parameters. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_hw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); + pHWParams = (ma_snd_pcm_hw_params_t*)ma_calloc(((ma_snd_pcm_hw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_hw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); if (pHWParams == NULL) { + ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for hardware parameters."); return MA_OUT_OF_MEMORY; } resultALSA = ((ma_snd_pcm_hw_params_any_proc)pDevice->pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed."); + return ma_result_from_errno(-resultALSA); } /* MMAP Mode. Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. */ @@ -20739,9 +26242,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic if (!isUsingMMap) { resultALSA = ((ma_snd_pcm_hw_params_set_access_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed."); + return ma_result_from_errno(-resultALSA); } } @@ -20769,24 +26273,27 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic } if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats."); + return MA_FORMAT_NOT_SUPPORTED; } } resultALSA = ((ma_snd_pcm_hw_params_set_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, formatALSA); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed."); + return ma_result_from_errno(-resultALSA); } internalFormat = ma_format_from_alsa(formatALSA); if (internalFormat == ma_format_unknown) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio."); + return MA_FORMAT_NOT_SUPPORTED; } } @@ -20799,9 +26306,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_channels_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_channels_near)(pPCM, pHWParams, &channels); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed."); + return ma_result_from_errno(-resultALSA); } internalChannels = (ma_uint32)channels; @@ -20837,9 +26345,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_rate_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_rate_near)(pPCM, pHWParams, &sampleRate, 0); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed."); + return ma_result_from_errno(-resultALSA); } internalSampleRate = (ma_uint32)sampleRate; @@ -20851,9 +26360,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_periods_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_periods_near)(pPCM, pHWParams, &periods, NULL); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed."); + return ma_result_from_errno(-resultALSA); } internalPeriods = periods; @@ -20865,9 +26375,10 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic resultALSA = ((ma_snd_pcm_hw_params_set_buffer_size_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)(pPCM, pHWParams, &actualBufferSizeInFrames); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed."); + return ma_result_from_errno(-resultALSA); } internalPeriodSizeInFrames = actualBufferSizeInFrames / internalPeriods; @@ -20876,34 +26387,38 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* Apply hardware parameters. */ resultALSA = ((ma_snd_pcm_hw_params_proc)pDevice->pContext->alsa.snd_pcm_hw_params)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed."); + return ma_result_from_errno(-resultALSA); } - ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pHWParams, &pDevice->pContext->allocationCallbacks); pHWParams = NULL; /* Software parameters. */ - pSWParams = (ma_snd_pcm_sw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_sw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_sw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); + pSWParams = (ma_snd_pcm_sw_params_t*)ma_calloc(((ma_snd_pcm_sw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_sw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); if (pSWParams == NULL) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for software parameters."); return MA_OUT_OF_MEMORY; } resultALSA = ((ma_snd_pcm_sw_params_current_proc)pDevice->pContext->alsa.snd_pcm_sw_params_current)(pPCM, pSWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_set_avail_min_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_avail_min)(pPCM, pSWParams, ma_prev_power_of_2(internalPeriodSizeInFrames)); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_get_boundary_proc)pDevice->pContext->alsa.snd_pcm_sw_params_get_boundary)(pSWParams, &bufferBoundary); @@ -20918,27 +26433,30 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic */ resultALSA = ((ma_snd_pcm_sw_params_set_start_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_start_threshold)(pPCM, pSWParams, internalPeriodSizeInFrames*2); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed."); + return ma_result_from_errno(-resultALSA); } resultALSA = ((ma_snd_pcm_sw_params_set_stop_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_stop_threshold)(pPCM, pSWParams, bufferBoundary); if (resultALSA < 0) { /* Set to boundary to loop instead of stop in the event of an xrun. */ - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed."); + return ma_result_from_errno(-resultALSA); } } resultALSA = ((ma_snd_pcm_sw_params_proc)pDevice->pContext->alsa.snd_pcm_sw_params)(pPCM, pSWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed."); + return ma_result_from_errno(-resultALSA); } - ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); + ma_free(pSWParams, &pDevice->pContext->allocationCallbacks); pSWParams = NULL; @@ -20964,7 +26482,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic ma_bool32 isValid = MA_TRUE; /* Fill with defaults. */ - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); /* Overwrite first pChmap->channels channels. */ for (iChannel = 0; iChannel < pChmap->channels; ++iChannel) { @@ -20984,7 +26502,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic /* If our channel map is invalid, fall back to defaults. */ if (!isValid) { - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } } @@ -20992,7 +26510,7 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic pChmap = NULL; } else { /* Could not retrieve the channel map. Fall back to a hard-coded assumption. */ - ma_get_standard_channel_map(ma_standard_channel_map_alsa, internalChannels, internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, internalChannelMap, ma_countof(internalChannelMap), internalChannels); } } @@ -21005,13 +26523,15 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic pollDescriptorCount = ((ma_snd_pcm_poll_descriptors_count_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors_count)(pPCM); if (pollDescriptorCount <= 0) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors count."); + return MA_ERROR; } - pPollDescriptors = (struct pollfd*)ma_malloc(sizeof(*pPollDescriptors) * (pollDescriptorCount + 1), &pDevice->pContext->allocationCallbacks/*, MA_ALLOCATION_TYPE_GENERAL*/); /* +1 because we want room for the wakeup descriptor. */ + pPollDescriptors = (struct pollfd*)ma_malloc(sizeof(*pPollDescriptors) * (pollDescriptorCount + 1), &pDevice->pContext->allocationCallbacks); /* +1 because we want room for the wakeup descriptor. */ if (pPollDescriptors == NULL) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for poll descriptors.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to allocate memory for poll descriptors."); + return MA_OUT_OF_MEMORY; } /* @@ -21022,7 +26542,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic if (wakeupfd < 0) { ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to create eventfd for poll wakeup."); + return ma_result_from_errno(errno); } /* We'll place the wakeup fd at the start of the buffer. */ @@ -21036,7 +26557,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic close(wakeupfd); ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to retrieve poll descriptors."); + return MA_ERROR; } if (deviceType == ma_device_type_capture) { @@ -21056,7 +26578,8 @@ static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_devic close(wakeupfd); ma_free(pPollDescriptors, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to prepare device."); + return ma_result_from_errno(-resultALSA); } @@ -21112,7 +26635,8 @@ static ma_result ma_device_start__alsa(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device."); + return ma_result_from_errno(-resultALSA); } } @@ -21126,30 +26650,30 @@ static ma_result ma_device_start__alsa(ma_device* pDevice) static ma_result ma_device_stop__alsa(ma_device* pDevice) { if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device...\n"); ((ma_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping capture device successful.\n"); /* We need to prepare the device again, otherwise we won't be able to restart the device. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device...\n"); if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) < 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device failed.\n"); } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing capture device successful.\n"); } } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device...\n"); ((ma_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Dropping playback device successful.\n"); /* We need to prepare the device again, otherwise we won't be able to restart the device. */ - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device...\n"); if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback) < 0) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device failed.\n"); } else { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Preparing playback device successful.\n"); } } @@ -21163,7 +26687,8 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st int resultALSA; int resultPoll = poll(pPollDescriptors, pollDescriptorCount, -1); if (resultPoll < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed."); + return ma_result_from_errno(errno); } /* @@ -21173,10 +26698,13 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st */ if ((pPollDescriptors[0].revents & POLLIN) != 0) { ma_uint64 t; - read(pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ + int resultRead = read(pPollDescriptors[0].fd, &t, sizeof(t)); /* <-- Important that we read here so that the next write() does not block. */ + if (resultRead < 0) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] read() failed."); + return ma_result_from_errno(errno); + } ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] POLLIN set for wakeupfd\n"); - return MA_DEVICE_NOT_STARTED; } @@ -21186,11 +26714,13 @@ static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, st */ resultALSA = ((ma_snd_pcm_poll_descriptors_revents_proc)pDevice->pContext->alsa.snd_pcm_poll_descriptors_revents)(pPCM, pPollDescriptors + 1, pollDescriptorCount - 1, &revents); /* +1, -1 to ignore the wakeup descriptor. */ if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed.", ma_result_from_errno(-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_poll_descriptors_revents() failed."); + return ma_result_from_errno(-resultALSA); } if ((revents & POLLERR) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] POLLERR detected.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] POLLERR detected."); + return ma_result_from_errno(errno); } if ((revents & requiredEvent) == requiredEvent) { @@ -21213,7 +26743,7 @@ static ma_result ma_device_wait_write__alsa(ma_device* pDevice) static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead) { - ma_snd_pcm_sframes_t resultALSA; + ma_snd_pcm_sframes_t resultALSA = 0; MA_ASSERT(pDevice != NULL); MA_ASSERT(pFramesOut != NULL); @@ -21222,7 +26752,7 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u *pFramesRead = 0; } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { ma_result result; /* The first thing to do is wait for data to become available for reading. This will return an error code if the device has been stopped. */ @@ -21237,20 +26767,22 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u break; /* Success. */ } else { if (resultALSA == -EAGAIN) { - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EGAIN (read)\n");*/ + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (read)\n");*/ continue; /* Try again. */ } else if (resultALSA == -EPIPE) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EPIPE (read)\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (read)\n"); /* Overrun. Recover and try again. If this fails we need to return an error. */ resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture, resultALSA, MA_TRUE); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun."); + return ma_result_from_errno((int)-resultALSA); } resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun."); + return ma_result_from_errno((int)-resultALSA); } continue; /* Try reading again. */ @@ -21267,7 +26799,7 @@ static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_u static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) { - ma_snd_pcm_sframes_t resultALSA; + ma_snd_pcm_sframes_t resultALSA = 0; MA_ASSERT(pDevice != NULL); MA_ASSERT(pFrames != NULL); @@ -21276,7 +26808,7 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, *pFramesWritten = 0; } - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + while (ma_device_get_state(pDevice) == ma_device_state_started) { ma_result result; /* The first thing to do is wait for space to become available for writing. This will return an error code if the device has been stopped. */ @@ -21290,15 +26822,16 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, break; /* Success. */ } else { if (resultALSA == -EAGAIN) { - /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EGAIN (write)\n");*/ + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EGAIN (write)\n");*/ continue; /* Try again. */ } else if (resultALSA == -EPIPE) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "TRACE: EPIPE (write)\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "EPIPE (write)\n"); /* Underrun. Recover and try again. If this fails we need to return an error. */ resultALSA = ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback, resultALSA, MA_TRUE); /* MA_TRUE=silent (don't print anything on error). */ if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun."); + return ma_result_from_errno((int)-resultALSA); } /* @@ -21310,7 +26843,8 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, */ resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun.", ma_result_from_errno((int)-resultALSA)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device after underrun."); + return ma_result_from_errno((int)-resultALSA); } continue; /* Try writing again. */ @@ -21328,20 +26862,26 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, static ma_result ma_device_data_loop_wakeup__alsa(ma_device* pDevice) { ma_uint64 t = 1; + int resultWrite = 0; MA_ASSERT(pDevice != NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up... "); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up...\n"); /* Write to an eventfd to trigger a wakeup from poll() and abort any reading or writing. */ if (pDevice->alsa.pPollDescriptorsCapture != NULL) { - write(pDevice->alsa.wakeupfdCapture, &t, sizeof(t)); + resultWrite = write(pDevice->alsa.wakeupfdCapture, &t, sizeof(t)); } if (pDevice->alsa.pPollDescriptorsPlayback != NULL) { - write(pDevice->alsa.wakeupfdPlayback, &t, sizeof(t)); + resultWrite = write(pDevice->alsa.wakeupfdPlayback, &t, sizeof(t)); } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Done\n"); + if (resultWrite < 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] write() failed.\n"); + return ma_result_from_errno(errno); + } + + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[ALSA] Waking up completed successfully.\n"); return MA_SUCCESS; } @@ -21365,6 +26905,7 @@ static ma_result ma_context_uninit__alsa(ma_context* pContext) static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { + ma_result result; #ifndef MA_NO_RUNTIME_LINKING const char* libasoundNames[] = { "libasound.so.2", @@ -21589,8 +27130,10 @@ static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_co pContext->alsa.useVerboseDeviceEnumeration = pConfig->alsa.useVerboseDeviceEnumeration; - if (ma_mutex_init(&pContext->alsa.internalDeviceEnumLock) != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration.", MA_ERROR); + result = ma_mutex_init(&pContext->alsa.internalDeviceEnumLock); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration."); + return result; } pCallbacks->onContextInit = ma_context_init__alsa; @@ -21692,7 +27235,7 @@ that point (it may still need to load files or whatnot). Instead, this callback stream be started which is how it works with literally *every* other callback-based audio API. Since miniaudio forbids firing of the data callback until the device has been started (as it should be with *all* callback based APIs), logic needs to be added to ensure miniaudio doesn't just blindly fire the application-defined data callback from within the PulseAudio callback before the stream has actually been -started. The device state is used for this - if the state is anything other than `MA_STATE_STARTING` or `MA_STATE_STARTED`, the main data +started. The device state is used for this - if the state is anything other than `ma_device_state_starting` or `ma_device_state_started`, the main data callback is not fired. This, unfortunately, is not the end of the problems with the PulseAudio write callback. Any normal callback based audio API will @@ -22288,6 +27831,7 @@ typedef const char* (* ma_pa_stream_get_device_name_proc) ( typedef void (* ma_pa_stream_set_write_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); typedef void (* ma_pa_stream_set_read_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata); typedef void (* ma_pa_stream_set_suspended_callback_proc) (ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); +typedef void (* ma_pa_stream_set_moved_callback_proc) (ma_pa_stream* s, ma_pa_stream_notify_cb_t cb, void* userdata); typedef int (* ma_pa_stream_is_suspended_proc) (const ma_pa_stream* s); typedef ma_pa_operation* (* ma_pa_stream_flush_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); typedef ma_pa_operation* (* ma_pa_stream_drain_proc) (ma_pa_stream* s, ma_pa_stream_success_cb_t cb, void* userdata); @@ -22482,7 +28026,7 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position } #endif -static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operation* pOP) +static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP) { int resultPA; ma_pa_operation_state_t state; @@ -22496,7 +28040,7 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operat break; /* Done. */ } - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); if (resultPA < 0) { return ma_result_from_pulse(resultPA); } @@ -22505,7 +28049,7 @@ static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operat return MA_SUCCESS; } -static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_pa_operation* pOP) +static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_pa_operation* pOP) { ma_result result; @@ -22513,28 +28057,29 @@ static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma return MA_INVALID_ARGS; } - result = ma_wait_for_operation__pulse(pContext, pOP); + result = ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); return result; } -static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pContext) +static ma_result ma_wait_for_pa_context_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pPulseContext) { int resultPA; ma_pa_context_state_t state; for (;;) { - state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pContext->pulse.pPulseContext); + state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pPulseContext); if (state == MA_PA_CONTEXT_READY) { break; /* Done. */ } if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context."); + return MA_ERROR; } - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); if (resultPA < 0) { return ma_result_from_pulse(resultPA); } @@ -22544,22 +28089,23 @@ static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pC return MA_SUCCESS; } -static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_pa_stream* pStream) +static ma_result ma_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_ptr pMainLoop, ma_ptr pStream) { int resultPA; ma_pa_stream_state_t state; for (;;) { - state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)(pStream); + state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pStream); if (state == MA_PA_STREAM_READY) { break; /* Done. */ } if (state == MA_PA_STREAM_FAILED || state == MA_PA_STREAM_TERMINATED) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream.", MA_ERROR); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream."); + return MA_ERROR; } - resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pMainLoop, 1, NULL); if (resultPA < 0) { return ma_result_from_pulse(resultPA); } @@ -22569,6 +28115,52 @@ static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pCo } +static ma_result ma_init_pa_mainloop_and_pa_context__pulse(ma_context* pContext, const char* pApplicationName, const char* pServerName, ma_bool32 tryAutoSpawn, ma_ptr* ppMainLoop, ma_ptr* ppPulseContext) +{ + ma_result result; + ma_ptr pMainLoop; + ma_ptr pPulseContext; + + MA_ASSERT(ppMainLoop != NULL); + MA_ASSERT(ppPulseContext != NULL); + + /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ + pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pMainLoop == NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop."); + return MA_FAILED_TO_INIT_BACKEND; + } + + pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pMainLoop), pApplicationName); + if (pPulseContext == NULL) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context."); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); + return MA_FAILED_TO_INIT_BACKEND; + } + + /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ + result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pPulseContext, pServerName, (tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context."); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); + return result; + } + + /* Since ma_context_init() runs synchronously we need to wait for the PulseAudio context to connect before we return. */ + result = ma_wait_for_pa_context_to_connect__pulse(pContext, pMainLoop, pPulseContext); + if (result != MA_SUCCESS) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[PulseAudio] Waiting for connection failed."); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pMainLoop)); + return result; + } + + *ppMainLoop = pMainLoop; + *ppPulseContext = pPulseContext; + + return MA_SUCCESS; +} + + static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) { ma_pa_sink_info* pInfoOut; @@ -22601,6 +28193,7 @@ static void ma_device_source_info_callback(ma_pa_context* pPulseContext, const m (void)pPulseContext; /* Unused. */ } +#if 0 static void ma_device_sink_name_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) { ma_device* pDevice; @@ -22632,7 +28225,7 @@ static void ma_device_source_name_callback(ma_pa_context* pPulseContext, const m (void)pPulseContext; /* Unused. */ } - +#endif static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_sink_info* pSinkInfo) { @@ -22643,7 +28236,7 @@ static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const cha return MA_ERROR; } - return ma_wait_for_operation_and_unref__pulse(pContext, pOP); + return ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); } static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_source_info* pSourceInfo) @@ -22655,7 +28248,7 @@ static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const c return MA_ERROR; } - return ma_wait_for_operation_and_unref__pulse(pContext, pOP);; + return ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); } static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext, ma_device_type deviceType, ma_uint32* pIndex) @@ -22799,7 +28392,7 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en goto done; } - result = ma_wait_for_operation__pulse(pContext, pOP); + result = ma_wait_for_operation__pulse(pContext, pContext->pulse.pMainLoop, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); if (result != MA_SUCCESS) { @@ -22816,7 +28409,7 @@ static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_en goto done; } - result = ma_wait_for_operation__pulse(pContext, pOP); + result = ma_wait_for_operation__pulse(pContext, pContext->pulse.pMainLoop, pOP); ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); if (result != MA_SUCCESS) { @@ -22937,7 +28530,7 @@ static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_devi } if (pOP != NULL) { - ma_wait_for_operation_and_unref__pulse(pContext, pOP); + ma_wait_for_operation_and_unref__pulse(pContext, pContext->pulse.pMainLoop, pOP); } else { result = MA_ERROR; goto done; @@ -22975,6 +28568,10 @@ static ma_result ma_device_uninit__pulse(ma_device* pDevice) ma_duplex_rb_uninit(&pDevice->duplexRB); } + ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext); + ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); + return MA_SUCCESS; } @@ -22990,7 +28587,7 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra return attr; } -static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) +static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { static int g_StreamCounter = 0; char actualStreamName[256]; @@ -23003,7 +28600,7 @@ static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, cons } g_StreamCounter += 1; - return ((ma_pa_stream_new_proc)pContext->pulse.pa_stream_new)((ma_pa_context*)pContext->pulse.pPulseContext, actualStreamName, ss, cmap); + return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); } @@ -23022,7 +28619,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo can fire this callback before the stream has even started. Ridiculous. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { return; } @@ -23032,7 +28629,7 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo frameCount = byteCount / bpf; framesProcessed = 0; - while (ma_device_get_state(pDevice) == MA_STATE_STARTED && framesProcessed < frameCount) { + while (ma_device_get_state(pDevice) == ma_device_state_started && framesProcessed < frameCount) { const void* pMappedPCMFrames; size_t bytesMapped; ma_uint64 framesMapped; @@ -23094,7 +28691,7 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre framesMapped = bytesMapped / bpf; - if (deviceState == MA_STATE_STARTED || deviceState == MA_STATE_STARTING) { /* Check for starting state just in case this is being used to do the initial fill. */ + if (deviceState == ma_device_state_started || deviceState == ma_device_state_starting) { /* Check for starting state just in case this is being used to do the initial fill. */ ma_device_handle_backend_data_callback(pDevice, pMappedPCMFrames, NULL, framesMapped); } else { /* Device is not started. Write silence. */ @@ -23109,7 +28706,7 @@ static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stre framesProcessed += framesMapped; } else { - result = MA_ERROR; /* No data available. Abort. */ + result = MA_SUCCESS; /* No data available for writing. */ goto done; } } else { @@ -23141,7 +28738,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v can fire this callback before the stream has even started. Ridiculous. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { return; } @@ -23156,7 +28753,7 @@ static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, v /* Don't keep trying to process frames if the device isn't started. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + if (deviceState != ma_device_state_starting && deviceState != ma_device_state_started) { break; } @@ -23185,13 +28782,47 @@ static void ma_device_on_suspended__pulse(ma_pa_stream* pStream, void* pUserData if (suspended == 1) { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Suspended.\n"); - - if (pDevice->onStop) { - pDevice->onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); } else { ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[Pulse] Device suspended state changed. Resumed.\n"); - } + ma_device__on_notification_started(pDevice); + } +} + +static void ma_device_on_rerouted__pulse(ma_pa_stream* pStream, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + + (void)pStream; + (void)pUserData; + + ma_device__on_notification_rerouted(pDevice); +} + +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__pulse(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +{ + /* + There have been reports from users where buffers of < ~20ms result glitches when running through + PipeWire. To work around this we're going to have to use a different default buffer size. + */ + const ma_uint32 defaultPeriodSizeInMilliseconds_LowLatency = 25; + const ma_uint32 defaultPeriodSizeInMilliseconds_Conservative = MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE; + + MA_ASSERT(nativeSampleRate != 0); + + if (pDescriptor->periodSizeInFrames == 0) { + if (pDescriptor->periodSizeInMilliseconds == 0) { + if (performanceProfile == ma_performance_profile_low_latency) { + return ma_calculate_buffer_size_in_frames_from_milliseconds(defaultPeriodSizeInMilliseconds_LowLatency, nativeSampleRate); + } else { + return ma_calculate_buffer_size_in_frames_from_milliseconds(defaultPeriodSizeInMilliseconds_Conservative, nativeSampleRate); + } + } else { + return ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); + } + } else { + return pDescriptor->periodSizeInFrames; + } } static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) @@ -23263,10 +28894,18 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi sampleRate = pDescriptorCapture->sampleRate; } + + + result = ma_init_pa_mainloop_and_pa_context__pulse(pDevice->pContext, pDevice->pContext->pulse.pApplicationName, pDevice->pContext->pulse.pServerName, MA_FALSE, &pDevice->pulse.pMainLoop, &pDevice->pulse.pPulseContext); + if (result != MA_SUCCESS) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize PA mainloop and context for device.\n"); + return result; + } + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { result = ma_context_get_source_info__pulse(pDevice->pContext, devCapture, &sourceInfo); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device."); goto on_error0; } @@ -23279,26 +28918,27 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } else { ss.format = MA_PA_SAMPLE_FLOAT32BE; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_RATE_FLOAT32\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32.\n"); } if (ss.rate == 0) { ss.rate = MA_DEFAULT_SAMPLE_RATE; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.rate = 0. Defaulting to %d\n", ss.rate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.\n", ss.rate); } if (ss.channels == 0) { ss.channels = MA_DEFAULT_CHANNELS; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.channels = 0. Defaulting to %d\n", ss.channels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.\n", ss.channels); } /* We now have enough information to calculate our actual period size in frames. */ - pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, ss.rate, pConfig->performanceProfile); + pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__pulse(pDescriptorCapture, ss.rate, pConfig->performanceProfile); attr = ma_device__pa_buffer_attr_new(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->periodCount, &ss); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); - pDevice->pulse.pStreamCapture = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNameCapture, &ss, &cmap); + pDevice->pulse.pStreamCapture = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNameCapture, &ss, &cmap); if (pDevice->pulse.pStreamCapture == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.\n"); + result = MA_ERROR; goto on_error0; } @@ -23309,6 +28949,9 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi /* State callback for checking when the device has been corked. */ ((ma_pa_stream_set_suspended_callback_proc)pDevice->pContext->pulse.pa_stream_set_suspended_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_suspended__pulse, pDevice); + /* Rerouting notification. */ + ((ma_pa_stream_set_moved_callback_proc)pDevice->pContext->pulse.pa_stream_set_moved_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_rerouted__pulse, pDevice); + /* Connect after we've got all of our internal state set up. */ streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; @@ -23318,33 +28961,64 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi error = ((ma_pa_stream_connect_record_proc)pDevice->pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream."); + result = ma_result_from_pulse(error); goto on_error1; } - result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamCapture); + result = ma_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, (ma_pa_stream*)pDevice->pulse.pStreamCapture); if (result != MA_SUCCESS) { goto on_error2; } + /* Internal format. */ pActualSS = ((ma_pa_stream_get_sample_spec_proc)pDevice->pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (pActualSS != NULL) { ss = *pActualSS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture sample spec: format=%s, channels=%d, rate=%d\n", ma_get_format_name(ma_format_from_pulse(ss.format)), ss.channels, ss.rate); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Failed to retrieve capture sample spec.\n"); } pDescriptorCapture->format = ma_format_from_pulse(ss.format); pDescriptorCapture->channels = ss.channels; pDescriptorCapture->sampleRate = ss.rate; - /* Internal channel map. */ - pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (pActualCMap != NULL) { - cmap = *pActualCMap; + if (pDescriptorCapture->format == ma_format_unknown || pDescriptorCapture->channels == 0 || pDescriptorCapture->sampleRate == 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Capture sample spec is invalid. Device unusable by miniaudio. format=%s, channels=%d, sampleRate=%d.\n", ma_get_format_name(pDescriptorCapture->format), pDescriptorCapture->channels, pDescriptorCapture->sampleRate); + result = MA_ERROR; + goto on_error4; } - for (iChannel = 0; iChannel < pDescriptorCapture->channels; ++iChannel) { - pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + /* Internal channel map. */ + + /* + Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting + the channel map. To somewhat workaround this, I'm hacking in a hard coded channel map for mono + and stereo. In this case it should be safe to assume mono = MONO and stereo = LEFT/RIGHT. For + all other channel counts we need to just put up with whatever PipeWire reports and hope it gets + fixed sooner than later. I might remove this hack later. + */ + if (pDescriptorCapture->channels > 2) { + pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (pActualCMap != NULL) { + cmap = *pActualCMap; + } + + for (iChannel = 0; iChannel < pDescriptorCapture->channels; ++iChannel) { + pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + } + } else { + /* Hack for mono and stereo. */ + if (pDescriptorCapture->channels == 1) { + pDescriptorCapture->channelMap[0] = MA_CHANNEL_MONO; + } else if (pDescriptorCapture->channels == 2) { + pDescriptorCapture->channelMap[0] = MA_CHANNEL_FRONT_LEFT; + pDescriptorCapture->channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } } @@ -23354,24 +29028,20 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi attr = *pActualAttr; } - pDescriptorCapture->periodCount = attr.maxlength / attr.fragsize; - pDescriptorCapture->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / pDescriptorCapture->periodCount; - - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); - - - /* Name. */ - devCapture = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (devCapture != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice); - ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + if (attr.fragsize > 0) { + pDescriptorCapture->periodCount = ma_max(attr.maxlength / attr.fragsize, 1); + } else { + pDescriptorCapture->periodCount = 1; } + + pDescriptorCapture->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / pDescriptorCapture->periodCount; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { result = ma_context_get_sink_info__pulse(pDevice->pContext, devPlayback, &sinkInfo); if (result != MA_SUCCESS) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.\n"); goto on_error2; } @@ -23384,40 +29054,44 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi } else { ss.format = MA_PA_SAMPLE_FLOAT32BE; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_RATE_FLOAT32\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.format not supported by miniaudio. Defaulting to PA_SAMPLE_FLOAT32.\n"); } if (ss.rate == 0) { ss.rate = MA_DEFAULT_SAMPLE_RATE; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.rate = 0. Defaulting to %d\n", ss.rate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.rate = 0. Defaulting to %d.\n", ss.rate); } if (ss.channels == 0) { ss.channels = MA_DEFAULT_CHANNELS; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] WARNING: sample_spec.channels = 0. Defaulting to %d\n", ss.channels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] sample_spec.channels = 0. Defaulting to %d.\n", ss.channels); } /* We now have enough information to calculate the actual buffer size in frames. */ - pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, ss.rate, pConfig->performanceProfile); + pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__pulse(pDescriptorPlayback, ss.rate, pConfig->performanceProfile); attr = ma_device__pa_buffer_attr_new(pDescriptorPlayback->periodSizeInFrames, pDescriptorPlayback->periodCount, &ss); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); - pDevice->pulse.pStreamPlayback = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); + pDevice->pulse.pStreamPlayback = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); if (pDevice->pulse.pStreamPlayback == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.\n"); + result = MA_ERROR; goto on_error2; } /* Note that this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a - device state of MA_STATE_UNINITIALIZED. + device state of ma_device_state_uninitialized. */ ((ma_pa_stream_set_write_callback_proc)pDevice->pContext->pulse.pa_stream_set_write_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_write__pulse, pDevice); /* State callback for checking when the device has been corked. */ ((ma_pa_stream_set_suspended_callback_proc)pDevice->pContext->pulse.pa_stream_set_suspended_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_suspended__pulse, pDevice); + /* Rerouting notification. */ + ((ma_pa_stream_set_moved_callback_proc)pDevice->pContext->pulse.pa_stream_set_moved_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_rerouted__pulse, pDevice); + /* Connect after we've got all of our internal state set up. */ streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; @@ -23427,11 +29101,12 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi error = ((ma_pa_stream_connect_playback_proc)pDevice->pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream."); + result = ma_result_from_pulse(error); goto on_error3; } - result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); + result = ma_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (result != MA_SUCCESS) { goto on_error3; } @@ -23441,20 +29116,49 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi pActualSS = ((ma_pa_stream_get_sample_spec_proc)pDevice->pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (pActualSS != NULL) { ss = *pActualSS; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback sample spec: format=%s, channels=%d, rate=%d\n", ma_get_format_name(ma_format_from_pulse(ss.format)), ss.channels, ss.rate); + } else { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Failed to retrieve playback sample spec.\n"); } pDescriptorPlayback->format = ma_format_from_pulse(ss.format); pDescriptorPlayback->channels = ss.channels; pDescriptorPlayback->sampleRate = ss.rate; - /* Internal channel map. */ - pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (pActualCMap != NULL) { - cmap = *pActualCMap; + if (pDescriptorPlayback->format == ma_format_unknown || pDescriptorPlayback->channels == 0 || pDescriptorPlayback->sampleRate == 0) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Playback sample spec is invalid. Device unusable by miniaudio. format=%s, channels=%d, sampleRate=%d.\n", ma_get_format_name(pDescriptorPlayback->format), pDescriptorPlayback->channels, pDescriptorPlayback->sampleRate); + result = MA_ERROR; + goto on_error4; } - for (iChannel = 0; iChannel < pDescriptorPlayback->channels; ++iChannel) { - pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + /* Internal channel map. */ + + /* + Bug in PipeWire. There have been reports that PipeWire is returning AUX channels when reporting + the channel map. To somewhat workaround this, I'm hacking in a hard coded channel map for mono + and stereo. In this case it should be safe to assume mono = MONO and stereo = LEFT/RIGHT. For + all other channel counts we need to just put up with whatever PipeWire reports and hope it gets + fixed sooner than later. I might remove this hack later. + */ + if (pDescriptorPlayback->channels > 2) { + pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + if (pActualCMap != NULL) { + cmap = *pActualCMap; + } + + for (iChannel = 0; iChannel < pDescriptorPlayback->channels; ++iChannel) { + pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + } + } else { + /* Hack for mono and stereo. */ + if (pDescriptorPlayback->channels == 1) { + pDescriptorPlayback->channelMap[0] = MA_CHANNEL_MONO; + } else if (pDescriptorPlayback->channels == 2) { + pDescriptorPlayback->channelMap[0] = MA_CHANNEL_FRONT_LEFT; + pDescriptorPlayback->channelMap[1] = MA_CHANNEL_FRONT_RIGHT; + } else { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } } @@ -23464,17 +29168,14 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi attr = *pActualAttr; } - pDescriptorPlayback->periodCount = attr.maxlength / attr.tlength; - pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); - - - /* Name. */ - devPlayback = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (devPlayback != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice); - ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + if (attr.tlength > 0) { + pDescriptorPlayback->periodCount = ma_max(attr.maxlength / attr.tlength, 1); + } else { + pDescriptorPlayback->periodCount = 1; } + + pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount; + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); } @@ -23485,9 +29186,13 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi onDeviceDataLoop callback is NULL, which is not the case for PulseAudio. */ if (pConfig->deviceType == ma_device_type_duplex) { - result = ma_duplex_rb_init(format, channels, sampleRate, pDescriptorCapture->sampleRate, pDescriptorCapture->periodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); + ma_format rbFormat = (format != ma_format_unknown) ? format : pDescriptorCapture->format; + ma_uint32 rbChannels = (channels > 0) ? channels : pDescriptorCapture->channels; + ma_uint32 rbSampleRate = (sampleRate > 0) ? sampleRate : pDescriptorCapture->sampleRate; + + result = ma_duplex_rb_init(rbFormat, rbChannels, rbSampleRate, pDescriptorCapture->sampleRate, pDescriptorCapture->periodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); if (result != MA_SUCCESS) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer.", result); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer. %s.\n", ma_result_description(result)); goto on_error4; } } @@ -23546,20 +29251,19 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ pOP = ((ma_pa_stream_cork_proc)pContext->pulse.pa_stream_cork)(pStream, cork, ma_pulse_operation_complete_callback, &wasSuccessful); if (pOP == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MA_FAILED_TO_START_BACKEND_DEVICE : MA_FAILED_TO_STOP_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream."); + return MA_ERROR; } - result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork."); + return result; } if (!wasSuccessful) { - if (cork) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to stop PulseAudio stream.", MA_FAILED_TO_STOP_BACKEND_DEVICE); - } else { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to start PulseAudio stream.", MA_FAILED_TO_START_BACKEND_DEVICE); - } + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to %s PulseAudio stream.", (cork) ? "stop" : "start"); + return MA_ERROR; } return MA_SUCCESS; @@ -23579,11 +29283,12 @@ static ma_result ma_device_start__pulse(ma_device* pDevice) } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* We need to fill some data before uncorking. Not doing this will result in the write callback never getting fired. */ - result = ma_device_write_to_stream__pulse(pDevice, (ma_pa_stream*)(pDevice->pulse.pStreamPlayback), NULL); - if (result != MA_SUCCESS) { - return result; /* Failed to write data. Not sure what to do here... Just aborting. */ - } + /* + We need to fill some data before uncorking. Not doing this will result in the write callback + never getting fired. We're not going to abort if writing fails because I still want the device + to get uncorked. + */ + ma_device_write_to_stream__pulse(pDevice, (ma_pa_stream*)(pDevice->pulse.pStreamPlayback), NULL); /* No need to check the result here. Always want to fall through an uncork.*/ result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); if (result != MA_SUCCESS) { @@ -23597,7 +29302,6 @@ static ma_result ma_device_start__pulse(ma_device* pDevice) static ma_result ma_device_stop__pulse(ma_device* pDevice) { ma_result result; - ma_bool32 wasSuccessful; MA_ASSERT(pDevice != NULL); @@ -23609,9 +29313,16 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* The stream needs to be drained if it's a playback device. */ + /* + Ideally we would drain the device here, but there's been cases where PulseAudio seems to be + broken on some systems to the point where no audio processing seems to happen. When this + happens, draining never completes and we get stuck here. For now I'm disabling draining of + the device so we don't just freeze the application. + */ + #if 0 ma_pa_operation* pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); - ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pDevice->pulse.pMainLoop, pOP); + #endif result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 1); if (result != MA_SUCCESS) { @@ -23634,8 +29345,8 @@ static ma_result ma_device_data_loop__pulse(ma_device* pDevice) All data is handled through callbacks. All we need to do is iterate over the main loop and let the callbacks deal with it. */ - while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { - resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 1, NULL); + while (ma_device_get_state(pDevice) == ma_device_state_started) { + resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); if (resultPA < 0) { break; } @@ -23649,7 +29360,7 @@ static ma_result ma_device_data_loop_wakeup__pulse(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - ((ma_pa_mainloop_wakeup_proc)pDevice->pContext->pulse.pa_mainloop_wakeup)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop); + ((ma_pa_mainloop_wakeup_proc)pDevice->pContext->pulse.pa_mainloop_wakeup)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); return MA_SUCCESS; } @@ -23663,6 +29374,9 @@ static ma_result ma_context_uninit__pulse(ma_context* pContext) ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext); ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pContext->pulse.pMainLoop); + ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); + ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); + #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif @@ -23739,6 +29453,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c pContext->pulse.pa_stream_set_write_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_write_callback"); pContext->pulse.pa_stream_set_read_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_read_callback"); pContext->pulse.pa_stream_set_suspended_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_suspended_callback"); + pContext->pulse.pa_stream_set_moved_callback = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_set_moved_callback"); pContext->pulse.pa_stream_is_suspended = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_is_suspended"); pContext->pulse.pa_stream_flush = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_flush"); pContext->pulse.pa_stream_drain = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_stream_drain"); @@ -23801,6 +29516,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c ma_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback; ma_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback; ma_pa_stream_set_suspended_callback_proc _pa_stream_set_suspended_callback = pa_stream_set_suspended_callback; + ma_pa_stream_set_moved_callback_proc _pa_stream_set_moved_callback = pa_stream_set_moved_callback; ma_pa_stream_is_suspended_proc _pa_stream_is_suspended = pa_stream_is_suspended; ma_pa_stream_flush_proc _pa_stream_flush = pa_stream_flush; ma_pa_stream_drain_proc _pa_stream_drain = pa_stream_drain; @@ -23862,6 +29578,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c pContext->pulse.pa_stream_set_write_callback = (ma_proc)_pa_stream_set_write_callback; pContext->pulse.pa_stream_set_read_callback = (ma_proc)_pa_stream_set_read_callback; pContext->pulse.pa_stream_set_suspended_callback = (ma_proc)_pa_stream_set_suspended_callback; + pContext->pulse.pa_stream_set_moved_callback = (ma_proc)_pa_stream_set_moved_callback; pContext->pulse.pa_stream_is_suspended = (ma_proc)_pa_stream_is_suspended; pContext->pulse.pa_stream_flush = (ma_proc)_pa_stream_flush; pContext->pulse.pa_stream_drain = (ma_proc)_pa_stream_drain; @@ -23876,48 +29593,28 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c pContext->pulse.pa_stream_readable_size = (ma_proc)_pa_stream_readable_size; #endif - /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ - pContext->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pContext->pulse.pMainLoop == NULL) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop.", MA_FAILED_TO_INIT_BACKEND); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return result; + /* We need to make a copy of the application and server names so we can pass them to the pa_context of each device. */ + pContext->pulse.pApplicationName = ma_copy_string(pConfig->pulse.pApplicationName, &pContext->allocationCallbacks); + if (pContext->pulse.pApplicationName == NULL && pConfig->pulse.pApplicationName != NULL) { + return MA_OUT_OF_MEMORY; } - pContext->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pContext->pulse.pMainLoop), pConfig->pulse.pApplicationName); - if (pContext->pulse.pPulseContext == NULL) { - result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context.", MA_FAILED_TO_INIT_BACKEND); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return result; + pContext->pulse.pServerName = ma_copy_string(pConfig->pulse.pServerName, &pContext->allocationCallbacks); + if (pContext->pulse.pServerName == NULL && pConfig->pulse.pServerName != NULL) { + ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); + return MA_OUT_OF_MEMORY; } - /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ - result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pContext->pulse.pPulseContext, pConfig->pulse.pServerName, (pConfig->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); + result = ma_init_pa_mainloop_and_pa_context__pulse(pContext, pConfig->pulse.pApplicationName, pConfig->pulse.pServerName, pConfig->pulse.tryAutoSpawn, &pContext->pulse.pMainLoop, &pContext->pulse.pPulseContext); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", result); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); + ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); + ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); #endif return result; } - /* Since ma_context_init() runs synchronously we need to wait for the PulseAudio context to connect before we return. */ - result = ma_context_wait_for_pa_context_to_connect__pulse(pContext); - if (result != MA_SUCCESS) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return result; - } - - /* With pa_mainloop we run a synchronous backend, but we implement our own main loop. */ pCallbacks->onContextInit = ma_context_init__pulse; pCallbacks->onContextUninit = ma_context_uninit__pulse; @@ -24082,7 +29779,8 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic /* The channel count and sample rate can only be determined by opening the device. */ result = ma_context_open_client__jack(pContext, &pClient); if (result != MA_SUCCESS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); + return result; } pDeviceInfo->nativeDataFormats[0].sampleRate = ((ma_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pClient); @@ -24091,7 +29789,8 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ((deviceType == ma_device_type_playback) ? ma_JackPortIsInput : ma_JackPortIsOutput)); if (ppPorts == NULL) { ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pClient); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } while (ppPorts[pDeviceInfo->nativeDataFormats[0].channels] != NULL) { @@ -24123,11 +29822,13 @@ static ma_result ma_device_uninit__jack(ma_device* pDevice) } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.ppPortsCapture, &pDevice->pContext->allocationCallbacks); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.ppPortsPlayback, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -24149,12 +29850,12 @@ static int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, vo if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { size_t newBufferSize = frameCount * (pDevice->capture.internalChannels * ma_get_bytes_per_sample(pDevice->capture.internalFormat)); - float* pNewBuffer = (float*)ma__calloc_from_callbacks(newBufferSize, &pDevice->pContext->allocationCallbacks); + float* pNewBuffer = (float*)ma_calloc(newBufferSize, &pDevice->pContext->allocationCallbacks); if (pNewBuffer == NULL) { return MA_OUT_OF_MEMORY; } - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferCapture, &pDevice->pContext->allocationCallbacks); pDevice->jack.pIntermediaryBufferCapture = pNewBuffer; pDevice->playback.internalPeriodSizeInFrames = frameCount; @@ -24162,12 +29863,12 @@ static int ma_device__jack_buffer_size_callback(ma_jack_nframes_t frameCount, vo if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { size_t newBufferSize = frameCount * (pDevice->playback.internalChannels * ma_get_bytes_per_sample(pDevice->playback.internalFormat)); - float* pNewBuffer = (float*)ma__calloc_from_callbacks(newBufferSize, &pDevice->pContext->allocationCallbacks); + float* pNewBuffer = (float*)ma_calloc(newBufferSize, &pDevice->pContext->allocationCallbacks); if (pNewBuffer == NULL) { return MA_OUT_OF_MEMORY; } - ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); pDevice->jack.pIntermediaryBufferPlayback = pNewBuffer; pDevice->playback.internalPeriodSizeInFrames = frameCount; @@ -24191,7 +29892,7 @@ static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { /* Channels need to be interleaved. */ for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { - const float* pSrc = (const float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.pPortsCapture[iChannel], frameCount); + const float* pSrc = (const float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.ppPortsCapture[iChannel], frameCount); if (pSrc != NULL) { float* pDst = pDevice->jack.pIntermediaryBufferCapture + iChannel; ma_jack_nframes_t iFrame; @@ -24212,7 +29913,7 @@ static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* /* Channels need to be deinterleaved. */ for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { - float* pDst = (float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.pPortsPlayback[iChannel], frameCount); + float* pDst = (float*)((ma_jack_port_get_buffer_proc)pContext->jack.jack_port_get_buffer)((ma_jack_port_t*)pDevice->jack.ppPortsPlayback[iChannel], frameCount); if (pDst != NULL) { const float* pSrc = pDevice->jack.pIntermediaryBufferPlayback + iChannel; ma_jack_nframes_t iFrame; @@ -24238,33 +29939,39 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config MA_ASSERT(pDevice != NULL); if (pConfig->deviceType == ma_device_type_loopback) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Loopback mode not supported."); return MA_DEVICE_TYPE_NOT_SUPPORTED; } /* Only supporting default devices with JACK. */ if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->pDeviceID != NULL && pDescriptorPlayback->pDeviceID->jack != 0) || ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->pDeviceID != NULL && pDescriptorCapture->pDeviceID->jack != 0)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Only default devices are supported."); return MA_NO_DEVICE; } /* No exclusive mode with the JACK backend. */ if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Exclusive mode not supported."); return MA_SHARE_MODE_NOT_SUPPORTED; } /* Open the client. */ result = ma_context_open_client__jack(pDevice->pContext, (ma_jack_client_t**)&pDevice->jack.pClient); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client."); + return result; } /* Callbacks. */ if (((ma_jack_set_process_callback_proc)pDevice->pContext->jack.jack_set_process_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_process_callback, pDevice) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } if (((ma_jack_set_buffer_size_callback_proc)pDevice->pContext->jack.jack_set_buffer_size_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_buffer_size_callback, pDevice) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } ((ma_jack_on_shutdown_proc)pDevice->pContext->jack.jack_on_shutdown)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_shutdown_callback, pDevice); @@ -24274,31 +29981,42 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config periodSizeInFrames = ((ma_jack_get_buffer_size_proc)pDevice->pContext->jack.jack_get_buffer_size)((ma_jack_client_t*)pDevice->jack.pClient); if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 iPort; const char** ppPorts; pDescriptorCapture->format = ma_format_f32; pDescriptorCapture->channels = 0; pDescriptorCapture->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap), pDescriptorCapture->channels); ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); if (ppPorts == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } + /* Need to count the number of ports first so we can allocate some memory. */ while (ppPorts[pDescriptorCapture->channels] != NULL) { + pDescriptorCapture->channels += 1; + } + + pDevice->jack.ppPortsCapture = (ma_ptr*)ma_malloc(sizeof(*pDevice->jack.ppPortsCapture) * pDescriptorCapture->channels, &pDevice->pContext->allocationCallbacks); + if (pDevice->jack.ppPortsCapture == NULL) { + return MA_OUT_OF_MEMORY; + } + + for (iPort = 0; iPort < pDescriptorCapture->channels; iPort += 1) { char name[64]; ma_strcpy_s(name, sizeof(name), "capture"); - ma_itoa_s((int)pDescriptorCapture->channels, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ + ma_itoa_s((int)iPort, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ - pDevice->jack.pPortsCapture[pDescriptorCapture->channels] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); - if (pDevice->jack.pPortsCapture[pDescriptorCapture->channels] == NULL) { + pDevice->jack.ppPortsCapture[iPort] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); + if (pDevice->jack.ppPortsCapture[iPort] == NULL) { ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); ma_device_uninit__jack(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } - - pDescriptorCapture->channels += 1; } ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); @@ -24306,7 +30024,7 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; pDescriptorCapture->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ - pDevice->jack.pIntermediaryBufferCapture = (float*)ma__calloc_from_callbacks(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels), &pDevice->pContext->allocationCallbacks); + pDevice->jack.pIntermediaryBufferCapture = (float*)ma_calloc(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels), &pDevice->pContext->allocationCallbacks); if (pDevice->jack.pIntermediaryBufferCapture == NULL) { ma_device_uninit__jack(pDevice); return MA_OUT_OF_MEMORY; @@ -24314,31 +30032,43 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + ma_uint32 iPort; const char** ppPorts; pDescriptorPlayback->format = ma_format_f32; pDescriptorPlayback->channels = 0; pDescriptorPlayback->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_alsa, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap), pDescriptorPlayback->channels); ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); if (ppPorts == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } + /* Need to count the number of ports first so we can allocate some memory. */ while (ppPorts[pDescriptorPlayback->channels] != NULL) { + pDescriptorPlayback->channels += 1; + } + + pDevice->jack.ppPortsPlayback = (ma_ptr*)ma_malloc(sizeof(*pDevice->jack.ppPortsPlayback) * pDescriptorPlayback->channels, &pDevice->pContext->allocationCallbacks); + if (pDevice->jack.ppPortsPlayback == NULL) { + ma_free(pDevice->jack.ppPortsCapture, &pDevice->pContext->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + for (iPort = 0; iPort < pDescriptorPlayback->channels; iPort += 1) { char name[64]; ma_strcpy_s(name, sizeof(name), "playback"); - ma_itoa_s((int)pDescriptorPlayback->channels, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ + ma_itoa_s((int)iPort, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ - pDevice->jack.pPortsPlayback[pDescriptorPlayback->channels] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); - if (pDevice->jack.pPortsPlayback[pDescriptorPlayback->channels] == NULL) { + pDevice->jack.ppPortsPlayback[iPort] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); + if (pDevice->jack.ppPortsPlayback[iPort] == NULL) { ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); ma_device_uninit__jack(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } - - pDescriptorPlayback->channels += 1; } ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); @@ -24346,7 +30076,7 @@ static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; pDescriptorPlayback->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ - pDevice->jack.pIntermediaryBufferPlayback = (float*)ma__calloc_from_callbacks(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels), &pDevice->pContext->allocationCallbacks); + pDevice->jack.pIntermediaryBufferPlayback = (float*)ma_calloc(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels), &pDevice->pContext->allocationCallbacks); if (pDevice->jack.pIntermediaryBufferPlayback == NULL) { ma_device_uninit__jack(pDevice); return MA_OUT_OF_MEMORY; @@ -24365,25 +30095,28 @@ static ma_result ma_device_start__jack(ma_device* pDevice) resultJACK = ((ma_jack_activate_proc)pContext->jack.jack_activate)((ma_jack_client_t*)pDevice->jack.pClient); if (resultJACK != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client.", MA_FAILED_TO_START_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to activate the JACK client."); + return MA_FAILED_TO_START_BACKEND_DEVICE; } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); if (ppServerPorts == NULL) { ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports."); + return MA_ERROR; } for (i = 0; ppServerPorts[i] != NULL; ++i) { const char* pServerPort = ppServerPorts[i]; - const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.pPortsCapture[i]); + const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.ppPortsCapture[i]); resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pServerPort, pClientPort); if (resultJACK != 0) { ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports."); + return MA_ERROR; } } @@ -24394,18 +30127,20 @@ static ma_result ma_device_start__jack(ma_device* pDevice) const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); if (ppServerPorts == NULL) { ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to retrieve physical ports."); + return MA_ERROR; } for (i = 0; ppServerPorts[i] != NULL; ++i) { const char* pServerPort = ppServerPorts[i]; - const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.pPortsPlayback[i]); + const char* pClientPort = ((ma_jack_port_name_proc)pContext->jack.jack_port_name)((ma_jack_port_t*)pDevice->jack.ppPortsPlayback[i]); resultJACK = ((ma_jack_connect_proc)pContext->jack.jack_connect)((ma_jack_client_t*)pDevice->jack.pClient, pClientPort, pServerPort); if (resultJACK != 0) { ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); ((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] Failed to connect ports."); + return MA_ERROR; } } @@ -24418,16 +30153,13 @@ static ma_result ma_device_start__jack(ma_device* pDevice) static ma_result ma_device_stop__jack(ma_device* pDevice) { ma_context* pContext = pDevice->pContext; - ma_stop_proc onStop; if (((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient) != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client."); + return MA_ERROR; } - onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); return MA_SUCCESS; } @@ -24592,6 +30324,13 @@ References #if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH == 1 #define MA_APPLE_WATCH #endif + #if __has_feature(objc_arc) + #define MA_BRIDGE_TRANSFER __bridge_transfer + #define MA_BRIDGE_RETAINED __bridge_retained + #else + #define MA_BRIDGE_TRANSFER + #define MA_BRIDGE_RETAINED + #endif #else #define MA_APPLE_DESKTOP #endif @@ -24930,7 +30669,7 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* case kAudioChannelLayoutTag_Binaural: case kAudioChannelLayoutTag_Ambisonic_B_Format: { - ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); } break; case kAudioChannelLayoutTag_Octagonal: @@ -24958,7 +30697,7 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* default: { - ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); } break; } } @@ -25095,14 +30834,14 @@ static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioOb return MA_FALSE; } - pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(dataSize, &pContext->allocationCallbacks); + pBufferList = (AudioBufferList*)ma_malloc(dataSize, &pContext->allocationCallbacks); if (pBufferList == NULL) { return MA_FALSE; /* Out of memory. */ } status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pBufferList); if (status != noErr) { - ma__free_from_callbacks(pBufferList, &pContext->allocationCallbacks); + ma_free(pBufferList, &pContext->allocationCallbacks); return MA_FALSE; } @@ -25111,7 +30850,7 @@ static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioOb isSupported = MA_TRUE; } - ma__free_from_callbacks(pBufferList, &pContext->allocationCallbacks); + ma_free(pBufferList, &pContext->allocationCallbacks); return isSupported; } @@ -25740,24 +31479,24 @@ static ma_result ma_get_AudioUnit_channel_map(ma_context* pContext, AudioUnit au return ma_result_from_OSStatus(status); } - pChannelLayout = (AudioChannelLayout*)ma__malloc_from_callbacks(channelLayoutSize, &pContext->allocationCallbacks); + pChannelLayout = (AudioChannelLayout*)ma_malloc(channelLayoutSize, &pContext->allocationCallbacks); if (pChannelLayout == NULL) { return MA_OUT_OF_MEMORY; } status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, pChannelLayout, &channelLayoutSize); if (status != noErr) { - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return ma_result_from_OSStatus(status); } result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return result; } - ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); + ma_free(pChannelLayout, &pContext->allocationCallbacks); return MA_SUCCESS; } #endif /* MA_APPLE_DESKTOP */ @@ -25993,7 +31732,7 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_ UInt32 propSize; /* We want to ensure we use a consistent device name to device enumeration. */ - if (pDeviceID != NULL) { + if (pDeviceID != NULL && pDeviceID->coreaudio[0] != '\0') { ma_bool32 found = MA_FALSE; if (deviceType == ma_device_type_playback) { NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs]; @@ -26109,7 +31848,7 @@ static AudioBufferList* ma_allocate_AudioBufferList__coreaudio(ma_uint32 sizeInF allocationSize += sizeInFrames * ma_get_bytes_per_frame(format, channels); - pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(allocationSize, pAllocationCallbacks); + pBufferList = (AudioBufferList*)ma_malloc(allocationSize, pAllocationCallbacks); if (pBufferList == NULL) { return NULL; } @@ -26145,12 +31884,12 @@ static ma_result ma_device_realloc_AudioBufferList__coreaudio(ma_device* pDevice AudioBufferList* pNewAudioBufferList; pNewAudioBufferList = ma_allocate_AudioBufferList__coreaudio(sizeInFrames, format, channels, layout, &pDevice->pContext->allocationCallbacks); - if (pNewAudioBufferList != NULL) { + if (pNewAudioBufferList == NULL) { return MA_OUT_OF_MEMORY; } /* At this point we'll have a new AudioBufferList and we can free the old one. */ - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); pDevice->coreaudio.pAudioBufferList = pNewAudioBufferList; pDevice->coreaudio.audioBufferCapInFrames = sizeInFrames; } @@ -26167,7 +31906,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl MA_ASSERT(pDevice != NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pBufferList->mNumberBuffers); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Output Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", (int)busNumber, (int)frameCount, (int)pBufferList->mNumberBuffers);*/ /* We need to check whether or not we are outputting interleaved or non-interleaved samples. The way we do this is slightly different for each type. */ layout = ma_stream_layout_interleaved; @@ -26185,7 +31924,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl ma_device_handle_backend_data_callback(pDevice, pBufferList->mBuffers[iBuffer].mData, NULL, frameCountForThisBuffer); } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + /*a_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ } else { /* This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's @@ -26193,7 +31932,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl output silence here. */ MA_ZERO_MEMORY(pBufferList->mBuffers[iBuffer].mData, pBufferList->mBuffers[iBuffer].mDataByteSize); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pBufferList->mBuffers[iBuffer].mNumberChannels, (int)pBufferList->mBuffers[iBuffer].mDataByteSize);*/ } } } else { @@ -26262,7 +32001,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla layout = ma_stream_layout_deinterleaved; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", (int)busNumber, (int)frameCount, (int)pRenderedBufferList->mNumberBuffers);*/ /* There has been a situation reported where frame count passed into this function is greater than the capacity of @@ -26272,9 +32011,12 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla */ result = ma_device_realloc_AudioBufferList__coreaudio(pDevice, frameCount, pDevice->capture.internalFormat, pDevice->capture.internalChannels, layout); if (result != MA_SUCCESS) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed to allocate AudioBufferList for capture."); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "Failed to allocate AudioBufferList for capture.\n"); return noErr; } + + pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; + MA_ASSERT(pRenderedBufferList); /* When you call AudioUnitRender(), Core Audio tries to be helpful by setting the mDataByteSize to the number of bytes @@ -26290,7 +32032,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla status = ((ma_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); if (status != noErr) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " ERROR: AudioUnitRender() failed with %d\n", status); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " ERROR: AudioUnitRender() failed with %d.\n", (int)status); return status; } @@ -26298,7 +32040,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->capture.internalChannels) { ma_device_handle_backend_data_callback(pDevice, NULL, pRenderedBufferList->mBuffers[iBuffer].mData, frameCount); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " mDataByteSize=%d.\n", (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ } else { /* This case is where the number of channels in the output buffer do not match our internal channels. It could mean that it's @@ -26321,7 +32063,7 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla framesRemaining -= framesToSend; } - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); + /*ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", (int)frameCount, (int)pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, (int)pRenderedBufferList->mBuffers[iBuffer].mDataByteSize);*/ } } } else { @@ -26383,24 +32125,17 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio can try waiting on the same lock. I'm going to try working around this by not calling any Core Audio APIs in the callback when the device has been stopped or uninitialized. */ - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED || ma_device_get_state(pDevice) == MA_STATE_STOPPING || ma_device_get_state(pDevice) == MA_STATE_STOPPED) { - ma_stop_proc onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } - - ma_event_signal(&pDevice->coreaudio.stopEvent); + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized || ma_device_get_state(pDevice) == ma_device_state_stopping || ma_device_get_state(pDevice) == ma_device_state_stopped) { + ma_device__on_notification_stopped(pDevice); } else { UInt32 isRunning; UInt32 isRunningSize = sizeof(isRunning); OSStatus status = ((ma_AudioUnitGetProperty_proc)pDevice->pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioOutputUnitProperty_IsRunning, scope, element, &isRunning, &isRunningSize); if (status != noErr) { - return; /* Don't really know what to do in this case... just ignore it, I suppose... */ + goto done; /* Don't really know what to do in this case... just ignore it, I suppose... */ } if (!isRunning) { - ma_stop_proc onStop; - /* The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: @@ -26414,12 +32149,12 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio /* It looks like the device is switching through an external event, such as the user unplugging the device or changing the default device via the operating system's sound settings. If we're re-initializing the device, we just terminate because we want the stopping of the - device to be seamless to the client (we don't want them receiving the onStop event and thinking that the device has stopped when it + device to be seamless to the client (we don't want them receiving the stopped event and thinking that the device has stopped when it hasn't!). */ if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isSwitchingPlaybackDevice) || ((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isSwitchingCaptureDevice)) { - return; + goto done; } /* @@ -26427,20 +32162,21 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most likely be successful in switching to the new device. - TODO: Try to predict if Core Audio will switch devices. If not, the onStop callback needs to be posted. + TODO: Try to predict if Core Audio will switch devices. If not, the stopped callback needs to be posted. */ - return; + goto done; } /* Getting here means we need to stop the device. */ - onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); } } (void)propertyID; /* Unused. */ + +done: + /* Always signal the stop event. It's possible for the "else" case to get hit which can happen during an interruption. */ + ma_event_signal(&pDevice->coreaudio.stopEvent); } #if defined(MA_APPLE_DESKTOP) @@ -26491,7 +32227,7 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn ma_device__post_init_setup(pDevice, deviceType); /* Restart the device if required. If this fails we need to stop the device entirely. */ - if (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) == ma_device_state_started) { OSStatus status; if (deviceType == ma_device_type_playback) { status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); @@ -26499,7 +32235,7 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn if (pDevice->type == ma_device_type_duplex) { ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } else if (deviceType == ma_device_type_capture) { status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture); @@ -26507,10 +32243,12 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn if (pDevice->type == ma_device_type_duplex) { ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } } + + ma_device__on_notification_rerouted(pDevice); } } } @@ -26574,7 +32312,7 @@ static ma_result ma_context__uninit_device_tracking__coreaudio(ma_context* pCont /* At this point there should be no tracked devices. If not there's an error somewhere. */ if (g_ppTrackedDevices_CoreAudio != NULL) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active.", MA_INVALID_OPERATION); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active."); ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); return MA_INVALID_OPERATION; } @@ -26595,17 +32333,15 @@ static ma_result ma_device__track__coreaudio(ma_device* pDevice) { /* Allocate memory if required. */ if (g_TrackedDeviceCap_CoreAudio <= g_TrackedDeviceCount_CoreAudio) { - ma_uint32 oldCap; ma_uint32 newCap; ma_device** ppNewDevices; - oldCap = g_TrackedDeviceCap_CoreAudio; newCap = g_TrackedDeviceCap_CoreAudio * 2; if (newCap == 0) { newCap = 1; } - ppNewDevices = (ma_device**)ma__realloc_from_callbacks(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, sizeof(*g_ppTrackedDevices_CoreAudio)*oldCap, &pDevice->pContext->allocationCallbacks); + ppNewDevices = (ma_device**)ma_realloc(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, &pDevice->pContext->allocationCallbacks); if (ppNewDevices == NULL) { ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); return MA_OUT_OF_MEMORY; @@ -26642,7 +32378,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) /* If there's nothing else in the list we need to free memory. */ if (g_TrackedDeviceCount_CoreAudio == 0) { - ma__free_from_callbacks(g_ppTrackedDevices_CoreAudio, &pDevice->pContext->allocationCallbacks); + ma_free(g_ppTrackedDevices_CoreAudio, &pDevice->pContext->allocationCallbacks); g_ppTrackedDevices_CoreAudio = NULL; g_TrackedDeviceCap_CoreAudio = 0; } @@ -26658,30 +32394,71 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) #endif #if defined(MA_APPLE_MOBILE) -@interface ma_router_change_handler:NSObject { +@interface ma_ios_notification_handler:NSObject { ma_device* m_pDevice; } @end -@implementation ma_router_change_handler +@implementation ma_ios_notification_handler -(id)init:(ma_device*)pDevice { self = [super init]; m_pDevice = pDevice; + /* For route changes. */ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_route_change:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]]; + /* For interruptions. */ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handle_interruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; + return self; } -(void)dealloc { [self remove_handler]; + + #if defined(__has_feature) + #if !__has_feature(objc_arc) + [super dealloc]; + #endif + #endif } -(void)remove_handler { [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil]; +} + +-(void)handle_interruption:(NSNotification*)pNotification +{ + NSInteger type = [[[pNotification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey] integerValue]; + switch (type) + { + case AVAudioSessionInterruptionTypeBegan: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Interruption: AVAudioSessionInterruptionTypeBegan\n"); + + /* + Core Audio will have stopped the internal device automatically, but we need explicitly + stop it at a higher level to ensure miniaudio-specific state is updated for consistency. + */ + ma_device_stop(m_pDevice); + + /* + Fire the notification after the device has been stopped to ensure it's in the correct + state when the notification handler is invoked. + */ + ma_device__on_notification_interruption_began(m_pDevice); + } break; + + case AVAudioSessionInterruptionTypeEnded: + { + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Interruption: AVAudioSessionInterruptionTypeEnded\n"); + ma_device__on_notification_interruption_ended(m_pDevice); + } break; + } } -(void)handle_route_change:(NSNotification*)pNotification @@ -26693,66 +32470,45 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) { case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOldDeviceUnavailable\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOldDeviceUnavailable\n"); } break; case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNewDeviceAvailable\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNewDeviceAvailable\n"); } break; case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory\n"); } break; case AVAudioSessionRouteChangeReasonWakeFromSleep: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonWakeFromSleep\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonWakeFromSleep\n"); } break; case AVAudioSessionRouteChangeReasonOverride: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOverride\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonOverride\n"); } break; case AVAudioSessionRouteChangeReasonCategoryChange: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonCategoryChange\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonCategoryChange\n"); } break; case AVAudioSessionRouteChangeReasonUnknown: default: { - ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonUnknown\n"); + ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_INFO, "[Core Audio] Route Changed: AVAudioSessionRouteChangeReasonUnknown\n"); } break; } ma_log_postf(ma_device_get_log(m_pDevice), MA_LOG_LEVEL_DEBUG, "[Core Audio] Changing Route. inputNumberChannels=%d; outputNumberOfChannels=%d\n", (int)pSession.inputNumberOfChannels, (int)pSession.outputNumberOfChannels); - - /* Temporarily disabling this section of code because it appears to be causing errors. */ -#if 0 - ma_uint32 previousState = ma_device_get_state(m_pDevice); - - if (previousState == MA_STATE_STARTED) { - ma_device_stop(m_pDevice); - } - - if (m_pDevice->type == ma_device_type_capture || m_pDevice->type == ma_device_type_duplex) { - m_pDevice->capture.internalChannels = (ma_uint32)pSession.inputNumberOfChannels; - m_pDevice->capture.internalSampleRate = (ma_uint32)pSession.sampleRate; - ma_device__post_init_setup(m_pDevice, ma_device_type_capture); - } - if (m_pDevice->type == ma_device_type_playback || m_pDevice->type == ma_device_type_duplex) { - m_pDevice->playback.internalChannels = (ma_uint32)pSession.outputNumberOfChannels; - m_pDevice->playback.internalSampleRate = (ma_uint32)pSession.sampleRate; - ma_device__post_init_setup(m_pDevice, ma_device_type_playback); - } - - if (previousState == MA_STATE_STARTED) { - ma_device_start(m_pDevice); - } -#endif + + /* Let the application know about the route change. */ + ma_device__on_notification_rerouted(m_pDevice); } @end #endif @@ -26760,7 +32516,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_uninitialized); #if defined(MA_APPLE_DESKTOP) /* @@ -26770,9 +32526,9 @@ static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) ma_device__untrack__coreaudio(pDevice); #endif #if defined(MA_APPLE_MOBILE) - if (pDevice->coreaudio.pRouteChangeHandler != NULL) { - ma_router_change_handler* pRouteChangeHandler = (__bridge_transfer ma_router_change_handler*)pDevice->coreaudio.pRouteChangeHandler; - [pRouteChangeHandler remove_handler]; + if (pDevice->coreaudio.pNotificationHandler != NULL) { + ma_ios_notification_handler* pNotificationHandler = (MA_BRIDGE_TRANSFER ma_ios_notification_handler*)pDevice->coreaudio.pNotificationHandler; + [pNotificationHandler remove_handler]; } #endif @@ -26784,7 +32540,7 @@ static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) } if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -26832,8 +32588,6 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev AURenderCallbackStruct callbackInfo; #if defined(MA_APPLE_DESKTOP) AudioObjectID deviceObjectID; -#else - UInt32 actualPeriodSizeInFramesSize = sizeof(actualPeriodSizeInFrames); #endif /* This API should only be used for a single device type: playback or capture. No full-duplex mode. */ @@ -27087,12 +32841,12 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev } #else /* Fall back to default assumptions. */ - ma_get_standard_channel_map(ma_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); #endif } #else /* TODO: Figure out how to get the channel map using AVAudioSession. */ - ma_get_standard_channel_map(ma_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pData->channelMapOut, ma_countof(pData->channelMapOut), pData->channelsOut); #endif @@ -27118,13 +32872,16 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev } #else /* - I don't know how to configure buffer sizes on iOS so for now we're not allowing it to be configured. Instead we're - just going to set it to the value of kAudioUnitProperty_MaximumFramesPerSlice. + On iOS, the size of the IO buffer needs to be specified in seconds and is a floating point + number. I don't trust any potential truncation errors due to converting from float to integer + so I'm going to explicitly set the actual period size to the next power of 2. */ - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualPeriodSizeInFrames, &actualPeriodSizeInFramesSize); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); + @autoreleasepool { + AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; + MA_ASSERT(pAudioSession != NULL); + + [pAudioSession setPreferredIOBufferDuration:((float)actualPeriodSizeInFrames / pAudioSession.sampleRate) error:nil]; + actualPeriodSizeInFrames = ma_next_power_of_2((ma_uint32)(pAudioSession.IOBufferDuration * pAudioSession.sampleRate)); } #endif @@ -27189,7 +32946,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev /* Initialize the audio unit. */ status = ((ma_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)(pData->audioUnit); if (status != noErr) { - ma__free_from_callbacks(pData->pAudioBufferList, &pContext->allocationCallbacks); + ma_free(pData->pAudioBufferList, &pContext->allocationCallbacks); pData->pAudioBufferList = NULL; ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); @@ -27236,7 +32993,7 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); } if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } } else if (deviceType == ma_device_type_playback) { data.formatIn = pDevice->playback.format; @@ -27269,6 +33026,7 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev if (deviceType == ma_device_type_capture) { #if defined(MA_APPLE_DESKTOP) pDevice->coreaudio.deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; + ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); #endif pDevice->coreaudio.audioUnitCapture = (ma_ptr)data.audioUnit; pDevice->coreaudio.pAudioBufferList = (ma_ptr)data.pAudioBufferList; @@ -27283,6 +33041,7 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev } else if (deviceType == ma_device_type_playback) { #if defined(MA_APPLE_DESKTOP) pDevice->coreaudio.deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; + ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); #endif pDevice->coreaudio.audioUnitPlayback = (ma_ptr)data.audioUnit; @@ -27360,6 +33119,8 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c pDescriptorCapture->periodCount = data.periodsOut; #if defined(MA_APPLE_DESKTOP) + ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDCapture, sizeof(pDevice->capture.id.coreaudio), pDevice->capture.id.coreaudio); + /* If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly switch the device in the background. @@ -27398,7 +33159,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c if (pConfig->deviceType == ma_device_type_duplex) { ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); if (pDevice->coreaudio.pAudioBufferList) { - ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } } return result; @@ -27422,6 +33183,8 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c pDescriptorPlayback->periodCount = data.periodsOut; #if defined(MA_APPLE_DESKTOP) + ma_get_AudioObject_uid(pDevice->pContext, pDevice->coreaudio.deviceObjectIDPlayback, sizeof(pDevice->playback.id.coreaudio), pDevice->playback.id.coreaudio); + /* If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly switch the device in the background. @@ -27445,7 +33208,7 @@ static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_c differently on non-Desktop Apple platforms. */ #if defined(MA_APPLE_MOBILE) - pDevice->coreaudio.pRouteChangeHandler = (__bridge_retained void*)[[ma_router_change_handler alloc] init:pDevice]; + pDevice->coreaudio.pNotificationHandler = (MA_BRIDGE_RETAINED void*)[[ma_ios_notification_handler alloc] init:pDevice]; #endif return MA_SUCCESS; @@ -27510,7 +33273,8 @@ static ma_result ma_context_uninit__coreaudio(ma_context* pContext) #if defined(MA_APPLE_MOBILE) if (!pContext->coreaudio.noAudioSessionDeactivate) { if (![[AVAudioSession sharedInstance] setActive:false error:nil]) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to deactivate audio session.", MA_FAILED_TO_INIT_BACKEND); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to deactivate audio session."); + return MA_FAILED_TO_INIT_BACKEND; } } #endif @@ -27529,7 +33293,7 @@ static ma_result ma_context_uninit__coreaudio(ma_context* pContext) return MA_SUCCESS; } -#if defined(MA_APPLE_MOBILE) +#if defined(MA_APPLE_MOBILE) && defined(__IPHONE_12_0) static AVAudioSessionCategory ma_to_AVAudioSessionCategory(ma_ios_session_category category) { /* The "default" and "none" categories are treated different and should not be used as an input into this function. */ @@ -27586,15 +33350,21 @@ static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_conte } } else { if (pConfig->coreaudio.sessionCategory != ma_ios_session_category_none) { + #if defined(__IPHONE_12_0) if (![pAudioSession setCategory: ma_to_AVAudioSessionCategory(pConfig->coreaudio.sessionCategory) withOptions:options error:nil]) { return MA_INVALID_OPERATION; /* Failed to set session category. */ } + #else + /* Ignore the session category on version 11 and older, but post a warning. */ + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Session category only supported in iOS 12 and newer."); + #endif } } if (!pConfig->coreaudio.noAudioSessionActivate) { if (![pAudioSession setActive:true error:nil]) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to activate audio session.", MA_FAILED_TO_INIT_BACKEND); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to activate audio session."); + return MA_FAILED_TO_INIT_BACKEND; } } } @@ -28255,7 +34025,7 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic MA_ASSERT(pDevice != NULL); if (deviceType == ma_device_type_capture) { - openFlags = MA_SIO_REC; + openFlags = MA_SIO_REC; } else { openFlags = MA_SIO_PLAY; } @@ -28272,13 +34042,15 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic handle = (ma_ptr)((ma_sio_open_proc)pDevice->pContext->sndio.sio_open)(pDeviceName, openFlags, 0); if (handle == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } /* We need to retrieve the device caps to determine the most appropriate format to use. */ if (((ma_sio_getcap_proc)pDevice->pContext->sndio.sio_getcap)((struct ma_sio_hdl*)handle, &caps) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MA_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps."); + return MA_ERROR; } /* @@ -28372,12 +34144,14 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic if (((ma_sio_setpar_proc)pDevice->pContext->sndio.sio_setpar)((struct ma_sio_hdl*)handle, &par) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size."); + return MA_ERROR; } if (((ma_sio_getpar_proc)pDevice->pContext->sndio.sio_getpar)((struct ma_sio_hdl*)handle, &par) == 0) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size."); + return MA_ERROR; } internalFormat = ma_format_from_sio_enc__sndio(par.bits, par.bps, par.sig, par.le, par.msb); @@ -28395,23 +34169,10 @@ static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_devic pDescriptor->format = internalFormat; pDescriptor->channels = internalChannels; pDescriptor->sampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sndio, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sndio, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; pDescriptor->periodCount = internalPeriods; - #ifdef MA_DEBUG_OUTPUT - { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "DEVICE INFO\n"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Format: %s\n", ma_get_format_name(internalFormat)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Channels: %d\n", internalChannels); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Sample Rate: %d\n", internalSampleRate); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Period Size: %d\n", internalPeriodSizeInFrames); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " Periods: %d\n", internalPeriods); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " appbufsz: %d\n", par.appbufsz); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, " round: %d\n", par.round); - } - #endif - return MA_SUCCESS; } @@ -28492,7 +34253,8 @@ static ma_result ma_device_write__sndio(ma_device* pDevice, const void* pPCMFram result = ((ma_sio_write_proc)pDevice->pContext->sndio.sio_write)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (result == 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device.", MA_IO_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to send data from the client to the device."); + return MA_IO_ERROR; } if (pFramesWritten != NULL) { @@ -28512,7 +34274,8 @@ static ma_result ma_device_read__sndio(ma_device* pDevice, void* pPCMFrames, ma_ result = ((ma_sio_read_proc)pDevice->pContext->sndio.sio_read)((struct ma_sio_hdl*)pDevice->sndio.handleCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (result == 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device.", MA_IO_ERROR); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[sndio] Failed to read data from the device to be sent to the device."); + return MA_IO_ERROR; } if (pFramesRead != NULL) { @@ -29055,7 +34818,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c } if (fd == -1) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device."); + return ma_result_from_errno(errno); } #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ @@ -29107,12 +34871,14 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed."); + return ma_result_from_errno(errno); } if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed."); + return ma_result_from_errno(errno); } if (deviceType == ma_device_type_capture) { @@ -29127,7 +34893,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } /* Buffer. */ @@ -29153,7 +34920,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c fdInfo.blocksize = internalPeriodSizeInBytes; if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed."); + return ma_result_from_errno(errno); } internalPeriods = fdInfo.hiwat; @@ -29167,7 +34935,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c /* We need to retrieve the format of the device so we can know the channel count and sample rate. Then we can calculate the buffer size. */ if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters."); + return ma_result_from_errno(errno); } internalFormat = ma_format_from_swpar__audio4(&fdPar); @@ -29176,7 +34945,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } /* Buffer. */ @@ -29196,12 +34966,14 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (ioctl(fd, AUDIO_SETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters."); + return ma_result_from_errno(errno); } if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters."); + return ma_result_from_errno(errno); } } @@ -29215,7 +34987,8 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c if (internalFormat == ma_format_unknown) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable."); + return MA_FORMAT_NOT_SUPPORTED; } if (deviceType == ma_device_type_capture) { @@ -29227,7 +35000,7 @@ static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_c pDescriptor->format = internalFormat; pDescriptor->channels = internalChannels; pDescriptor->sampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, internalChannels, pDescriptor->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), internalChannels); pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; pDescriptor->periodCount = internalPeriods; @@ -29309,11 +35082,13 @@ static ma_result ma_device_stop_fd__audio4(ma_device* pDevice, int fd) #if !defined(MA_AUDIO4_USE_NEW_API) if (ioctl(fd, AUDIO_FLUSH, 0) < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_FLUSH failed."); + return ma_result_from_errno(errno); } #else if (ioctl(fd, AUDIO_STOP, 0) < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to stop device. AUDIO_STOP failed."); + return ma_result_from_errno(errno); } #endif @@ -29361,7 +35136,8 @@ static ma_result ma_device_write__audio4(ma_device* pDevice, const void* pPCMFra result = write(pDevice->audio4.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (result < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to write data to the device."); + return ma_result_from_errno(errno); } if (pFramesWritten != NULL) { @@ -29381,7 +35157,8 @@ static ma_result ma_device_read__audio4(ma_device* pDevice, void* pPCMFrames, ma result = read(pDevice->audio4.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (result < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[audio4] Failed to read data from the device."); + return ma_result_from_errno(errno); } if (pFramesRead != NULL) { @@ -29496,7 +35273,8 @@ static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum fd = ma_open_temp_device__oss(); if (fd == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration."); + return MA_NO_BACKEND; } result = ioctl(fd, SNDCTL_SYSINFO, &si); @@ -29543,7 +35321,8 @@ static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum } } else { close(fd); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration."); + return MA_NO_BACKEND; } close(fd); @@ -29626,7 +35405,8 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device fdTemp = ma_open_temp_device__oss(); if (fdTemp == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration."); + return MA_NO_BACKEND; } result = ioctl(fdTemp, SNDCTL_SYSINFO, &si); @@ -29684,7 +35464,8 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device } } else { close(fdTemp); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve system information for device enumeration."); + return MA_NO_BACKEND; } @@ -29774,7 +35555,8 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf result = ma_context_open_device__oss(pDevice->pContext, deviceType, pDeviceID, shareMode, &fd); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } /* @@ -29788,21 +35570,24 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf ossResult = ioctl(fd, SNDCTL_DSP_SETFMT, &ossFormat); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set format."); + return ma_result_from_errno(errno); } /* Channels. */ ossResult = ioctl(fd, SNDCTL_DSP_CHANNELS, &ossChannels); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set channel count."); + return ma_result_from_errno(errno); } /* Sample Rate. */ ossResult = ioctl(fd, SNDCTL_DSP_SPEED, &ossSampleRate); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set sample rate."); + return ma_result_from_errno(errno); } /* @@ -29836,7 +35621,8 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf ossResult = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &ossFragment); if (ossResult == -1) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to set fragment size and period count."); + return ma_result_from_errno(errno); } } @@ -29850,12 +35636,13 @@ static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_conf pDescriptor->format = ma_format_from_oss(ossFormat); pDescriptor->channels = ossChannels; pDescriptor->sampleRate = ossSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, pDescriptor->channels, pDescriptor->channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_sound4, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); pDescriptor->periodCount = (ma_uint32)(ossFragment >> 16); pDescriptor->periodSizeInFrames = (ma_uint32)(1 << (ossFragment & 0xFFFF)) / ma_get_bytes_per_frame(pDescriptor->format, pDescriptor->channels); if (pDescriptor->format == ma_format_unknown) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio."); + return MA_FORMAT_NOT_SUPPORTED; } return MA_SUCCESS; @@ -29875,14 +35662,16 @@ static ma_result ma_device_init__oss(ma_device* pDevice, const ma_device_config* if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device."); + return result; } } @@ -29939,13 +35728,14 @@ static ma_result ma_device_write__oss(ma_device* pDevice, const void* pPCMFrames /* Don't do any processing if the device is stopped. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTED && deviceState != MA_STATE_STARTING) { + if (deviceState != ma_device_state_started && deviceState != ma_device_state_starting) { return MA_SUCCESS; } resultOSS = write(pDevice->oss.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (resultOSS < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device."); + return ma_result_from_errno(errno); } if (pFramesWritten != NULL) { @@ -29966,13 +35756,14 @@ static ma_result ma_device_read__oss(ma_device* pDevice, void* pPCMFrames, ma_ui /* Don't do any processing if the device is stopped. */ deviceState = ma_device_get_state(pDevice); - if (deviceState != MA_STATE_STARTED && deviceState != MA_STATE_STARTING) { + if (deviceState != ma_device_state_started && deviceState != ma_device_state_starting) { return MA_SUCCESS; } resultOSS = read(pDevice->oss.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (resultOSS < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client.", ma_result_from_errno(errno)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client."); + return ma_result_from_errno(errno); } if (pFramesRead != NULL) { @@ -30004,7 +35795,8 @@ static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_con /* Try opening a temporary device first so we can get version information. This is closed at the end. */ fd = ma_open_temp_device__oss(); if (fd == -1) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties.", MA_NO_BACKEND); /* Looks liks OSS isn't installed, or there are no available devices. */ + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to open temporary device for retrieving system properties."); /* Looks liks OSS isn't installed, or there are no available devices. */ + return MA_NO_BACKEND; } /* Grab the OSS version. */ @@ -30012,7 +35804,8 @@ static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_con result = ioctl(fd, OSS_GETVERSION, &ossVersion); if (result == -1) { close(fd); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version.", MA_NO_BACKEND); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version."); + return MA_NO_BACKEND; } /* The file handle to temp device is no longer needed. Close ASAP. */ @@ -30238,14 +36031,29 @@ static void ma_stream_error_callback__aaudio(ma_AAudioStream* pStream, void* pUs (void)error; - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d\n", error, ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] ERROR CALLBACK: error=%d, AAudioStream_getState()=%d\n", error, ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream)); /* From the documentation for AAudio, when a device is disconnected all we can do is stop it. However, we cannot stop it from the callback - we need to do it from another thread. Therefore we are going to use an event thread for the AAudio backend to do this cleanly and safely. */ if (((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream) == MA_AAUDIO_STREAM_STATE_DISCONNECTED) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "[AAudio] Device Disconnected.\n"); + /* We need to post a job to the job thread for processing. This will reroute the device by reinitializing the stream. */ + ma_result result; + ma_job job = ma_job_init(MA_JOB_TYPE_DEVICE_AAUDIO_REROUTE); + job.data.device.aaudio.reroute.pDevice = pDevice; + + if (pStream == pDevice->aaudio.pStreamCapture) { + job.data.device.aaudio.reroute.deviceType = ma_device_type_capture; + } else { + job.data.device.aaudio.reroute.deviceType = ma_device_type_playback; + } + + result = ma_device_job_thread_post(&pDevice->pContext->aaudio.jobThread, &job); + if (result != MA_SUCCESS) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[AAudio] Device Disconnected. Failed to post job for rerouting.\n"); + return; + } } } @@ -30391,8 +36199,9 @@ static ma_result ma_open_stream__aaudio(ma_device* pDevice, const ma_device_conf ma_result result; ma_AAudioStreamBuilder* pBuilder; - MA_ASSERT(pConfig != NULL); - MA_ASSERT(pConfig->deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pDescriptor != NULL); + MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ *ppStream = NULL; @@ -30576,9 +36385,9 @@ static ma_result ma_device_init_by_type__aaudio(ma_device* pDevice, const ma_dev /* For the channel map we need to be sure we don't overflow any buffers. */ if (pDescriptor->channels <= MA_MAX_CHANNELS) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptor->channels, pDescriptor->channelMap); /* <-- Cannot find info on channel order, so assuming a default. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); /* <-- Cannot find info on channel order, so assuming a default. */ } else { - ma_channel_map_init_blank(MA_MAX_CHANNELS, pDescriptor->channelMap); /* Too many channels. Use a blank channel map. */ + ma_channel_map_init_blank(pDescriptor->channelMap, MA_MAX_CHANNELS); /* Too many channels. Use a blank channel map. */ } bufferCapacityInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pDevice->pContext->aaudio.AAudioStream_getBufferCapacityInFrames)(pStream); @@ -30607,11 +36416,10 @@ static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_conf return MA_DEVICE_TYPE_NOT_SUPPORTED; } - /* No exclusive mode with AAudio. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } + pDevice->aaudio.usage = pConfig->aaudio.usage; + pDevice->aaudio.contentType = pConfig->aaudio.contentType; + pDevice->aaudio.inputPreset = pConfig->aaudio.inputPreset; + pDevice->aaudio.noAutoStartAfterReroute = pConfig->aaudio.noAutoStartAfterReroute; if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { result = ma_device_init_by_type__aaudio(pDevice, pConfig, ma_device_type_capture, pDescriptorCapture, (ma_AAudioStream**)&pDevice->aaudio.pStreamCapture); @@ -30676,6 +36484,10 @@ static ma_result ma_device_stop_stream__aaudio(ma_device* pDevice, ma_AAudioStre This maps with miniaudio's requirement that device's be drained which means we don't need to implement any draining logic. */ + currentState = ((MA_PFN_AAudioStream_getState)pDevice->pContext->aaudio.AAudioStream_getState)(pStream); + if (currentState == MA_AAUDIO_STREAM_STATE_DISCONNECTED) { + return MA_SUCCESS; /* The device is disconnected. Don't try stopping it. */ + } resultAA = ((MA_PFN_AAudioStream_requestStop)pDevice->pContext->aaudio.AAudioStream_requestStop)(pStream); if (resultAA != MA_AAUDIO_OK) { @@ -30726,8 +36538,6 @@ static ma_result ma_device_start__aaudio(ma_device* pDevice) static ma_result ma_device_stop__aaudio(ma_device* pDevice) { - ma_stop_proc onStop; - MA_ASSERT(pDevice != NULL); if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { @@ -30744,11 +36554,131 @@ static ma_result ma_device_stop__aaudio(ma_device* pDevice) } } - onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); + ma_device__on_notification_stopped(pDevice); + + return MA_SUCCESS; +} + +static ma_result ma_device_reinit__aaudio(ma_device* pDevice, ma_device_type deviceType) +{ + ma_result result; + + MA_ASSERT(pDevice != NULL); + + /* The first thing to do is close the streams. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); + pDevice->aaudio.pStreamCapture = NULL; } + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); + pDevice->aaudio.pStreamPlayback = NULL; + } + + /* Now we need to reinitialize each streams. The hardest part with this is just filling output the config and descriptors. */ + { + ma_device_config deviceConfig; + ma_device_descriptor descriptorPlayback; + ma_device_descriptor descriptorCapture; + + deviceConfig = ma_device_config_init(deviceType); + deviceConfig.playback.pDeviceID = NULL; /* Only doing rerouting with default devices. */ + deviceConfig.playback.shareMode = pDevice->playback.shareMode; + deviceConfig.playback.format = pDevice->playback.format; + deviceConfig.playback.channels = pDevice->playback.channels; + deviceConfig.capture.pDeviceID = NULL; /* Only doing rerouting with default devices. */ + deviceConfig.capture.shareMode = pDevice->capture.shareMode; + deviceConfig.capture.format = pDevice->capture.format; + deviceConfig.capture.channels = pDevice->capture.channels; + deviceConfig.sampleRate = pDevice->sampleRate; + deviceConfig.aaudio.usage = pDevice->aaudio.usage; + deviceConfig.aaudio.contentType = pDevice->aaudio.contentType; + deviceConfig.aaudio.inputPreset = pDevice->aaudio.inputPreset; + deviceConfig.aaudio.noAutoStartAfterReroute = pDevice->aaudio.noAutoStartAfterReroute; + deviceConfig.periods = 1; + + /* Try to get an accurate period size. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + deviceConfig.periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; + } else { + deviceConfig.periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; + } + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + descriptorCapture.pDeviceID = deviceConfig.capture.pDeviceID; + descriptorCapture.shareMode = deviceConfig.capture.shareMode; + descriptorCapture.format = deviceConfig.capture.format; + descriptorCapture.channels = deviceConfig.capture.channels; + descriptorCapture.sampleRate = deviceConfig.sampleRate; + descriptorCapture.periodSizeInFrames = deviceConfig.periodSizeInFrames; + descriptorCapture.periodCount = deviceConfig.periods; + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + descriptorPlayback.pDeviceID = deviceConfig.playback.pDeviceID; + descriptorPlayback.shareMode = deviceConfig.playback.shareMode; + descriptorPlayback.format = deviceConfig.playback.format; + descriptorPlayback.channels = deviceConfig.playback.channels; + descriptorPlayback.sampleRate = deviceConfig.sampleRate; + descriptorPlayback.periodSizeInFrames = deviceConfig.periodSizeInFrames; + descriptorPlayback.periodCount = deviceConfig.periods; + } + + result = ma_device_init__aaudio(pDevice, &deviceConfig, &descriptorPlayback, &descriptorCapture); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_device_post_init(pDevice, deviceType, &descriptorPlayback, &descriptorCapture); + if (result != MA_SUCCESS) { + ma_device_uninit__aaudio(pDevice); + return result; + } + + /* We'll only ever do this in response to a reroute. */ + ma_device__on_notification_rerouted(pDevice); + + /* If the device is started, start the streams. Maybe make this configurable? */ + if (ma_device_get_state(pDevice) == ma_device_state_started) { + if (pDevice->aaudio.noAutoStartAfterReroute == MA_FALSE) { + ma_device_start__aaudio(pDevice); + } else { + ma_device_stop(pDevice); /* Do a full device stop so we set internal state correctly. */ + } + } + + return MA_SUCCESS; + } +} + +static ma_result ma_device_get_info__aaudio(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo) +{ + ma_AAudioStream* pStream = NULL; + + MA_ASSERT(pDevice != NULL); + MA_ASSERT(type != ma_device_type_duplex); + MA_ASSERT(pDeviceInfo != NULL); + + if (type == ma_device_type_playback) { + pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamCapture; + pDeviceInfo->id.aaudio = pDevice->capture.id.aaudio; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ + } + if (type == ma_device_type_capture) { + pStream = (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback; + pDeviceInfo->id.aaudio = pDevice->playback.id.aaudio; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); /* Only supporting default devices. */ + } + + /* Safety. Should never happen. */ + if (pStream == NULL) { + return MA_INVALID_OPERATION; + } + + pDeviceInfo->nativeDataFormatCount = 0; + ma_context_add_native_data_format_from_AAudioStream__aaudio(pDevice->pContext, pStream, 0, pDeviceInfo); + return MA_SUCCESS; } @@ -30758,6 +36688,8 @@ static ma_result ma_context_uninit__aaudio(ma_context* pContext) MA_ASSERT(pContext != NULL); MA_ASSERT(pContext->backend == ma_backend_aaudio); + ma_device_job_thread_uninit(&pContext->aaudio.jobThread, &pContext->allocationCallbacks); + ma_dlclose(pContext, pContext->aaudio.hAAudio); pContext->aaudio.hAAudio = NULL; @@ -30823,10 +36755,47 @@ static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_ pCallbacks->onDeviceRead = NULL; /* Not used because AAudio is asynchronous. */ pCallbacks->onDeviceWrite = NULL; /* Not used because AAudio is asynchronous. */ pCallbacks->onDeviceDataLoop = NULL; /* Not used because AAudio is asynchronous. */ + pCallbacks->onDeviceGetInfo = ma_device_get_info__aaudio; + + + /* We need a job thread so we can deal with rerouting. */ + { + ma_result result; + ma_device_job_thread_config jobThreadConfig; + + jobThreadConfig = ma_device_job_thread_config_init(); + + result = ma_device_job_thread_init(&jobThreadConfig, &pContext->allocationCallbacks, &pContext->aaudio.jobThread); + if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->aaudio.hAAudio); + pContext->aaudio.hAAudio = NULL; + return result; + } + } + (void)pConfig; return MA_SUCCESS; } + +static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) +{ + ma_device* pDevice; + + MA_ASSERT(pJob != NULL); + + pDevice = (ma_device*)pJob->data.device.aaudio.reroute.pDevice; + MA_ASSERT(pDevice != NULL); + + /* Here is where we need to reroute the device. To do this we need to uninitialize the stream and reinitialize it. */ + return ma_device_reinit__aaudio(pDevice, (ma_device_type)pJob->data.device.aaudio.reroute.deviceType); +} +#else +/* Getting here means there is no AAudio backend so we need a no-op job implementation. */ +static ma_result ma_job_process__device__aaudio_reroute(ma_job* pJob) +{ + return ma_job_process__noop(pJob); +} #endif /* AAudio */ @@ -31153,6 +37122,7 @@ return_default_device:; if (cbResult) { ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); + deviceInfo.id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); } @@ -31161,6 +37131,7 @@ return_default_device:; if (cbResult) { ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); + deviceInfo.id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } @@ -31259,10 +37230,12 @@ return_default_device: } } - /* Name / Description */ + /* ID and Name / Description */ if (deviceType == ma_device_type_playback) { + pDeviceInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); } else { + pDeviceInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } @@ -31309,7 +37282,7 @@ static void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBuff */ /* Don't do anything if the device is not started. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { return; } @@ -31343,7 +37316,7 @@ static void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBuf (void)pBufferQueue; /* Don't do anything if the device is not started. */ - if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != ma_device_state_started) { return; } @@ -31380,7 +37353,7 @@ static ma_result ma_device_uninit__opensl(ma_device* pDevice) MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioRecorderObj); } - ma__free_from_callbacks(pDevice->opensl.pBufferCapture, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->opensl.pBufferCapture, &pDevice->pContext->allocationCallbacks); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { @@ -31391,7 +37364,7 @@ static ma_result ma_device_uninit__opensl(ma_device* pDevice) MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Destroy((SLObjectItf)pDevice->opensl.pOutputMixObj); } - ma__free_from_callbacks(pDevice->opensl.pBufferPlayback, &pDevice->pContext->allocationCallbacks); + ma_free(pDevice->opensl.pBufferPlayback, &pDevice->pContext->allocationCallbacks); } return MA_SUCCESS; @@ -31428,8 +37401,8 @@ static ma_result ma_SLDataFormat_PCM_init__opensl(ma_format format, ma_uint32 ch #endif pDataFormat->numChannels = channels; - ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = ma_round_to_standard_sample_rate__opensl(sampleRate) * 1000; /* In millihertz. Annoyingly, the sample rate variable is named differently between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM */ - pDataFormat->bitsPerSample = ma_get_bytes_per_sample(format)*8; + ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = ma_round_to_standard_sample_rate__opensl(sampleRate * 1000); /* In millihertz. Annoyingly, the sample rate variable is named differently between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM */ + pDataFormat->bitsPerSample = ma_get_bytes_per_sample(format) * 8; pDataFormat->channelMask = ma_channel_map_to_channel_mask__opensl(channelMap, channels); pDataFormat->endianness = (ma_is_little_endian()) ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; @@ -31556,7 +37529,7 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf locatorDevice.locatorType = SL_DATALOCATOR_IODEVICE; locatorDevice.deviceType = SL_IODEVICE_AUDIOINPUT; - locatorDevice.deviceID = (pDescriptorCapture->pDeviceID == NULL) ? SL_DEFAULTDEVICEID_AUDIOINPUT : pDescriptorCapture->pDeviceID->opensl; + locatorDevice.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; /* Must always use the default device with Android. */ locatorDevice.device = NULL; source.pLocator = &locatorDevice; @@ -31568,20 +37541,21 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf sink.pFormat = (SLDataFormat_PCM*)&pcm; resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); - if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED) { + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED || resultSL == SL_RESULT_PARAMETER_INVALID) { /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ pcm.formatType = SL_DATAFORMAT_PCM; pcm.numChannels = 1; ((SLDataFormat_PCM*)&pcm)->samplesPerSec = SL_SAMPLINGRATE_16; /* The name of the sample rate variable is different between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM. */ pcm.bitsPerSample = 16; pcm.containerSize = pcm.bitsPerSample; /* Always tightly packed for now. */ - pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + pcm.channelMask = 0; resultSL = (*g_maEngineSL)->CreateAudioRecorder(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); } if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder."); + return ma_result_from_OpenSL(resultSL); } @@ -31600,25 +37574,29 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Realize((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_RECORD, &pDevice->opensl.pAudioRecorder); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueueCapture); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, ma_buffer_queue_callback_capture__opensl_android, pDevice); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); + return ma_result_from_OpenSL(resultSL); } /* The internal format is determined by the "pcm" object. */ @@ -31629,10 +37607,11 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf pDevice->opensl.currentBufferIndexCapture = 0; bufferSizeInBytes = pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodCount; - pDevice->opensl.pBufferCapture = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); + pDevice->opensl.pBufferCapture = (ma_uint8*)ma_calloc(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); if (pDevice->opensl.pBufferCapture == NULL) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer."); + return MA_OUT_OF_MEMORY; } MA_ZERO_MEMORY(pDevice->opensl.pBufferCapture, bufferSizeInBytes); } @@ -31649,19 +37628,22 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = (*g_maEngineSL)->CreateOutputMix(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pOutputMixObj, 0, NULL, NULL); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create output mix."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Realize((SLObjectItf)pDevice->opensl.pOutputMixObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->GetInterface((SLObjectItf)pDevice->opensl.pOutputMixObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_OUTPUTMIX, &pDevice->opensl.pOutputMix); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface."); + return ma_result_from_OpenSL(resultSL); } /* Set the output device. */ @@ -31682,7 +37664,7 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf sink.pFormat = NULL; resultSL = (*g_maEngineSL)->CreateAudioPlayer(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioPlayerObj, &source, &sink, ma_countof(itfIDs), itfIDs, itfIDsRequired); - if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED) { + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED || resultSL == SL_RESULT_PARAMETER_INVALID) { /* Unsupported format. Fall back to something safer and try again. If this fails, just abort. */ pcm.formatType = SL_DATAFORMAT_PCM; pcm.numChannels = 2; @@ -31695,7 +37677,8 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player."); + return ma_result_from_OpenSL(resultSL); } @@ -31714,25 +37697,29 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Realize((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_PLAY, &pDevice->opensl.pAudioPlayer); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueuePlayback); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface."); + return ma_result_from_OpenSL(resultSL); } resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, ma_buffer_queue_callback_playback__opensl_android, pDevice); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to register buffer queue callback."); + return ma_result_from_OpenSL(resultSL); } /* The internal format is determined by the "pcm" object. */ @@ -31743,10 +37730,11 @@ static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_conf pDevice->opensl.currentBufferIndexPlayback = 0; bufferSizeInBytes = pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodCount; - pDevice->opensl.pBufferPlayback = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); + pDevice->opensl.pBufferPlayback = (ma_uint8*)ma_calloc(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); if (pDevice->opensl.pBufferPlayback == NULL) { ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer.", MA_OUT_OF_MEMORY); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer."); + return MA_OUT_OF_MEMORY; } MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, bufferSizeInBytes); } @@ -31773,7 +37761,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_RECORDING); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal capture device."); + return ma_result_from_OpenSL(resultSL); } periodSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); @@ -31781,7 +37770,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, pDevice->opensl.pBufferCapture + (periodSizeInBytes * iPeriod), periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for capture device."); + return ma_result_from_OpenSL(resultSL); } } } @@ -31789,7 +37779,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_PLAYING); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to start internal playback device."); + return ma_result_from_OpenSL(resultSL); } /* In playback mode (no duplex) we need to load some initial buffers. In duplex mode we need to enqueu silent buffers. */ @@ -31804,7 +37795,8 @@ static ma_result ma_device_start__opensl(ma_device* pDevice) resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, pDevice->opensl.pBufferPlayback + (periodSizeInBytes * iPeriod), periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to enqueue buffer for playback device."); + return ma_result_from_OpenSL(resultSL); } } } @@ -31849,7 +37841,6 @@ static ma_result ma_device_drain__opensl(ma_device* pDevice, ma_device_type devi static ma_result ma_device_stop__opensl(ma_device* pDevice) { SLresult resultSL; - ma_stop_proc onStop; MA_ASSERT(pDevice != NULL); @@ -31863,7 +37854,8 @@ static ma_result ma_device_stop__opensl(ma_device* pDevice) resultSL = MA_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal capture device."); + return ma_result_from_OpenSL(resultSL); } MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture); @@ -31874,17 +37866,15 @@ static ma_result ma_device_stop__opensl(ma_device* pDevice) resultSL = MA_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); if (resultSL != SL_RESULT_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device.", ma_result_from_OpenSL(resultSL)); + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to stop internal playback device."); + return ma_result_from_OpenSL(resultSL); } MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback); } /* Make sure the client is aware that the device has stopped. There may be an OpenSL|ES callback for this, but I haven't found it. */ - onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); return MA_SUCCESS; } @@ -31916,7 +37906,7 @@ static ma_result ma_dlsym_SLInterfaceID__opensl(ma_context* pContext, const char /* We need to return an error if the symbol cannot be found. This is important because there have been reports that some symbols do not exist. */ ma_handle* p = (ma_handle*)ma_dlsym(pContext, pContext->opensl.libOpenSLES, pName); if (p == NULL) { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL|ES] Cannot find symbol %s", pName); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol %s", pName); return MA_NO_BACKEND; } @@ -31979,7 +37969,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ } if (pContext->opensl.libOpenSLES == NULL) { - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Could not find libOpenSLES.so"); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Could not find libOpenSLES.so"); return MA_NO_BACKEND; } @@ -32028,7 +38018,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ pContext->opensl.slCreateEngine = (ma_proc)ma_dlsym(pContext, pContext->opensl.libOpenSLES, "slCreateEngine"); if (pContext->opensl.slCreateEngine == NULL) { ma_dlclose(pContext, pContext->opensl.libOpenSLES); - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Cannot find symbol slCreateEngine."); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Cannot find symbol slCreateEngine."); return MA_NO_BACKEND; } #else @@ -32052,7 +38042,7 @@ static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_ if (result != MA_SUCCESS) { ma_dlclose(pContext, pContext->opensl.libOpenSLES); - ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Failed to initialize OpenSL engine."); + ma_log_post(ma_context_get_log(pContext), MA_LOG_LEVEL_INFO, "[OpenSL] Failed to initialize OpenSL engine."); return result; } @@ -32290,6 +38280,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d sampleRate = (pDescriptor->sampleRate > 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE; periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptor, sampleRate, pConfig->performanceProfile); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_DEBUG, "periodSizeInFrames = %d\n", (int)periodSizeInFrames); /* We create the device on the JavaScript side and reference it using an index. We use this to make it possible to reference the device between JavaScript and C. */ deviceIndex = EM_ASM_INT({ @@ -32299,7 +38290,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d var isCapture = $3; var pDevice = $4; - if (typeof(miniaudio) === 'undefined') { + if (typeof(window.miniaudio) === 'undefined') { return -1; /* Context not initialized. */ } @@ -32308,7 +38299,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d /* The AudioContext must be created in a suspended state. */ device.webaudio = new (window.AudioContext || window.webkitAudioContext)({sampleRate:sampleRate}); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ /* We need an intermediary buffer which we use for JavaScript and C interop. This buffer stores interleaved f32 PCM data. Because it's passed between @@ -32334,7 +38325,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d how well this would work. Although ScriptProccessorNode is deprecated, in practice it seems to have pretty good browser support so I'm leaving it like this for now. If anyone knows how I could get raw PCM data using the MediaRecorder API please let me know! */ - device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, channels, channels); + device.scriptNode = device.webaudio.createScriptProcessor(bufferSize, (isCapture) ? channels : 0, (isCapture) ? 0 : channels); if (isCapture) { device.scriptNode.onaudioprocess = function(e) { @@ -32342,7 +38333,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d return; /* This means the device has been uninitialized. */ } - if(device.intermediaryBufferView.length == 0) { + if (device.intermediaryBufferView.length == 0) { /* Recreate intermediaryBufferView when losing reference to the underlying buffer, probably due to emscripten resizing heap. */ device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, device.intermediaryBuffer, device.intermediaryBufferSizeInBytes); } @@ -32440,8 +38431,10 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d } } else { for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { + var outputBuffer = e.outputBuffer.getChannelData(iChannel); + var intermediaryBuffer = device.intermediaryBufferView; for (var iFrame = 0; iFrame < framesToProcess; ++iFrame) { - e.outputBuffer.getChannelData(iChannel)[totalFramesProcessed + iFrame] = device.intermediaryBufferView[iFrame*channels + iChannel]; + outputBuffer[totalFramesProcessed + iFrame] = intermediaryBuffer[iFrame*channels + iChannel]; } } } @@ -32466,12 +38459,12 @@ static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_d pDevice->webaudio.indexPlayback = deviceIndex; } - pDescriptor->format = ma_format_f32; - pDescriptor->channels = channels; - ma_get_standard_channel_map(ma_standard_channel_map_webaudio, pDescriptor->channels, pDescriptor->channelMap); - pDescriptor->sampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); - pDescriptor->periodSizeInFrames = periodSizeInFrames; - pDescriptor->periodCount = 1; + pDescriptor->format = ma_format_f32; + pDescriptor->channels = channels; + ma_channel_map_init_standard(ma_standard_channel_map_webaudio, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); + pDescriptor->sampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); + pDescriptor->periodSizeInFrames = periodSizeInFrames; + pDescriptor->periodCount = 1; return MA_SUCCESS; } @@ -32518,7 +38511,7 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.resume(); - device.state = 2; /* MA_STATE_STARTED */ + device.state = 2; /* ma_device_state_started */ }, pDevice->webaudio.indexCapture); } @@ -32526,7 +38519,7 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.resume(); - device.state = 2; /* MA_STATE_STARTED */ + device.state = 2; /* ma_device_state_started */ }, pDevice->webaudio.indexPlayback); } @@ -32551,7 +38544,7 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ }, pDevice->webaudio.indexCapture); } @@ -32559,14 +38552,11 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) EM_ASM({ var device = miniaudio.get_device_by_index($0); device.webaudio.suspend(); - device.state = 1; /* MA_STATE_STOPPED */ + device.state = 1; /* ma_device_state_stopped */ }, pDevice->webaudio.indexPlayback); } - ma_stop_proc onStop = pDevice->onStop; - if (onStop) { - onStop(pDevice); - } + ma_device__on_notification_stopped(pDevice); return MA_SUCCESS; } @@ -32596,8 +38586,8 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex return 0; /* Web Audio not supported. */ } - if (typeof(miniaudio) === 'undefined') { - miniaudio = {}; + if (typeof(window.miniaudio) === 'undefined') { + window.miniaudio = {}; miniaudio.devices = []; /* Device cache for mapping devices to indexes for JavaScript/C interop. */ miniaudio.track_device = function(device) { @@ -32647,7 +38637,7 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex miniaudio.unlock = function() { for(var i = 0; i < miniaudio.devices.length; ++i) { var device = miniaudio.devices[i]; - if (device != null && device.webaudio != null && device.state === 2 /* MA_STATE_STARTED */) { + if (device != null && device.webaudio != null && device.state === 2 /* ma_device_state_started */) { device.webaudio.resume(); } } @@ -32686,10 +38676,10 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex -static ma_bool32 ma__is_channel_map_valid(const ma_channel* channelMap, ma_uint32 channels) +static ma_bool32 ma__is_channel_map_valid(const ma_channel* pChannelMap, ma_uint32 channels) { /* A blank channel map should be allowed, in which case it should use an appropriate default which will depend on context. */ - if (channelMap[0] != MA_CHANNEL_NONE) { + if (pChannelMap != NULL && pChannelMap[0] != MA_CHANNEL_NONE) { ma_uint32 iChannel; if (channels == 0 || channels > MA_MAX_CHANNELS) { @@ -32700,7 +38690,7 @@ static ma_bool32 ma__is_channel_map_valid(const ma_channel* channelMap, ma_uint3 for (iChannel = 0; iChannel < channels; ++iChannel) { ma_uint32 jChannel; for (jChannel = iChannel + 1; jChannel < channels; ++jChannel) { - if (channelMap[iChannel] == channelMap[jChannel]) { + if (pChannelMap[iChannel] == pChannelMap[jChannel]) { return MA_FALSE; } } @@ -32730,9 +38720,9 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d ma_channel_map_copy(pDevice->capture.channelMap, pDevice->capture.internalChannelMap, pDevice->capture.channels); } else { if (pDevice->capture.channelMixMode == ma_channel_mix_mode_simple) { - ma_channel_map_init_blank(pDevice->capture.channels, pDevice->capture.channelMap); + ma_channel_map_init_blank(pDevice->capture.channelMap, pDevice->capture.channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->capture.channels, pDevice->capture.channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDevice->capture.channelMap, ma_countof(pDevice->capture.channelMap), pDevice->capture.channels); } } } @@ -32751,9 +38741,9 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d ma_channel_map_copy(pDevice->playback.channelMap, pDevice->playback.internalChannelMap, pDevice->playback.channels); } else { if (pDevice->playback.channelMixMode == ma_channel_mix_mode_simple) { - ma_channel_map_init_blank(pDevice->playback.channels, pDevice->playback.channelMap); + ma_channel_map_init_blank(pDevice->playback.channelMap, pDevice->playback.channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->playback.channels, pDevice->playback.channelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pDevice->playback.channelMap, ma_countof(pDevice->playback.channelMap), pDevice->playback.channels); } } } @@ -32771,21 +38761,27 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { /* Converting from internal device format to client format. */ ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - converterConfig.formatIn = pDevice->capture.internalFormat; - converterConfig.channelsIn = pDevice->capture.internalChannels; - converterConfig.sampleRateIn = pDevice->capture.internalSampleRate; - ma_channel_map_copy(converterConfig.channelMapIn, pDevice->capture.internalChannelMap, ma_min(pDevice->capture.internalChannels, MA_MAX_CHANNELS)); - converterConfig.formatOut = pDevice->capture.format; - converterConfig.channelsOut = pDevice->capture.channels; - converterConfig.sampleRateOut = pDevice->sampleRate; - ma_channel_map_copy(converterConfig.channelMapOut, pDevice->capture.channelMap, ma_min(pDevice->capture.channels, MA_MAX_CHANNELS)); - converterConfig.channelMixMode = pDevice->capture.channelMixMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; - converterConfig.resampling.algorithm = pDevice->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pDevice->resampling.speex.quality; + converterConfig.formatIn = pDevice->capture.internalFormat; + converterConfig.channelsIn = pDevice->capture.internalChannels; + converterConfig.sampleRateIn = pDevice->capture.internalSampleRate; + converterConfig.pChannelMapIn = pDevice->capture.internalChannelMap; + converterConfig.formatOut = pDevice->capture.format; + converterConfig.channelsOut = pDevice->capture.channels; + converterConfig.sampleRateOut = pDevice->sampleRate; + converterConfig.pChannelMapOut = pDevice->capture.channelMap; + converterConfig.channelMixMode = pDevice->capture.channelMixMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; + converterConfig.resampling.algorithm = pDevice->resampling.algorithm; + converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; + converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; + converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; - result = ma_data_converter_init(&converterConfig, &pDevice->capture.converter); + /* Make sure the old converter is uninitialized first. */ + if (ma_device_get_state(pDevice) != ma_device_state_uninitialized) { + ma_data_converter_uninit(&pDevice->capture.converter, &pDevice->pContext->allocationCallbacks); + } + + result = ma_data_converter_init(&converterConfig, &pDevice->pContext->allocationCallbacks, &pDevice->capture.converter); if (result != MA_SUCCESS) { return result; } @@ -32794,29 +38790,164 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { /* Converting from client format to device format. */ ma_data_converter_config converterConfig = ma_data_converter_config_init_default(); - converterConfig.formatIn = pDevice->playback.format; - converterConfig.channelsIn = pDevice->playback.channels; - converterConfig.sampleRateIn = pDevice->sampleRate; - ma_channel_map_copy(converterConfig.channelMapIn, pDevice->playback.channelMap, ma_min(pDevice->playback.channels, MA_MAX_CHANNELS)); - converterConfig.formatOut = pDevice->playback.internalFormat; - converterConfig.channelsOut = pDevice->playback.internalChannels; - converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; - ma_channel_map_copy(converterConfig.channelMapOut, pDevice->playback.internalChannelMap, ma_min(pDevice->playback.internalChannels, MA_MAX_CHANNELS)); - converterConfig.channelMixMode = pDevice->playback.channelMixMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; - converterConfig.resampling.algorithm = pDevice->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pDevice->resampling.speex.quality; + converterConfig.formatIn = pDevice->playback.format; + converterConfig.channelsIn = pDevice->playback.channels; + converterConfig.sampleRateIn = pDevice->sampleRate; + converterConfig.pChannelMapIn = pDevice->playback.channelMap; + converterConfig.formatOut = pDevice->playback.internalFormat; + converterConfig.channelsOut = pDevice->playback.internalChannels; + converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; + converterConfig.pChannelMapOut = pDevice->playback.internalChannelMap; + converterConfig.channelMixMode = pDevice->playback.channelMixMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; + converterConfig.resampling.algorithm = pDevice->resampling.algorithm; + converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; + converterConfig.resampling.pBackendVTable = pDevice->resampling.pBackendVTable; + converterConfig.resampling.pBackendUserData = pDevice->resampling.pBackendUserData; - result = ma_data_converter_init(&converterConfig, &pDevice->playback.converter); + /* Make sure the old converter is uninitialized first. */ + if (ma_device_get_state(pDevice) != ma_device_state_uninitialized) { + ma_data_converter_uninit(&pDevice->playback.converter, &pDevice->pContext->allocationCallbacks); + } + + result = ma_data_converter_init(&converterConfig, &pDevice->pContext->allocationCallbacks, &pDevice->playback.converter); if (result != MA_SUCCESS) { return result; } } + + /* + In playback mode, if the data converter does not support retrieval of the required number of + input frames given a number of output frames, we need to fall back to a heap-allocated cache. + */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + ma_uint64 unused; + + pDevice->playback.inputCacheConsumed = 0; + pDevice->playback.inputCacheRemaining = 0; + + if (deviceType == ma_device_type_duplex || ma_data_converter_get_required_input_frame_count(&pDevice->playback.converter, 1, &unused) != MA_SUCCESS) { + /* We need a heap allocated cache. We want to size this based on the period size. */ + void* pNewInputCache; + ma_uint64 newInputCacheCap; + ma_uint64 newInputCacheSizeInBytes; + + newInputCacheCap = ma_calculate_frame_count_after_resampling(pDevice->playback.internalSampleRate, pDevice->sampleRate, pDevice->playback.internalPeriodSizeInFrames); + + newInputCacheSizeInBytes = newInputCacheCap * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + if (newInputCacheSizeInBytes > MA_SIZE_MAX) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + return MA_OUT_OF_MEMORY; /* Allocation too big. Should never hit this, but makes the cast below safer for 32-bit builds. */ + } + + pNewInputCache = ma_realloc(pDevice->playback.pInputCache, (size_t)newInputCacheSizeInBytes, &pDevice->pContext->allocationCallbacks); + if (pNewInputCache == NULL) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + return MA_OUT_OF_MEMORY; + } + + pDevice->playback.pInputCache = pNewInputCache; + pDevice->playback.inputCacheCap = newInputCacheCap; + } else { + /* Heap allocation not required. Make sure we clear out the old cache just in case this function was called in response to a route change. */ + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + pDevice->playback.pInputCache = NULL; + pDevice->playback.inputCacheCap = 0; + } + } + return MA_SUCCESS; } +MA_API ma_result ma_device_post_init(ma_device* pDevice, ma_device_type deviceType, const ma_device_descriptor* pDescriptorPlayback, const ma_device_descriptor* pDescriptorCapture) +{ + ma_result result; + + if (pDevice == NULL) { + return MA_INVALID_ARGS; + } + + /* Capture. */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + if (ma_device_descriptor_is_valid(pDescriptorCapture) == MA_FALSE) { + return MA_INVALID_ARGS; + } + + pDevice->capture.internalFormat = pDescriptorCapture->format; + pDevice->capture.internalChannels = pDescriptorCapture->channels; + pDevice->capture.internalSampleRate = pDescriptorCapture->sampleRate; + MA_COPY_MEMORY(pDevice->capture.internalChannelMap, pDescriptorCapture->channelMap, sizeof(pDescriptorCapture->channelMap)); + pDevice->capture.internalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; + pDevice->capture.internalPeriods = pDescriptorCapture->periodCount; + + if (pDevice->capture.internalPeriodSizeInFrames == 0) { + pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptorCapture->periodSizeInMilliseconds, pDescriptorCapture->sampleRate); + } + } + + /* Playback. */ + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + if (ma_device_descriptor_is_valid(pDescriptorPlayback) == MA_FALSE) { + return MA_INVALID_ARGS; + } + + pDevice->playback.internalFormat = pDescriptorPlayback->format; + pDevice->playback.internalChannels = pDescriptorPlayback->channels; + pDevice->playback.internalSampleRate = pDescriptorPlayback->sampleRate; + MA_COPY_MEMORY(pDevice->playback.internalChannelMap, pDescriptorPlayback->channelMap, sizeof(pDescriptorPlayback->channelMap)); + pDevice->playback.internalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; + pDevice->playback.internalPeriods = pDescriptorPlayback->periodCount; + + if (pDevice->playback.internalPeriodSizeInFrames == 0) { + pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptorPlayback->periodSizeInMilliseconds, pDescriptorPlayback->sampleRate); + } + } + + /* + The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info(pDevice, deviceType)` instead. + For loopback devices, we need to retrieve the name of the playback device. + */ + { + ma_device_info deviceInfo; + + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex || deviceType == ma_device_type_loopback) { + result = ma_device_get_info(pDevice, (deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, &deviceInfo); + if (result == MA_SUCCESS) { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); + } else { + /* We failed to retrieve the device info. Fall back to a default name. */ + if (pDescriptorCapture->pDeviceID == NULL) { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), "Capture Device", (size_t)-1); + } + } + } + + if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { + result = ma_device_get_info(pDevice, ma_device_type_playback, &deviceInfo); + if (result == MA_SUCCESS) { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1); + } else { + /* We failed to retrieve the device info. Fall back to a default name. */ + if (pDescriptorPlayback->pDeviceID == NULL) { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), "Playback Device", (size_t)-1); + } + } + } + } + + /* Update data conversion. */ + return ma_device__post_init_setup(pDevice, deviceType); /* TODO: Should probably rename ma_device__post_init_setup() to something better. */ +} + static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) { @@ -32828,17 +38959,17 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) #endif /* - When the device is being initialized it's initial state is set to MA_STATE_UNINITIALIZED. Before returning from + When the device is being initialized it's initial state is set to ma_device_state_uninitialized. Before returning from ma_device_init(), the state needs to be set to something valid. In miniaudio the device's default state immediately after initialization is stopped, so therefore we need to mark the device as such. miniaudio will wait on the worker thread to signal an event to know when the worker thread is ready for action. */ - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); ma_event_signal(&pDevice->stopEvent); for (;;) { /* <-- This loop just keeps the thread alive. The main audio loop is inside. */ ma_result startResult; - ma_result stopResult; /* <-- This will store the result from onDeviceStop(). If it returns an error, we don't fire the onStop callback. */ + ma_result stopResult; /* <-- This will store the result from onDeviceStop(). If it returns an error, we don't fire the stopped notification callback. */ /* We wait on an event to know when something has requested that the device be started and the main loop entered. */ ma_event_wait(&pDevice->wakeupEvent); @@ -32847,7 +38978,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) pDevice->workResult = MA_SUCCESS; /* If the reason for the wake up is that we are terminating, just break from the loop. */ - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { break; } @@ -32856,7 +38987,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) be started will be waiting on an event (pDevice->startEvent) which means we need to make sure we signal the event in both the success and error case. It's important that the state of the device is set _before_ signaling the event. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STARTING); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_starting); /* If the device has a start callback, start it now. */ if (pDevice->pContext->callbacks.onDeviceStart != NULL) { @@ -32865,15 +38996,22 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) startResult = MA_SUCCESS; } + /* + If starting was not successful we'll need to loop back to the start and wait for something + to happen (pDevice->wakeupEvent). + */ if (startResult != MA_SUCCESS) { pDevice->workResult = startResult; - continue; /* Failed to start. Loop back to the start and wait for something to happen (pDevice->wakeupEvent). */ + ma_event_signal(&pDevice->startEvent); /* <-- Always signal the start event so ma_device_start() can return as it'll be waiting on it. */ + continue; } /* Make sure the state is set appropriately. */ - ma_device__set_state(pDevice, MA_STATE_STARTED); + ma_device__set_state(pDevice, ma_device_state_started); /* <-- Set this before signaling the event so that the state is always guaranteed to be good after ma_device_start() has returned. */ ma_event_signal(&pDevice->startEvent); + ma_device__on_notification_started(pDevice); + if (pDevice->pContext->callbacks.onDeviceDataLoop != NULL) { pDevice->pContext->callbacks.onDeviceDataLoop(pDevice); } else { @@ -32889,16 +39027,16 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) } /* - After the device has stopped, make sure an event is posted. Don't post an onStop event if + After the device has stopped, make sure an event is posted. Don't post a stopped event if stopping failed. This can happen on some backends when the underlying stream has been stopped due to the device being physically unplugged or disabled via an OS setting. */ - if (pDevice->onStop && stopResult != MA_SUCCESS) { - pDevice->onStop(pDevice); + if (stopResult == MA_SUCCESS) { + ma_device__on_notification_stopped(pDevice); } /* A function somewhere is waiting for the device to have stopped for real so we need to signal an event to allow it to continue. */ - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); ma_event_signal(&pDevice->stopEvent); } @@ -32917,17 +39055,22 @@ static ma_bool32 ma_device__is_initialized(ma_device* pDevice) return MA_FALSE; } - return ma_device_get_state(pDevice) != MA_STATE_UNINITIALIZED; + return ma_device_get_state(pDevice) != ma_device_state_uninitialized; } #ifdef MA_WIN32 static ma_result ma_context_uninit_backend_apis__win32(ma_context* pContext) { + /* For some reason UWP complains when CoUninitialize() is called. I'm just not going to call it on UWP. */ +#ifdef MA_WIN32_DESKTOP ma_CoUninitialize(pContext); ma_dlclose(pContext, pContext->win32.hUser32DLL); ma_dlclose(pContext, pContext->win32.hOle32DLL); ma_dlclose(pContext, pContext->win32.hAdvapi32DLL); +#else + (void)pContext; +#endif return MA_SUCCESS; } @@ -33087,7 +39230,138 @@ static ma_bool32 ma_context_is_backend_asynchronous(ma_context* pContext) } -MA_API ma_context_config ma_context_config_init() +/* The default capacity doesn't need to be too big. */ +#ifndef MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY +#define MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY 32 +#endif + +MA_API ma_device_job_thread_config ma_device_job_thread_config_init(void) +{ + ma_device_job_thread_config config; + + MA_ZERO_OBJECT(&config); + config.noThread = MA_FALSE; + config.jobQueueCapacity = MA_DEFAULT_DEVICE_JOB_QUEUE_CAPACITY; + config.jobQueueFlags = 0; + + return config; +} + + +static ma_thread_result MA_THREADCALL ma_device_job_thread_entry(void* pUserData) +{ + ma_device_job_thread* pJobThread = (ma_device_job_thread*)pUserData; + MA_ASSERT(pJobThread != NULL); + + for (;;) { + ma_result result; + ma_job job; + + result = ma_device_job_thread_next(pJobThread, &job); + if (result != MA_SUCCESS) { + break; + } + + if (job.toc.breakup.code == MA_JOB_TYPE_QUIT) { + break; + } + + ma_job_process(&job); + } + + return (ma_thread_result)0; +} + +MA_API ma_result ma_device_job_thread_init(const ma_device_job_thread_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_device_job_thread* pJobThread) +{ + ma_result result; + ma_job_queue_config jobQueueConfig; + + if (pJobThread == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pJobThread); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + + /* Initialize the job queue before the thread to ensure it's in a valid state. */ + jobQueueConfig = ma_job_queue_config_init(pConfig->jobQueueFlags, pConfig->jobQueueCapacity); + + result = ma_job_queue_init(&jobQueueConfig, pAllocationCallbacks, &pJobThread->jobQueue); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize job queue. */ + } + + + /* The thread needs to be initialized after the job queue to ensure the thread doesn't try to access it prematurely. */ + if (pConfig->noThread == MA_FALSE) { + result = ma_thread_create(&pJobThread->thread, ma_thread_priority_normal, 0, ma_device_job_thread_entry, pJobThread, pAllocationCallbacks); + if (result != MA_SUCCESS) { + ma_job_queue_uninit(&pJobThread->jobQueue, pAllocationCallbacks); + return result; /* Failed to create the job thread. */ + } + + pJobThread->_hasThread = MA_TRUE; + } else { + pJobThread->_hasThread = MA_FALSE; + } + + + return MA_SUCCESS; +} + +MA_API void ma_device_job_thread_uninit(ma_device_job_thread* pJobThread, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pJobThread == NULL) { + return; + } + + /* The first thing to do is post a quit message to the job queue. If we're using a thread we'll need to wait for it. */ + { + ma_job job = ma_job_init(MA_JOB_TYPE_QUIT); + ma_device_job_thread_post(pJobThread, &job); + } + + /* Wait for the thread to terminate naturally. */ + if (pJobThread->_hasThread) { + ma_thread_wait(&pJobThread->thread); + } + + /* At this point the thread should be terminated so we can safely uninitialize the job queue. */ + ma_job_queue_uninit(&pJobThread->jobQueue, pAllocationCallbacks); +} + +MA_API ma_result ma_device_job_thread_post(ma_device_job_thread* pJobThread, const ma_job* pJob) +{ + if (pJobThread == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_queue_post(&pJobThread->jobQueue, pJob); +} + +MA_API ma_result ma_device_job_thread_next(ma_device_job_thread* pJobThread, ma_job* pJob) +{ + if (pJob == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pJob); + + if (pJobThread == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_queue_next(&pJobThread->jobQueue, pJob); +} + + + +MA_API ma_context_config ma_context_config_init(void) { ma_context_config config; MA_ZERO_OBJECT(&config); @@ -33134,7 +39408,6 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC } } - pContext->logCallback = pConfig->logCallback; pContext->threadPriority = pConfig->threadPriority; pContext->threadStackSize = pConfig->threadStackSize; pContext->pUserData = pConfig->pUserData; @@ -33272,23 +39545,19 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC if (result == MA_SUCCESS) { result = ma_mutex_init(&pContext->deviceEnumLock); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe.\n", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe.\n"); } result = ma_mutex_init(&pContext->deviceInfoLock); if (result != MA_SUCCESS) { - ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. ma_context_get_device_info() is not thread safe.\n", result); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. ma_context_get_device_info() is not thread safe.\n"); } - #ifdef MA_DEBUG_OUTPUT - { - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] Endian: %s\n", ma_is_little_endian() ? "LE" : "BE"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] SSE2: %s\n", ma_has_sse2() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] AVX2: %s\n", ma_has_avx2() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] AVX512F: %s\n", ma_has_avx512f() ? "YES" : "NO"); - ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "[miniaudio] NEON: %s\n", ma_has_neon() ? "YES" : "NO"); - } - #endif + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, "System Architecture:\n"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " Endian: %s\n", ma_is_little_endian() ? "LE" : "BE"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " SSE2: %s\n", ma_has_sse2() ? "YES" : "NO"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " AVX2: %s\n", ma_has_avx2() ? "YES" : "NO"); + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_DEBUG, " NEON: %s\n", ma_has_neon() ? "YES" : "NO"); pContext->backend = backend; return result; @@ -33314,7 +39583,7 @@ MA_API ma_result ma_context_uninit(ma_context* pContext) ma_mutex_uninit(&pContext->deviceEnumLock); ma_mutex_uninit(&pContext->deviceInfoLock); - ma__free_from_callbacks(pContext->pDeviceInfos, &pContext->allocationCallbacks); + ma_free(pContext->pDeviceInfos, &pContext->allocationCallbacks); ma_context_uninit_backend_apis(pContext); if (pContext->pLog == &pContext->log) { @@ -33377,9 +39646,8 @@ static ma_bool32 ma_context_get_devices__enum_callback(ma_context* pContext, ma_ const ma_uint32 totalDeviceInfoCount = pContext->playbackDeviceInfoCount + pContext->captureDeviceInfoCount; if (totalDeviceInfoCount >= pContext->deviceInfoCapacity) { - ma_uint32 oldCapacity = pContext->deviceInfoCapacity; - ma_uint32 newCapacity = oldCapacity + bufferExpansionCount; - ma_device_info* pNewInfos = (ma_device_info*)ma__realloc_from_callbacks(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity, sizeof(*pContext->pDeviceInfos)*oldCapacity, &pContext->allocationCallbacks); + ma_uint32 newCapacity = pContext->deviceInfoCapacity + bufferExpansionCount; + ma_device_info* pNewInfos = (ma_device_info*)ma_realloc(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity, &pContext->allocationCallbacks); if (pNewInfos == NULL) { return MA_FALSE; /* Out of memory. */ } @@ -33461,13 +39729,11 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p return result; } -MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_result result; ma_device_info deviceInfo; - (void)shareMode; /* Unused. This parameter will be removed in version 0.11. */ - /* NOTE: Do not clear pDeviceInfo on entry. The reason is the pDeviceID may actually point to pDeviceInfo->id which will break things. */ if (pContext == NULL || pDeviceInfo == NULL) { return MA_INVALID_ARGS; @@ -33490,81 +39756,6 @@ MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type } ma_mutex_unlock(&pContext->deviceInfoLock); - /* - If the backend is using the new device info system, do a pass to fill out the old settings for backwards compatibility. This will be removed in - the future when all backends have implemented the new device info system. - */ - if (deviceInfo.nativeDataFormatCount > 0) { - ma_uint32 iNativeFormat; - ma_uint32 iSampleFormat; - - deviceInfo.minChannels = 0xFFFFFFFF; - deviceInfo.maxChannels = 0; - deviceInfo.minSampleRate = 0xFFFFFFFF; - deviceInfo.maxSampleRate = 0; - - for (iNativeFormat = 0; iNativeFormat < deviceInfo.nativeDataFormatCount; iNativeFormat += 1) { - /* Formats. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].format == ma_format_unknown) { - /* All formats are supported. */ - deviceInfo.formats[0] = ma_format_u8; - deviceInfo.formats[1] = ma_format_s16; - deviceInfo.formats[2] = ma_format_s24; - deviceInfo.formats[3] = ma_format_s32; - deviceInfo.formats[4] = ma_format_f32; - deviceInfo.formatCount = 5; - } else { - /* Make sure the format isn't already in the list. If so, skip. */ - ma_bool32 alreadyExists = MA_FALSE; - for (iSampleFormat = 0; iSampleFormat < deviceInfo.formatCount; iSampleFormat += 1) { - if (deviceInfo.formats[iSampleFormat] == deviceInfo.nativeDataFormats[iNativeFormat].format) { - alreadyExists = MA_TRUE; - break; - } - } - - if (!alreadyExists) { - deviceInfo.formats[deviceInfo.formatCount++] = deviceInfo.nativeDataFormats[iNativeFormat].format; - } - } - - /* Channels. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].channels == 0) { - /* All channels supported. */ - deviceInfo.minChannels = MA_MIN_CHANNELS; - deviceInfo.maxChannels = MA_MAX_CHANNELS; - } else { - if (deviceInfo.minChannels > deviceInfo.nativeDataFormats[iNativeFormat].channels) { - deviceInfo.minChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels; - } - if (deviceInfo.maxChannels < deviceInfo.nativeDataFormats[iNativeFormat].channels) { - deviceInfo.maxChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels; - } - } - - /* Sample rate. */ - if (deviceInfo.nativeDataFormats[iNativeFormat].sampleRate == 0) { - /* All sample rates supported. */ - deviceInfo.minSampleRate = (ma_uint32)ma_standard_sample_rate_min; - deviceInfo.maxSampleRate = (ma_uint32)ma_standard_sample_rate_max; - } else { - if (deviceInfo.minSampleRate > deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) { - deviceInfo.minSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate; - } - if (deviceInfo.maxSampleRate < deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) { - deviceInfo.maxSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate; - } - } - } - } - - - /* Clamp ranges. */ - deviceInfo.minChannels = ma_max(deviceInfo.minChannels, MA_MIN_CHANNELS); - deviceInfo.maxChannels = ma_min(deviceInfo.maxChannels, MA_MAX_CHANNELS); - deviceInfo.minSampleRate = ma_max(deviceInfo.minSampleRate, (ma_uint32)ma_standard_sample_rate_min); - deviceInfo.maxSampleRate = ma_min(deviceInfo.maxSampleRate, (ma_uint32)ma_standard_sample_rate_max); - *pDeviceInfo = deviceInfo; return result; } @@ -33584,11 +39775,7 @@ MA_API ma_device_config ma_device_config_init(ma_device_type deviceType) ma_device_config config; MA_ZERO_OBJECT(&config); config.deviceType = deviceType; - - /* Resampling defaults. We must never use the Speex backend by default because it uses licensed third party code. */ - config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.resampling.speex.quality = 3; + config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate don't matter here. */ return config; } @@ -33605,92 +39792,92 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } if (pDevice == NULL) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pDevice); if (pConfig == NULL) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pConfig == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } - /* Check that we have our callbacks defined. */ if (pContext->callbacks.onDeviceInit == NULL) { return MA_INVALID_OPERATION; } - /* Basic config validation. */ - if (pConfig->deviceType != ma_device_type_playback && pConfig->deviceType != ma_device_type_capture && pConfig->deviceType != ma_device_type_duplex && pConfig->deviceType != ma_device_type_loopback) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Device type is invalid. Make sure the device type has been set in the config.", MA_INVALID_DEVICE_CONFIG); - } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { if (pConfig->capture.channels > MA_MAX_CHANNELS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Capture channel count cannot exceed 32.", MA_INVALID_DEVICE_CONFIG); + return MA_INVALID_ARGS; } - if (!ma__is_channel_map_valid(pConfig->capture.channelMap, pConfig->capture.channels)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid config. Capture channel map is invalid.", MA_INVALID_DEVICE_CONFIG); + + if (!ma__is_channel_map_valid(pConfig->capture.pChannelMap, pConfig->capture.channels)) { + return MA_INVALID_ARGS; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { if (pConfig->playback.channels > MA_MAX_CHANNELS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Playback channel count cannot exceed 32.", MA_INVALID_DEVICE_CONFIG); + return MA_INVALID_ARGS; } - if (!ma__is_channel_map_valid(pConfig->playback.channelMap, pConfig->playback.channels)) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid config. Playback channel map is invalid.", MA_INVALID_DEVICE_CONFIG); + + if (!ma__is_channel_map_valid(pConfig->playback.pChannelMap, pConfig->playback.channels)) { + return MA_INVALID_ARGS; } } pDevice->pContext = pContext; /* Set the user data and log callback ASAP to ensure it is available for the entire initialization process. */ - pDevice->pUserData = pConfig->pUserData; - pDevice->onData = pConfig->dataCallback; - pDevice->onStop = pConfig->stopCallback; - - if (((ma_uintptr)pDevice % sizeof(pDevice)) != 0) { - if (pContext->logCallback) { - pContext->logCallback(pContext, pDevice, MA_LOG_LEVEL_WARNING, "WARNING: ma_device_init() called for a device that is not properly aligned. Thread safety is not supported."); - } - } + pDevice->pUserData = pConfig->pUserData; + pDevice->onData = pConfig->dataCallback; + pDevice->onNotification = pConfig->notificationCallback; + pDevice->onStop = pConfig->stopCallback; if (pConfig->playback.pDeviceID != NULL) { MA_COPY_MEMORY(&pDevice->playback.id, pConfig->playback.pDeviceID, sizeof(pDevice->playback.id)); + pDevice->playback.pID = &pDevice->playback.id; + } else { + pDevice->playback.pID = NULL; } if (pConfig->capture.pDeviceID != NULL) { MA_COPY_MEMORY(&pDevice->capture.id, pConfig->capture.pDeviceID, sizeof(pDevice->capture.id)); + pDevice->capture.pID = &pDevice->capture.id; + } else { + pDevice->capture.pID = NULL; } - pDevice->noPreZeroedOutputBuffer = pConfig->noPreZeroedOutputBuffer; - pDevice->noClip = pConfig->noClip; - pDevice->masterVolumeFactor = 1; + pDevice->noPreSilencedOutputBuffer = pConfig->noPreSilencedOutputBuffer; + pDevice->noClip = pConfig->noClip; + pDevice->noDisableDenormals = pConfig->noDisableDenormals; + pDevice->noFixedSizedCallback = pConfig->noFixedSizedCallback; + pDevice->masterVolumeFactor = 1; - pDevice->type = pConfig->deviceType; - pDevice->sampleRate = pConfig->sampleRate; - pDevice->resampling.algorithm = pConfig->resampling.algorithm; - pDevice->resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; - pDevice->resampling.speex.quality = pConfig->resampling.speex.quality; + pDevice->type = pConfig->deviceType; + pDevice->sampleRate = pConfig->sampleRate; + pDevice->resampling.algorithm = pConfig->resampling.algorithm; + pDevice->resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; + pDevice->resampling.pBackendVTable = pConfig->resampling.pBackendVTable; + pDevice->resampling.pBackendUserData = pConfig->resampling.pBackendUserData; - pDevice->capture.shareMode = pConfig->capture.shareMode; - pDevice->capture.format = pConfig->capture.format; - pDevice->capture.channels = pConfig->capture.channels; - ma_channel_map_copy(pDevice->capture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels); - pDevice->capture.channelMixMode = pConfig->capture.channelMixMode; + pDevice->capture.shareMode = pConfig->capture.shareMode; + pDevice->capture.format = pConfig->capture.format; + pDevice->capture.channels = pConfig->capture.channels; + ma_channel_map_copy_or_default(pDevice->capture.channelMap, ma_countof(pDevice->capture.channelMap), pConfig->capture.pChannelMap, pConfig->capture.channels); + pDevice->capture.channelMixMode = pConfig->capture.channelMixMode; - pDevice->playback.shareMode = pConfig->playback.shareMode; - pDevice->playback.format = pConfig->playback.format; - pDevice->playback.channels = pConfig->playback.channels; - ma_channel_map_copy(pDevice->playback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels); - pDevice->playback.channelMixMode = pConfig->playback.channelMixMode; + pDevice->playback.shareMode = pConfig->playback.shareMode; + pDevice->playback.format = pConfig->playback.format; + pDevice->playback.channels = pConfig->playback.channels; + ma_channel_map_copy_or_default(pDevice->playback.channelMap, ma_countof(pDevice->playback.channelMap), pConfig->playback.pChannelMap, pConfig->playback.channels); + pDevice->playback.channelMixMode = pConfig->playback.channelMixMode; result = ma_mutex_init(&pDevice->startStopLock); if (result != MA_SUCCESS) { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create mutex.", result); + return result; } /* @@ -33703,14 +39890,14 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC result = ma_event_init(&pDevice->wakeupEvent); if (result != MA_SUCCESS) { ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread wakeup event.", result); + return result; } result = ma_event_init(&pDevice->startEvent); if (result != MA_SUCCESS) { ma_event_uninit(&pDevice->wakeupEvent); ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread start event.", result); + return result; } result = ma_event_init(&pDevice->stopEvent); @@ -33718,7 +39905,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_event_uninit(&pDevice->startEvent); ma_event_uninit(&pDevice->wakeupEvent); ma_mutex_uninit(&pDevice->startStopLock); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread stop event.", result); + return result; } @@ -33728,7 +39915,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC descriptorPlayback.format = pConfig->playback.format; descriptorPlayback.channels = pConfig->playback.channels; descriptorPlayback.sampleRate = pConfig->sampleRate; - ma_channel_map_copy(descriptorPlayback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels); + ma_channel_map_copy_or_default(descriptorPlayback.channelMap, ma_countof(descriptorPlayback.channelMap), pConfig->playback.pChannelMap, pConfig->playback.channels); descriptorPlayback.periodSizeInFrames = pConfig->periodSizeInFrames; descriptorPlayback.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; descriptorPlayback.periodCount = pConfig->periods; @@ -33744,7 +39931,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC descriptorCapture.format = pConfig->capture.format; descriptorCapture.channels = pConfig->capture.channels; descriptorCapture.sampleRate = pConfig->sampleRate; - ma_channel_map_copy(descriptorCapture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels); + ma_channel_map_copy_or_default(descriptorCapture.channelMap, ma_countof(descriptorCapture.channelMap), pConfig->capture.pChannelMap, pConfig->capture.channels); descriptorCapture.periodSizeInFrames = pConfig->periodSizeInFrames; descriptorCapture.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; descriptorCapture.periodCount = pConfig->periods; @@ -33762,7 +39949,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC return result; } - +#if 0 /* On output the descriptors will contain the *actual* data format of the device. We need this to know how to convert the data between the requested format and the internal format. @@ -33812,7 +39999,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_device_info deviceInfo; if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { - result = ma_context_get_device_info(pContext, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, descriptorCapture.pDeviceID, descriptorCapture.shareMode, &deviceInfo); + result = ma_device_get_info(pDevice, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, &deviceInfo); if (result == MA_SUCCESS) { ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); } else { @@ -33826,7 +40013,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_context_get_device_info(pContext, ma_device_type_playback, descriptorPlayback.pDeviceID, descriptorPlayback.shareMode, &deviceInfo); + result = ma_device_get_info(pDevice, ma_device_type_playback, &deviceInfo); if (result == MA_SUCCESS) { ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1); } else { @@ -33842,6 +40029,77 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_device__post_init_setup(pDevice, pConfig->deviceType); +#endif + + result = ma_device_post_init(pDevice, pConfig->deviceType, &descriptorPlayback, &descriptorCapture); + if (result != MA_SUCCESS) { + ma_device_uninit(pDevice); + return result; + } + + + + /* + If we're using fixed sized callbacks we'll need to make use of an intermediary buffer. Needs to + be done after post_init_setup() because we'll need access to the sample rate. + */ + if (pConfig->noFixedSizedCallback == MA_FALSE) { + /* We're using a fixed sized data callback so we'll need an intermediary buffer. */ + ma_uint32 intermediaryBufferCap = pConfig->periodSizeInFrames; + if (intermediaryBufferCap == 0) { + intermediaryBufferCap = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, pDevice->sampleRate); + } + + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { + ma_uint32 intermediaryBufferSizeInBytes; + + pDevice->capture.intermediaryBufferLen = 0; + pDevice->capture.intermediaryBufferCap = intermediaryBufferCap; + if (pDevice->capture.intermediaryBufferCap == 0) { + pDevice->capture.intermediaryBufferCap = pDevice->capture.internalPeriodSizeInFrames; + } + + intermediaryBufferSizeInBytes = pDevice->capture.intermediaryBufferCap * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + + pDevice->capture.pIntermediaryBuffer = ma_malloc((size_t)intermediaryBufferSizeInBytes, &pContext->allocationCallbacks); + if (pDevice->capture.pIntermediaryBuffer == NULL) { + ma_device_uninit(pDevice); + return MA_OUT_OF_MEMORY; + } + + /* Silence the buffer for safety. */ + ma_silence_pcm_frames(pDevice->capture.pIntermediaryBuffer, pDevice->capture.intermediaryBufferCap, pDevice->capture.format, pDevice->capture.channels); + pDevice->capture.intermediaryBufferLen = pDevice->capture.intermediaryBufferCap; + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + ma_uint64 intermediaryBufferSizeInBytes; + + pDevice->playback.intermediaryBufferLen = 0; + if (pConfig->deviceType == ma_device_type_duplex) { + pDevice->playback.intermediaryBufferCap = pDevice->capture.intermediaryBufferCap; /* In duplex mode, make sure the intermediary buffer is always the same size as the capture side. */ + } else { + pDevice->playback.intermediaryBufferCap = intermediaryBufferCap; + if (pDevice->playback.intermediaryBufferCap == 0) { + pDevice->playback.intermediaryBufferCap = pDevice->playback.internalPeriodSizeInFrames; + } + } + + intermediaryBufferSizeInBytes = pDevice->playback.intermediaryBufferCap * ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + + pDevice->playback.pIntermediaryBuffer = ma_malloc((size_t)intermediaryBufferSizeInBytes, &pContext->allocationCallbacks); + if (pDevice->playback.pIntermediaryBuffer == NULL) { + ma_device_uninit(pDevice); + return MA_OUT_OF_MEMORY; + } + + /* Silence the buffer for safety. */ + ma_silence_pcm_frames(pDevice->playback.pIntermediaryBuffer, pDevice->playback.intermediaryBufferCap, pDevice->playback.format, pDevice->playback.channels); + pDevice->playback.intermediaryBufferLen = 0; + } + } else { + /* Not using a fixed sized data callback so no need for an intermediary buffer. */ + } /* Some backends don't require the worker thread. */ @@ -33850,12 +40108,12 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC result = ma_thread_create(&pDevice->thread, pContext->threadPriority, pContext->threadStackSize, ma_worker_thread, pDevice, &pContext->allocationCallbacks); if (result != MA_SUCCESS) { ma_device_uninit(pDevice); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread.", result); + return result; } /* Wait for the worker thread to put the device into it's stopped state for real. */ ma_event_wait(&pDevice->stopEvent); - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); } else { /* If the backend is asynchronous and the device is duplex, we'll need an intermediary ring buffer. Note that this needs to be done @@ -33871,39 +40129,47 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } + /* Log device information. */ + { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[%s]\n", ma_get_backend_name(pDevice->pContext->backend)); + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(pDevice, ma_device_type_capture, name, sizeof(name), NULL); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, "[%s]\n", ma_get_backend_name(pDevice->pContext->backend)); - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", pDevice->capture.name, "Capture"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->capture.internalChannels, pDevice->capture.channels); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->capture.internalSampleRate, pDevice->sampleRate); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->capture.internalPeriodSizeInFrames, pDevice->capture.internalPeriods, (pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->capture.converter.hasPreFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->capture.converter.hasPostFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->capture.converter.hasChannelConverter ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->capture.converter.hasResampler ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->capture.converter.isPassthrough ? "YES" : "NO"); - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", pDevice->playback.name, "Playback"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->playback.format), ma_get_format_name(pDevice->playback.internalFormat)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->playback.channels, pDevice->playback.internalChannels); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->playback.internalSampleRate); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->playback.internalPeriodSizeInFrames, pDevice->playback.internalPeriods, (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods)); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->playback.converter.hasPreFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->playback.converter.hasPostFormatConversion ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->playback.converter.hasChannelConverter ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->playback.converter.hasResampler ? "YES" : "NO"); - ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", name, "Capture"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->capture.internalChannels, pDevice->capture.channels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->capture.internalSampleRate, pDevice->sampleRate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->capture.internalPeriodSizeInFrames, pDevice->capture.internalPeriods, (pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->capture.converter.hasPreFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->capture.converter.hasPostFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->capture.converter.hasChannelConverter ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->capture.converter.hasResampler ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->capture.converter.isPassthrough ? "YES" : "NO"); + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + char name[MA_MAX_DEVICE_NAME_LENGTH + 1]; + ma_device_get_name(pDevice, ma_device_type_playback, name, sizeof(name), NULL); + + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " %s (%s)\n", name, "Playback"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Format: %s -> %s\n", ma_get_format_name(pDevice->playback.format), ma_get_format_name(pDevice->playback.internalFormat)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channels: %d -> %d\n", pDevice->playback.channels, pDevice->playback.internalChannels); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d\n", pDevice->sampleRate, pDevice->playback.internalSampleRate); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)\n", pDevice->playback.internalPeriodSizeInFrames, pDevice->playback.internalPeriods, (pDevice->playback.internalPeriodSizeInFrames * pDevice->playback.internalPeriods)); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Conversion:\n"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s\n", pDevice->playback.converter.hasPreFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Post Format Conversion: %s\n", pDevice->playback.converter.hasPostFormatConversion ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Channel Routing: %s\n", pDevice->playback.converter.hasChannelConverter ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Resampling: %s\n", pDevice->playback.converter.hasResampler ? "YES" : "NO"); + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_INFO, " Passthrough: %s\n", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); + } } - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); return MA_SUCCESS; } @@ -33931,7 +40197,7 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen } - pContext = (ma_context*)ma__malloc_from_callbacks(sizeof(*pContext), &allocationCallbacks); + pContext = (ma_context*)ma_malloc(sizeof(*pContext), &allocationCallbacks); if (pContext == NULL) { return MA_OUT_OF_MEMORY; } @@ -33962,7 +40228,7 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen } if (result != MA_SUCCESS) { - ma__free_from_callbacks(pContext, &allocationCallbacks); + ma_free(pContext, &allocationCallbacks); return result; } @@ -33982,7 +40248,7 @@ MA_API void ma_device_uninit(ma_device* pDevice) } /* Putting the device into an uninitialized state will make the worker thread return. */ - ma_device__set_state(pDevice, MA_STATE_UNINITIALIZED); + ma_device__set_state(pDevice, ma_device_state_uninitialized); /* Wake up the worker thread and wait for it to properly terminate. */ if (!ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -34006,11 +40272,29 @@ MA_API void ma_device_uninit(ma_device* pDevice) } } + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + ma_data_converter_uninit(&pDevice->capture.converter, &pDevice->pContext->allocationCallbacks); + } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ma_data_converter_uninit(&pDevice->playback.converter, &pDevice->pContext->allocationCallbacks); + } + + if (pDevice->playback.pInputCache != NULL) { + ma_free(pDevice->playback.pInputCache, &pDevice->pContext->allocationCallbacks); + } + + if (pDevice->capture.pIntermediaryBuffer != NULL) { + ma_free(pDevice->capture.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); + } + if (pDevice->playback.pIntermediaryBuffer != NULL) { + ma_free(pDevice->playback.pIntermediaryBuffer, &pDevice->pContext->allocationCallbacks); + } + if (pDevice->isOwnerOfContext) { ma_allocation_callbacks allocationCallbacks = pDevice->pContext->allocationCallbacks; ma_context_uninit(pDevice->pContext); - ma__free_from_callbacks(pDevice->pContext, &allocationCallbacks); + ma_free(pDevice->pContext, &allocationCallbacks); } MA_ZERO_OBJECT(pDevice); @@ -34030,28 +40314,92 @@ MA_API ma_log* ma_device_get_log(ma_device* pDevice) return ma_context_get_log(ma_device_get_context(pDevice)); } +MA_API ma_result ma_device_get_info(ma_device* pDevice, ma_device_type type, ma_device_info* pDeviceInfo) +{ + if (pDeviceInfo == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDeviceInfo); + + if (pDevice == NULL) { + return MA_INVALID_ARGS; + } + + /* If the onDeviceGetInfo() callback is set, use that. Otherwise we'll fall back to ma_context_get_device_info(). */ + if (pDevice->pContext->callbacks.onDeviceGetInfo != NULL) { + return pDevice->pContext->callbacks.onDeviceGetInfo(pDevice, type, pDeviceInfo); + } + + /* Getting here means onDeviceGetInfo is not implemented so we need to fall back to an alternative. */ + if (type == ma_device_type_playback) { + return ma_context_get_device_info(pDevice->pContext, type, pDevice->playback.pID, pDeviceInfo); + } else { + return ma_context_get_device_info(pDevice->pContext, type, pDevice->capture.pID, pDeviceInfo); + } +} + +MA_API ma_result ma_device_get_name(ma_device* pDevice, ma_device_type type, char* pName, size_t nameCap, size_t* pLengthNotIncludingNullTerminator) +{ + ma_result result; + ma_device_info deviceInfo; + + if (pLengthNotIncludingNullTerminator != NULL) { + *pLengthNotIncludingNullTerminator = 0; + } + + if (pName != NULL && nameCap > 0) { + pName[0] = '\0'; + } + + result = ma_device_get_info(pDevice, type, &deviceInfo); + if (result != MA_SUCCESS) { + return result; + } + + if (pName != NULL) { + ma_strncpy_s(pName, nameCap, deviceInfo.name, (size_t)-1); + + /* + For safety, make sure the length is based on the truncated output string rather than the + source. Otherwise the caller might assume the output buffer contains more content than it + actually does. + */ + if (pLengthNotIncludingNullTerminator != NULL) { + *pLengthNotIncludingNullTerminator = strlen(pName); + } + } else { + /* Name not specified. Just report the length of the source string. */ + if (pLengthNotIncludingNullTerminator != NULL) { + *pLengthNotIncludingNullTerminator = strlen(deviceInfo.name); + } + } + + return MA_SUCCESS; +} + MA_API ma_result ma_device_start(ma_device* pDevice) { ma_result result; if (pDevice == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_start() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_start() called for an uninitialized device.", MA_DEVICE_NOT_INITIALIZED); + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + return MA_INVALID_OPERATION; /* Not initialized. */ } - if (ma_device_get_state(pDevice) == MA_STATE_STARTED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_WARNING, "ma_device_start() called when the device is already started.", MA_INVALID_OPERATION); /* Already started. Returning an error to let the application know because it probably means they're doing something wrong. */ + if (ma_device_get_state(pDevice) == ma_device_state_started) { + return MA_SUCCESS; /* Already started. */ } ma_mutex_lock(&pDevice->startStopLock); { /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a stopped or paused state. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_stopped); - ma_device__set_state(pDevice, MA_STATE_STARTING); + ma_device__set_state(pDevice, ma_device_state_starting); /* Asynchronous backends need to be handled differently. */ if (ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -34062,7 +40410,8 @@ MA_API ma_result ma_device_start(ma_device* pDevice) } if (result == MA_SUCCESS) { - ma_device__set_state(pDevice, MA_STATE_STARTED); + ma_device__set_state(pDevice, ma_device_state_started); + ma_device__on_notification_started(pDevice); } } else { /* @@ -34081,7 +40430,7 @@ MA_API ma_result ma_device_start(ma_device* pDevice) /* We changed the state from stopped to started, so if we failed, make sure we put the state back to stopped. */ if (result != MA_SUCCESS) { - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } } ma_mutex_unlock(&pDevice->startStopLock); @@ -34094,23 +40443,23 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) ma_result result; if (pDevice == NULL) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_stop() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); + return MA_INVALID_ARGS; } - if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_stop() called for an uninitialized device.", MA_DEVICE_NOT_INITIALIZED); + if (ma_device_get_state(pDevice) == ma_device_state_uninitialized) { + return MA_INVALID_OPERATION; /* Not initialized. */ } - if (ma_device_get_state(pDevice) == MA_STATE_STOPPED) { - return ma_post_error(pDevice, MA_LOG_LEVEL_WARNING, "ma_device_stop() called when the device is already stopped.", MA_INVALID_OPERATION); /* Already stopped. Returning an error to let the application know because it probably means they're doing something wrong. */ + if (ma_device_get_state(pDevice) == ma_device_state_stopped) { + return MA_SUCCESS; /* Already stopped. */ } ma_mutex_lock(&pDevice->startStopLock); { /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a started or paused state. */ - MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STARTED); + MA_ASSERT(ma_device_get_state(pDevice) == ma_device_state_started); - ma_device__set_state(pDevice, MA_STATE_STOPPING); + ma_device__set_state(pDevice, ma_device_state_stopping); /* Asynchronous backends need to be handled differently. */ if (ma_context_is_backend_asynchronous(pDevice->pContext)) { @@ -34121,7 +40470,7 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) result = MA_INVALID_OPERATION; } - ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_device__set_state(pDevice, ma_device_state_stopped); } else { /* Synchronous backends. The stop callback is always called from the worker thread. Do not call the stop callback here. If @@ -34129,7 +40478,7 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) sure the state of the device is *not* playing right now, which it shouldn't be since we set it above. This is super important though, so I'm asserting it here as well for extra safety in case we accidentally change something later. */ - MA_ASSERT(ma_device_get_state(pDevice) != MA_STATE_STARTED); + MA_ASSERT(ma_device_get_state(pDevice) != ma_device_state_started); if (pDevice->pContext->callbacks.onDeviceDataLoopWakeup != NULL) { pDevice->pContext->callbacks.onDeviceDataLoopWakeup(pDevice); @@ -34150,16 +40499,16 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) MA_API ma_bool32 ma_device_is_started(const ma_device* pDevice) { - return ma_device_get_state(pDevice) == MA_STATE_STARTED; + return ma_device_get_state(pDevice) == ma_device_state_started; } -MA_API ma_uint32 ma_device_get_state(const ma_device* pDevice) +MA_API ma_device_state ma_device_get_state(const ma_device* pDevice) { if (pDevice == NULL) { - return MA_STATE_UNINITIALIZED; + return ma_device_state_uninitialized; } - return c89atomic_load_32((ma_uint32*)&pDevice->state); /* Naughty cast to get rid of a const warning. */ + return (ma_device_state)c89atomic_load_i32((ma_int32*)&pDevice->state); /* Naughty cast to get rid of a const warning. */ } MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) @@ -34168,7 +40517,7 @@ MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) return MA_INVALID_ARGS; } - if (volume < 0.0f || volume > 1.0f) { + if (volume < 0.0f) { return MA_INVALID_ARGS; } @@ -34193,16 +40542,16 @@ MA_API ma_result ma_device_get_master_volume(ma_device* pDevice, float* pVolume) return MA_SUCCESS; } -MA_API ma_result ma_device_set_master_gain_db(ma_device* pDevice, float gainDB) +MA_API ma_result ma_device_set_master_volume_db(ma_device* pDevice, float gainDB) { if (gainDB > 0) { return MA_INVALID_ARGS; } - return ma_device_set_master_volume(pDevice, ma_gain_db_to_factor(gainDB)); + return ma_device_set_master_volume(pDevice, ma_volume_db_to_linear(gainDB)); } -MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB) +MA_API ma_result ma_device_get_master_volume_db(ma_device* pDevice, float* pGainDB) { float factor; ma_result result; @@ -34217,7 +40566,7 @@ MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB return result; } - *pGainDB = ma_factor_to_gain_db(factor); + *pGainDB = ma_volume_linear_to_db(factor); return MA_SUCCESS; } @@ -34300,11 +40649,6 @@ MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_dev #endif /* MA_NO_DEVICE_IO */ -MA_API ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale) -{ - return ma_max(1, (ma_uint32)(baseBufferSize*scale)); -} - MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate) { /* Prevent a division by zero. */ @@ -34358,13 +40702,89 @@ MA_API const void* ma_offset_pcm_frames_const_ptr(const void* p, ma_uint64 offse } -MA_API void ma_clip_samples_f32(float* p, ma_uint64 sampleCount) +MA_API void ma_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count) { - ma_uint32 iSample; + ma_uint64 iSample; - /* TODO: Research a branchless SSE implementation. */ - for (iSample = 0; iSample < sampleCount; iSample += 1) { - p[iSample] = ma_clip_f32(p[iSample]); + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_u8(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s16(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + ma_int64 s = ma_clip_s24(pSrc[iSample]); + pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >> 0); + pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >> 8); + pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16); + } +} + +MA_API void ma_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s32(pSrc[iSample]); + } +} + +MA_API void ma_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_f32(pSrc[iSample]); + } +} + +MA_API void ma_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels) +{ + ma_uint64 sampleCount; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + sampleCount = frameCount * channels; + + switch (format) { + case ma_format_u8: ma_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount); break; + case ma_format_s16: ma_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount); break; + case ma_format_s24: ma_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount); break; + case ma_format_s32: ma_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount); break; + case ma_format_f32: ma_clip_samples_f32(( float*)pDst, (const float*)pSrc, sampleCount); break; + + /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */ + case ma_format_unknown: + case ma_format_count: + break; } } @@ -34441,8 +40861,19 @@ MA_API void ma_copy_and_apply_volume_factor_f32(float* pSamplesOut, const float* return; } - for (iSample = 0; iSample < sampleCount; iSample += 1) { - pSamplesOut[iSample] = pSamplesIn[iSample] * factor; + if (factor == 1) { + if (pSamplesOut == pSamplesIn) { + /* In place. No-op. */ + } else { + /* Just a copy. */ + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamplesOut[iSample] = pSamplesIn[iSample]; + } + } + } else { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pSamplesOut[iSample] = pSamplesIn[iSample] * factor; + } } } @@ -34471,85 +40902,236 @@ MA_API void ma_apply_volume_factor_f32(float* pSamples, ma_uint64 sampleCount, f ma_copy_and_apply_volume_factor_f32(pSamples, pSamples, sampleCount, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFramesOut, const ma_uint8* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_u8(ma_uint8* pFramesOut, const ma_uint8* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_u8(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_u8(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFramesOut, const ma_int16* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s16(ma_int16* pFramesOut, const ma_int16* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s16(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s16(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s24(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s24(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s24(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFramesOut, const ma_int32* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_s32(ma_int32* pFramesOut, const ma_int32* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_s32(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_s32(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pPCMFramesOut, const float* pPCMFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_f32(pPCMFramesOut, pPCMFramesIn, frameCount*channels, factor); + ma_copy_and_apply_volume_factor_f32(pFramesOut, pFramesIn, frameCount*channels, factor); } -MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pPCMFramesOut, const void* pPCMFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) +MA_API void ma_copy_and_apply_volume_factor_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) { switch (format) { - case ma_format_u8: ma_copy_and_apply_volume_factor_pcm_frames_u8 ((ma_uint8*)pPCMFramesOut, (const ma_uint8*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s16: ma_copy_and_apply_volume_factor_pcm_frames_s16((ma_int16*)pPCMFramesOut, (const ma_int16*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s24: ma_copy_and_apply_volume_factor_pcm_frames_s24( pPCMFramesOut, pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_s32: ma_copy_and_apply_volume_factor_pcm_frames_s32((ma_int32*)pPCMFramesOut, (const ma_int32*)pPCMFramesIn, frameCount, channels, factor); return; - case ma_format_f32: ma_copy_and_apply_volume_factor_pcm_frames_f32( (float*)pPCMFramesOut, (const float*)pPCMFramesIn, frameCount, channels, factor); return; + case ma_format_u8: ma_copy_and_apply_volume_factor_pcm_frames_u8 ((ma_uint8*)pFramesOut, (const ma_uint8*)pFramesIn, frameCount, channels, factor); return; + case ma_format_s16: ma_copy_and_apply_volume_factor_pcm_frames_s16((ma_int16*)pFramesOut, (const ma_int16*)pFramesIn, frameCount, channels, factor); return; + case ma_format_s24: ma_copy_and_apply_volume_factor_pcm_frames_s24( pFramesOut, pFramesIn, frameCount, channels, factor); return; + case ma_format_s32: ma_copy_and_apply_volume_factor_pcm_frames_s32((ma_int32*)pFramesOut, (const ma_int32*)pFramesIn, frameCount, channels, factor); return; + case ma_format_f32: ma_copy_and_apply_volume_factor_pcm_frames_f32( (float*)pFramesOut, (const float*)pFramesIn, frameCount, channels, factor); return; default: return; /* Do nothing. */ } } -MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_u8(ma_uint8* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_u8(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_u8(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s16(ma_int16* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s16(ma_int16* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s16(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s16(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s24(void* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s24(void* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s24(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s24(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_s32(ma_int32* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_s32(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_s32(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pPCMFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames_f32(float* pFrames, ma_uint64 frameCount, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames_f32(pPCMFrames, pPCMFrames, frameCount, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames_f32(pFrames, pFrames, frameCount, channels, factor); } -MA_API void ma_apply_volume_factor_pcm_frames(void* pPCMFrames, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) +MA_API void ma_apply_volume_factor_pcm_frames(void* pFramesOut, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float factor) { - ma_copy_and_apply_volume_factor_pcm_frames(pPCMFrames, pPCMFrames, frameCount, format, channels, factor); + ma_copy_and_apply_volume_factor_pcm_frames(pFramesOut, pFramesOut, frameCount, format, channels, factor); } -MA_API float ma_factor_to_gain_db(float factor) +MA_API void ma_copy_and_apply_volume_factor_per_channel_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, ma_uint32 channels, float* pChannelGains) { - return (float)(20*ma_log10f(factor)); + ma_uint64 iFrame; + + if (channels == 2) { + /* TODO: Do an optimized implementation for stereo and mono. Can do a SIMD optimized implementation as well. */ + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + ma_uint32 iChannel; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOut[iFrame * channels + iChannel] = pFramesIn[iFrame * channels + iChannel] * pChannelGains[iChannel]; + } + } } -MA_API float ma_gain_db_to_factor(float gain) + + +static MA_INLINE ma_int16 ma_apply_volume_unclipped_u8(ma_int16 x, ma_int16 volume) { - return (float)ma_powf(10, gain/20.0f); + return (ma_int16)(((ma_int32)x * (ma_int32)volume) >> 8); } +static MA_INLINE ma_int32 ma_apply_volume_unclipped_s16(ma_int32 x, ma_int16 volume) +{ + return (ma_int32)((x * volume) >> 8); +} + +static MA_INLINE ma_int64 ma_apply_volume_unclipped_s24(ma_int64 x, ma_int16 volume) +{ + return (ma_int64)((x * volume) >> 8); +} + +static MA_INLINE ma_int64 ma_apply_volume_unclipped_s32(ma_int64 x, ma_int16 volume) +{ + return (ma_int64)((x * volume) >> 8); +} + +static MA_INLINE float ma_apply_volume_unclipped_f32(float x, float volume) +{ + return x * volume; +} + + +MA_API void ma_copy_and_apply_volume_and_clip_samples_u8(ma_uint8* pDst, const ma_int16* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_u8(ma_apply_volume_unclipped_u8(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s16(ma_int16* pDst, const ma_int32* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s16(ma_apply_volume_unclipped_s16(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s24(ma_uint8* pDst, const ma_int64* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + ma_int64 s = ma_clip_s24(ma_apply_volume_unclipped_s24(pSrc[iSample], volumeFixed)); + pDst[iSample*3 + 0] = (ma_uint8)((s & 0x000000FF) >> 0); + pDst[iSample*3 + 1] = (ma_uint8)((s & 0x0000FF00) >> 8); + pDst[iSample*3 + 2] = (ma_uint8)((s & 0x00FF0000) >> 16); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_s32(ma_int32* pDst, const ma_int64* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + ma_int16 volumeFixed; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + volumeFixed = ma_float_to_fixed_16(volume); + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_s32(ma_apply_volume_unclipped_s32(pSrc[iSample], volumeFixed)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_samples_f32(float* pDst, const float* pSrc, ma_uint64 count, float volume) +{ + ma_uint64 iSample; + + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + /* For the f32 case we need to make sure this supports in-place processing where the input and output buffers are the same. */ + + for (iSample = 0; iSample < count; iSample += 1) { + pDst[iSample] = ma_clip_f32(ma_apply_volume_unclipped_f32(pSrc[iSample], volume)); + } +} + +MA_API void ma_copy_and_apply_volume_and_clip_pcm_frames(void* pDst, const void* pSrc, ma_uint64 frameCount, ma_format format, ma_uint32 channels, float volume) +{ + MA_ASSERT(pDst != NULL); + MA_ASSERT(pSrc != NULL); + + if (volume == 1) { + ma_clip_pcm_frames(pDst, pSrc, frameCount, format, channels); /* Optimized case for volume = 1. */ + } else if (volume == 0) { + ma_silence_pcm_frames(pDst, frameCount, format, channels); /* Optimized case for volume = 0. */ + } else { + ma_uint64 sampleCount = frameCount * channels; + + switch (format) { + case ma_format_u8: ma_copy_and_apply_volume_and_clip_samples_u8( (ma_uint8*)pDst, (const ma_int16*)pSrc, sampleCount, volume); break; + case ma_format_s16: ma_copy_and_apply_volume_and_clip_samples_s16((ma_int16*)pDst, (const ma_int32*)pSrc, sampleCount, volume); break; + case ma_format_s24: ma_copy_and_apply_volume_and_clip_samples_s24((ma_uint8*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break; + case ma_format_s32: ma_copy_and_apply_volume_and_clip_samples_s32((ma_int32*)pDst, (const ma_int64*)pSrc, sampleCount, volume); break; + case ma_format_f32: ma_copy_and_apply_volume_and_clip_samples_f32(( float*)pDst, (const float*)pSrc, sampleCount, volume); break; + + /* Do nothing if we don't know the format. We're including these here to silence a compiler warning about enums not being handled by the switch. */ + case ma_format_unknown: + case ma_format_count: + break; + } + } +} + + + +MA_API float ma_volume_linear_to_db(float factor) +{ + return 20*ma_log10f(factor); +} + +MA_API float ma_volume_db_to_linear(float gain) +{ + return ma_powf(10, gain/20.0f); +} + + /************************************************************************************************************************************************************** @@ -34580,33 +41162,6 @@ static MA_INLINE void ma_pcm_sample_s32_to_s24_no_scale(ma_int64 x, ma_uint8* s2 } -static MA_INLINE ma_uint8 ma_clip_u8(ma_int16 x) -{ - return (ma_uint8)(ma_clamp(x, -128, 127) + 128); -} - -static MA_INLINE ma_int16 ma_clip_s16(ma_int32 x) -{ - return (ma_int16)ma_clamp(x, -32768, 32767); -} - -static MA_INLINE ma_int64 ma_clip_s24(ma_int64 x) -{ - return (ma_int64)ma_clamp(x, -8388608, 8388607); -} - -static MA_INLINE ma_int32 ma_clip_s32(ma_int64 x) -{ - /* This dance is to silence warnings with -std=c89. A good compiler should be able to optimize this away. */ - ma_int64 clipMin; - ma_int64 clipMax; - clipMin = -((ma_int64)2147483647 + 1); - clipMax = (ma_int64)2147483647; - - return (ma_int32)ma_clamp(x, clipMin, clipMax); -} - - /* u8 */ MA_API void ma_pcm_u8_to_u8(void* dst, const void* src, ma_uint64 count, ma_dither_mode ditherMode) { @@ -36985,25 +43540,131 @@ MA_API ma_biquad_config ma_biquad_config_init(ma_format format, ma_uint32 channe return config; } -MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ) + +typedef struct { + size_t sizeInBytes; + size_t r1Offset; + size_t r2Offset; +} ma_biquad_heap_layout; + +static ma_result ma_biquad_get_heap_layout(const ma_biquad_config* pConfig, ma_biquad_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* R0 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* R1 */ + pHeapLayout->r2Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_biquad_get_heap_size(const ma_biquad_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_biquad_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_biquad_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_biquad_init_preallocated(const ma_biquad_config* pConfig, void* pHeap, ma_biquad* pBQ) +{ + ma_result result; + ma_biquad_heap_layout heapLayout; + if (pBQ == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pBQ); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_biquad_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pBQ->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pBQ->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); + pBQ->pR2 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r2Offset); return ma_biquad_reinit(pConfig, pBQ); } +MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad* pBQ) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_biquad_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_biquad_init_preallocated(pConfig, pHeap, pBQ); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBQ->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_biquad_uninit(ma_biquad* pBQ, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pBQ == NULL) { + return; + } + + if (pBQ->_ownsHeap) { + ma_free(pBQ->_pHeap, pAllocationCallbacks); + } +} + MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ) { if (pBQ == NULL || pConfig == NULL) { @@ -37051,6 +43712,23 @@ MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pB return MA_SUCCESS; } +MA_API ma_result ma_biquad_clear_cache(ma_biquad* pBQ) +{ + if (pBQ == NULL) { + return MA_INVALID_ARGS; + } + + if (pBQ->format == ma_format_f32) { + pBQ->pR1->f32 = 0; + pBQ->pR2->f32 = 0; + } else { + pBQ->pR1->s32 = 0; + pBQ->pR2->s32 = 0; + } + + return MA_SUCCESS; +} + static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed(ma_biquad* pBQ, float* pY, const float* pX) { ma_uint32 c; @@ -37061,10 +43739,10 @@ static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed( const float a1 = pBQ->a1.f32; const float a2 = pBQ->a2.f32; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pBQ->r1[c].f32; - float r2 = pBQ->r2[c].f32; + float r1 = pBQ->pR1[c].f32; + float r2 = pBQ->pR2[c].f32; float x = pX[c]; float y; @@ -37072,9 +43750,9 @@ static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed( r1 = b1*x - a1*y + r2; r2 = b2*x - a2*y; - pY[c] = y; - pBQ->r1[c].f32 = r1; - pBQ->r2[c].f32 = r2; + pY[c] = y; + pBQ->pR1[c].f32 = r1; + pBQ->pR2[c].f32 = r2; } } @@ -37093,10 +43771,10 @@ static MA_INLINE void ma_biquad_process_pcm_frame_s16__direct_form_2_transposed( const ma_int32 a1 = pBQ->a1.s32; const ma_int32 a2 = pBQ->a2.s32; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pBQ->r1[c].s32; - ma_int32 r2 = pBQ->r2[c].s32; + ma_int32 r1 = pBQ->pR1[c].s32; + ma_int32 r2 = pBQ->pR2[c].s32; ma_int32 x = pX[c]; ma_int32 y; @@ -37104,9 +43782,9 @@ static MA_INLINE void ma_biquad_process_pcm_frame_s16__direct_form_2_transposed( r1 = (b1*x - a1*y + r2); r2 = (b2*x - a2*y); - pY[c] = (ma_int16)ma_clamp(y, -32768, 32767); - pBQ->r1[c].s32 = r1; - pBQ->r2[c].s32 = r2; + pY[c] = (ma_int16)ma_clamp(y, -32768, 32767); + pBQ->pR1[c].s32 = r1; + pBQ->pR2[c].s32 = r2; } } @@ -37200,25 +43878,122 @@ MA_API ma_lpf2_config ma_lpf2_config_init(ma_format format, ma_uint32 channels, } -MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) +typedef struct { + size_t sizeInBytes; + size_t r1Offset; +} ma_lpf1_heap_layout; + +static ma_result ma_lpf1_get_heap_layout(const ma_lpf1_config* pConfig, ma_lpf1_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* R1 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_lpf1_get_heap_size(const ma_lpf1_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_lpf1_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_lpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_lpf1_init_preallocated(const ma_lpf1_config* pConfig, void* pHeap, ma_lpf1* pLPF) +{ + ma_result result; + ma_lpf1_heap_layout heapLayout; + if (pLPF == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pLPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_lpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); return ma_lpf1_reinit(pConfig, pLPF); } +MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf1* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf1_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf1_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_lpf1_uninit(ma_lpf1* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pLPF == NULL) { + return; + } + + if (pLPF->_ownsHeap) { + ma_free(pLPF->_pHeap, pAllocationCallbacks); + } +} + MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) { double a; @@ -37255,6 +44030,21 @@ MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) return MA_SUCCESS; } +MA_API ma_result ma_lpf1_clear_cache(ma_lpf1* pLPF) +{ + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + if (pLPF->format == ma_format_f32) { + pLPF->a.f32 = 0; + } else { + pLPF->a.s32 = 0; + } + + return MA_SUCCESS; +} + static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, const float* pX) { ma_uint32 c; @@ -37262,16 +44052,16 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, co const float a = pLPF->a.f32; const float b = 1 - a; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pLPF->r1[c].f32; + float r1 = pLPF->pR1[c].f32; float x = pX[c]; float y; y = b*x + a*r1; pY[c] = y; - pLPF->r1[c].f32 = y; + pLPF->pR1[c].f32 = y; } } @@ -37282,16 +44072,16 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_s16(ma_lpf1* pLPF, ma_int16* pY, const ma_int32 a = pLPF->a.s32; const ma_int32 b = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - a); - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pLPF->r1[c].s32; + ma_int32 r1 = pLPF->pR1[c].s32; ma_int32 x = pX[c]; ma_int32 y; y = (b*x + a*r1) >> MA_BIQUAD_FIXED_POINT_SHIFT; - pY[c] = (ma_int16)y; - pLPF->r1[c].s32 = (ma_int32)y; + pY[c] = (ma_int16)y; + pLPF->pR1[c].s32 = (ma_int32)y; } } @@ -37371,7 +44161,15 @@ static MA_INLINE ma_biquad_config ma_lpf2__get_biquad_config(const ma_lpf2_confi return bqConfig; } -MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) +MA_API ma_result ma_lpf2_get_heap_size(const ma_lpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_lpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_lpf2_init_preallocated(const ma_lpf2_config* pConfig, void* pHeap, ma_lpf2* pLPF) { ma_result result; ma_biquad_config bqConfig; @@ -37387,7 +44185,7 @@ MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) } bqConfig = ma_lpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pLPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pLPF->bq); if (result != MA_SUCCESS) { return result; } @@ -37395,6 +44193,45 @@ MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) return MA_SUCCESS; } +MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf2* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf2_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_lpf2_uninit(ma_lpf2* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pLPF == NULL) { + return; + } + + ma_biquad_uninit(&pLPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) { ma_result result; @@ -37413,6 +44250,17 @@ MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF) return MA_SUCCESS; } +MA_API ma_result ma_lpf2_clear_cache(ma_lpf2* pLPF) +{ + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + ma_biquad_clear_cache(&pLPF->bq); + + return MA_SUCCESS; +} + static MA_INLINE void ma_lpf2_process_pcm_frame_s16(ma_lpf2* pLPF, ma_int16* pFrameOut, const ma_int16* pFrameIn) { ma_biquad_process_pcm_frame_s16(&pLPF->bq, pFrameOut, pFrameIn); @@ -37456,7 +44304,24 @@ MA_API ma_lpf_config ma_lpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* pLPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t lpf1Offset; + size_t lpf2Offset; /* Offset of the first second order filter. Subsequent filters will come straight after, and will each have the same heap size. */ +} ma_lpf_heap_layout; + +static void ma_lpf_calculate_sub_lpf_counts(ma_uint32 order, ma_uint32* pLPF1Count, ma_uint32* pLPF2Count) +{ + MA_ASSERT(pLPF1Count != NULL); + MA_ASSERT(pLPF2Count != NULL); + + *pLPF1Count = order % 2; + *pLPF2Count = order / 2; +} + +static ma_result ma_lpf_get_heap_layout(const ma_lpf_config* pConfig, ma_lpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 lpf1Count; @@ -37464,6 +44329,69 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p ma_uint32 ilpf1; ma_uint32 ilpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + ma_lpf_calculate_sub_lpf_counts(pConfig->order, &lpf1Count, &lpf2Count); + + pHeapLayout->sizeInBytes = 0; + + /* LPF 1 */ + pHeapLayout->lpf1Offset = pHeapLayout->sizeInBytes; + for (ilpf1 = 0; ilpf1 < lpf1Count; ilpf1 += 1) { + size_t lpf1HeapSizeInBytes; + ma_lpf1_config lpf1Config = ma_lpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); + + result = ma_lpf1_get_heap_size(&lpf1Config, &lpf1HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_lpf1) + lpf1HeapSizeInBytes; + } + + /* LPF 2*/ + pHeapLayout->lpf2Offset = pHeapLayout->sizeInBytes; + for (ilpf2 = 0; ilpf2 < lpf2Count; ilpf2 += 1) { + size_t lpf2HeapSizeInBytes; + ma_lpf2_config lpf2Config = ma_lpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_lpf2_get_heap_size(&lpf2Config, &lpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_lpf2) + lpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 lpf1Count; + ma_uint32 lpf2Count; + ma_uint32 ilpf1; + ma_uint32 ilpf2; + ma_lpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pLPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -37487,11 +44415,7 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p return MA_INVALID_ARGS; } - lpf1Count = pConfig->order % 2; - lpf2Count = pConfig->order / 2; - - MA_ASSERT(lpf1Count <= ma_countof(pLPF->lpf1)); - MA_ASSERT(lpf2Count <= ma_countof(pLPF->lpf2)); + ma_lpf_calculate_sub_lpf_counts(pConfig->order, &lpf1Count, &lpf2Count); /* The filter order can't change between reinits. */ if (!isNew) { @@ -37500,16 +44424,42 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p } } + if (isNew) { + result = ma_lpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pLPF1 = (ma_lpf1*)ma_offset_ptr(pHeap, heapLayout.lpf1Offset); + pLPF->pLPF2 = (ma_lpf2*)ma_offset_ptr(pHeap, heapLayout.lpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); /* To silence a compiler warning. */ + } + for (ilpf1 = 0; ilpf1 < lpf1Count; ilpf1 += 1) { ma_lpf1_config lpf1Config = ma_lpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); if (isNew) { - result = ma_lpf1_init(&lpf1Config, &pLPF->lpf1[ilpf1]); + size_t lpf1HeapSizeInBytes; + + result = ma_lpf1_get_heap_size(&lpf1Config, &lpf1HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_lpf1_init_preallocated(&lpf1Config, ma_offset_ptr(pHeap, heapLayout.lpf1Offset + (sizeof(ma_lpf1) * lpf1Count) + (ilpf1 * lpf1HeapSizeInBytes)), &pLPF->pLPF1[ilpf1]); + } } else { - result = ma_lpf1_reinit(&lpf1Config, &pLPF->lpf1[ilpf1]); + result = ma_lpf1_reinit(&lpf1Config, &pLPF->pLPF1[ilpf1]); } if (result != MA_SUCCESS) { + ma_uint32 jlpf1; + + for (jlpf1 = 0; jlpf1 < ilpf1; jlpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[jlpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -37530,12 +44480,28 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p lpf2Config = ma_lpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_lpf2_init(&lpf2Config, &pLPF->lpf2[ilpf2]); + size_t lpf2HeapSizeInBytes; + + result = ma_lpf2_get_heap_size(&lpf2Config, &lpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_lpf2_init_preallocated(&lpf2Config, ma_offset_ptr(pHeap, heapLayout.lpf2Offset + (sizeof(ma_lpf2) * lpf2Count) + (ilpf2 * lpf2HeapSizeInBytes)), &pLPF->pLPF2[ilpf2]); + } } else { - result = ma_lpf2_reinit(&lpf2Config, &pLPF->lpf2[ilpf2]); + result = ma_lpf2_reinit(&lpf2Config, &pLPF->pLPF2[ilpf2]); } if (result != MA_SUCCESS) { + ma_uint32 jlpf1; + ma_uint32 jlpf2; + + for (jlpf1 = 0; jlpf1 < lpf1Count; jlpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[jlpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + + for (jlpf2 = 0; jlpf2 < ilpf2; jlpf2 += 1) { + ma_lpf2_uninit(&pLPF->pLPF2[jlpf2], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -37549,7 +44515,28 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p return MA_SUCCESS; } -MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF) +MA_API ma_result ma_lpf_get_heap_size(const ma_lpf_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_lpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_lpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_lpf_init_preallocated(const ma_lpf_config* pConfig, void* pHeap, ma_lpf* pLPF) { if (pLPF == NULL) { return MA_INVALID_ARGS; @@ -37557,16 +44544,84 @@ MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF) MA_ZERO_OBJECT(pLPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + return ma_lpf_reinit__internal(pConfig, pHeap, pLPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_lpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; } - return ma_lpf_reinit__internal(pConfig, pLPF, /*isNew*/MA_TRUE); + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_lpf_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_lpf_uninit(ma_lpf* pLPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ilpf1; + ma_uint32 ilpf2; + + if (pLPF == NULL) { + return; + } + + for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { + ma_lpf1_uninit(&pLPF->pLPF1[ilpf1], pAllocationCallbacks); + } + + for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { + ma_lpf2_uninit(&pLPF->pLPF2[ilpf2], pAllocationCallbacks); + } + + if (pLPF->_ownsHeap) { + ma_free(pLPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF) { - return ma_lpf_reinit__internal(pConfig, pLPF, /*isNew*/MA_FALSE); + return ma_lpf_reinit__internal(pConfig, NULL, pLPF, /*isNew*/MA_FALSE); +} + +MA_API ma_result ma_lpf_clear_cache(ma_lpf* pLPF) +{ + ma_uint32 ilpf1; + ma_uint32 ilpf2; + + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { + ma_lpf1_clear_cache(&pLPF->pLPF1[ilpf1]); + } + + for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { + ma_lpf2_clear_cache(&pLPF->pLPF2[ilpf2]); + } + + return MA_SUCCESS; } static MA_INLINE void ma_lpf_process_pcm_frame_f32(ma_lpf* pLPF, float* pY, const void* pX) @@ -37579,11 +44634,11 @@ static MA_INLINE void ma_lpf_process_pcm_frame_f32(ma_lpf* pLPF, float* pY, cons MA_COPY_MEMORY(pY, pX, ma_get_bytes_per_frame(pLPF->format, pLPF->channels)); for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - ma_lpf1_process_pcm_frame_f32(&pLPF->lpf1[ilpf1], pY, pY); + ma_lpf1_process_pcm_frame_f32(&pLPF->pLPF1[ilpf1], pY, pY); } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - ma_lpf2_process_pcm_frame_f32(&pLPF->lpf2[ilpf2], pY, pY); + ma_lpf2_process_pcm_frame_f32(&pLPF->pLPF2[ilpf2], pY, pY); } } @@ -37597,11 +44652,11 @@ static MA_INLINE void ma_lpf_process_pcm_frame_s16(ma_lpf* pLPF, ma_int16* pY, c MA_COPY_MEMORY(pY, pX, ma_get_bytes_per_frame(pLPF->format, pLPF->channels)); for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - ma_lpf1_process_pcm_frame_s16(&pLPF->lpf1[ilpf1], pY, pY); + ma_lpf1_process_pcm_frame_s16(&pLPF->pLPF1[ilpf1], pY, pY); } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - ma_lpf2_process_pcm_frame_s16(&pLPF->lpf2[ilpf2], pY, pY); + ma_lpf2_process_pcm_frame_s16(&pLPF->pLPF2[ilpf2], pY, pY); } } @@ -37618,14 +44673,14 @@ MA_API ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ilpf1 = 0; ilpf1 < pLPF->lpf1Count; ilpf1 += 1) { - result = ma_lpf1_process_pcm_frames(&pLPF->lpf1[ilpf1], pFramesOut, pFramesOut, frameCount); + result = ma_lpf1_process_pcm_frames(&pLPF->pLPF1[ilpf1], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } } for (ilpf2 = 0; ilpf2 < pLPF->lpf2Count; ilpf2 += 1) { - result = ma_lpf2_process_pcm_frames(&pLPF->lpf2[ilpf2], pFramesOut, pFramesOut, frameCount); + result = ma_lpf2_process_pcm_frames(&pLPF->pLPF2[ilpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -37711,23 +44766,120 @@ MA_API ma_hpf2_config ma_hpf2_config_init(ma_format format, ma_uint32 channels, } -MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, ma_hpf1* pHPF) +typedef struct { - if (pHPF == NULL) { - return MA_INVALID_ARGS; - } + size_t sizeInBytes; + size_t r1Offset; +} ma_hpf1_heap_layout; - MA_ZERO_OBJECT(pHPF); +static ma_result ma_hpf1_get_heap_layout(const ma_hpf1_config* pConfig, ma_hpf1_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); if (pConfig == NULL) { return MA_INVALID_ARGS; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { + if (pConfig->channels == 0) { return MA_INVALID_ARGS; } - return ma_hpf1_reinit(pConfig, pHPF); + pHeapLayout->sizeInBytes = 0; + + /* R1 */ + pHeapLayout->r1Offset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_biquad_coefficient) * pConfig->channels; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_hpf1_get_heap_size(const ma_hpf1_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_hpf1_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_hpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_hpf1_init_preallocated(const ma_hpf1_config* pConfig, void* pHeap, ma_hpf1* pLPF) +{ + ma_result result; + ma_hpf1_heap_layout heapLayout; + + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pLPF); + + result = ma_hpf1_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pLPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pLPF->pR1 = (ma_biquad_coefficient*)ma_offset_ptr(pHeap, heapLayout.r1Offset); + + return ma_hpf1_reinit(pConfig, pLPF); +} + +MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf1* pLPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf1_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf1_init_preallocated(pConfig, pHeap, pLPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pLPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_hpf1_uninit(ma_hpf1* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pHPF == NULL) { + return; + } + + if (pHPF->_ownsHeap) { + ma_free(pHPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_hpf1_reinit(const ma_hpf1_config* pConfig, ma_hpf1* pHPF) @@ -37773,16 +44925,16 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_f32(ma_hpf1* pHPF, float* pY, co const float a = 1 - pHPF->a.f32; const float b = 1 - a; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - float r1 = pHPF->r1[c].f32; + float r1 = pHPF->pR1[c].f32; float x = pX[c]; float y; y = b*x - a*r1; - pY[c] = y; - pHPF->r1[c].f32 = y; + pY[c] = y; + pHPF->pR1[c].f32 = y; } } @@ -37793,16 +44945,16 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_s16(ma_hpf1* pHPF, ma_int16* pY, const ma_int32 a = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - pHPF->a.s32); const ma_int32 b = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - a); - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { - ma_int32 r1 = pHPF->r1[c].s32; + ma_int32 r1 = pHPF->pR1[c].s32; ma_int32 x = pX[c]; ma_int32 y; y = (b*x - a*r1) >> MA_BIQUAD_FIXED_POINT_SHIFT; - pY[c] = (ma_int16)y; - pHPF->r1[c].s32 = (ma_int32)y; + pY[c] = (ma_int16)y; + pHPF->pR1[c].s32 = (ma_int32)y; } } @@ -37882,7 +45034,15 @@ static MA_INLINE ma_biquad_config ma_hpf2__get_biquad_config(const ma_hpf2_confi return bqConfig; } -MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) +MA_API ma_result ma_hpf2_get_heap_size(const ma_hpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_hpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_hpf2_init_preallocated(const ma_hpf2_config* pConfig, void* pHeap, ma_hpf2* pHPF) { ma_result result; ma_biquad_config bqConfig; @@ -37898,7 +45058,7 @@ MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) } bqConfig = ma_hpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pHPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pHPF->bq); if (result != MA_SUCCESS) { return result; } @@ -37906,6 +45066,45 @@ MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) return MA_SUCCESS; } +MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf2* pHPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf2_init_preallocated(pConfig, pHeap, pHPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pHPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_hpf2_uninit(ma_hpf2* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pHPF == NULL) { + return; + } + + ma_biquad_uninit(&pHPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_hpf2_reinit(const ma_hpf2_config* pConfig, ma_hpf2* pHPF) { ma_result result; @@ -37967,7 +45166,24 @@ MA_API ma_hpf_config ma_hpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* pHPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t hpf1Offset; + size_t hpf2Offset; /* Offset of the first second order filter. Subsequent filters will come straight after, and will each have the same heap size. */ +} ma_hpf_heap_layout; + +static void ma_hpf_calculate_sub_hpf_counts(ma_uint32 order, ma_uint32* pHPF1Count, ma_uint32* pHPF2Count) +{ + MA_ASSERT(pHPF1Count != NULL); + MA_ASSERT(pHPF2Count != NULL); + + *pHPF1Count = order % 2; + *pHPF2Count = order / 2; +} + +static ma_result ma_hpf_get_heap_layout(const ma_hpf_config* pConfig, ma_hpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 hpf1Count; @@ -37975,6 +45191,69 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p ma_uint32 ihpf1; ma_uint32 ihpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + ma_hpf_calculate_sub_hpf_counts(pConfig->order, &hpf1Count, &hpf2Count); + + pHeapLayout->sizeInBytes = 0; + + /* HPF 1 */ + pHeapLayout->hpf1Offset = pHeapLayout->sizeInBytes; + for (ihpf1 = 0; ihpf1 < hpf1Count; ihpf1 += 1) { + size_t hpf1HeapSizeInBytes; + ma_hpf1_config hpf1Config = ma_hpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); + + result = ma_hpf1_get_heap_size(&hpf1Config, &hpf1HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_hpf1) + hpf1HeapSizeInBytes; + } + + /* HPF 2*/ + pHeapLayout->hpf2Offset = pHeapLayout->sizeInBytes; + for (ihpf2 = 0; ihpf2 < hpf2Count; ihpf2 += 1) { + size_t hpf2HeapSizeInBytes; + ma_hpf2_config hpf2Config = ma_hpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_hpf2_get_heap_size(&hpf2Config, &hpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_hpf2) + hpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pHPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 hpf1Count; + ma_uint32 hpf2Count; + ma_uint32 ihpf1; + ma_uint32 ihpf2; + ma_hpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pHPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -37998,11 +45277,7 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p return MA_INVALID_ARGS; } - hpf1Count = pConfig->order % 2; - hpf2Count = pConfig->order / 2; - - MA_ASSERT(hpf1Count <= ma_countof(pHPF->hpf1)); - MA_ASSERT(hpf2Count <= ma_countof(pHPF->hpf2)); + ma_hpf_calculate_sub_hpf_counts(pConfig->order, &hpf1Count, &hpf2Count); /* The filter order can't change between reinits. */ if (!isNew) { @@ -38011,16 +45286,42 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p } } + if (isNew) { + result = ma_hpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pHPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pHPF->pHPF1 = (ma_hpf1*)ma_offset_ptr(pHeap, heapLayout.hpf1Offset); + pHPF->pHPF2 = (ma_hpf2*)ma_offset_ptr(pHeap, heapLayout.hpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); /* To silence a compiler warning. */ + } + for (ihpf1 = 0; ihpf1 < hpf1Count; ihpf1 += 1) { ma_hpf1_config hpf1Config = ma_hpf1_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency); if (isNew) { - result = ma_hpf1_init(&hpf1Config, &pHPF->hpf1[ihpf1]); + size_t hpf1HeapSizeInBytes; + + result = ma_hpf1_get_heap_size(&hpf1Config, &hpf1HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_hpf1_init_preallocated(&hpf1Config, ma_offset_ptr(pHeap, heapLayout.hpf1Offset + (sizeof(ma_hpf1) * hpf1Count) + (ihpf1 * hpf1HeapSizeInBytes)), &pHPF->pHPF1[ihpf1]); + } } else { - result = ma_hpf1_reinit(&hpf1Config, &pHPF->hpf1[ihpf1]); + result = ma_hpf1_reinit(&hpf1Config, &pHPF->pHPF1[ihpf1]); } if (result != MA_SUCCESS) { + ma_uint32 jhpf1; + + for (jhpf1 = 0; jhpf1 < ihpf1; jhpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[jhpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -38041,12 +45342,28 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p hpf2Config = ma_hpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_hpf2_init(&hpf2Config, &pHPF->hpf2[ihpf2]); + size_t hpf2HeapSizeInBytes; + + result = ma_hpf2_get_heap_size(&hpf2Config, &hpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_hpf2_init_preallocated(&hpf2Config, ma_offset_ptr(pHeap, heapLayout.hpf2Offset + (sizeof(ma_hpf2) * hpf2Count) + (ihpf2 * hpf2HeapSizeInBytes)), &pHPF->pHPF2[ihpf2]); + } } else { - result = ma_hpf2_reinit(&hpf2Config, &pHPF->hpf2[ihpf2]); + result = ma_hpf2_reinit(&hpf2Config, &pHPF->pHPF2[ihpf2]); } if (result != MA_SUCCESS) { + ma_uint32 jhpf1; + ma_uint32 jhpf2; + + for (jhpf1 = 0; jhpf1 < hpf1Count; jhpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[jhpf1], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + + for (jhpf2 = 0; jhpf2 < ihpf2; jhpf2 += 1) { + ma_hpf2_uninit(&pHPF->pHPF2[jhpf2], NULL); /* No need for allocation callbacks here since we used a preallocated heap allocation. */ + } + return result; } } @@ -38060,24 +45377,93 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p return MA_SUCCESS; } -MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, ma_hpf* pHPF) +MA_API ma_result ma_hpf_get_heap_size(const ma_hpf_config* pConfig, size_t* pHeapSizeInBytes) { + ma_result result; + ma_hpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_hpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return result; +} + +MA_API ma_result ma_hpf_init_preallocated(const ma_hpf_config* pConfig, void* pHeap, ma_hpf* pLPF) +{ + if (pLPF == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pLPF); + + return ma_hpf_reinit__internal(pConfig, pHeap, pLPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf* pHPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hpf_init_preallocated(pConfig, pHeap, pHPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pHPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_hpf_uninit(ma_hpf* pHPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ihpf1; + ma_uint32 ihpf2; + if (pHPF == NULL) { - return MA_INVALID_ARGS; + return; } - MA_ZERO_OBJECT(pHPF); - - if (pConfig == NULL) { - return MA_INVALID_ARGS; + for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { + ma_hpf1_uninit(&pHPF->pHPF1[ihpf1], pAllocationCallbacks); } - return ma_hpf_reinit__internal(pConfig, pHPF, /*isNew*/MA_TRUE); + for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { + ma_hpf2_uninit(&pHPF->pHPF2[ihpf2], pAllocationCallbacks); + } + + if (pHPF->_ownsHeap) { + ma_free(pHPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_hpf_reinit(const ma_hpf_config* pConfig, ma_hpf* pHPF) { - return ma_hpf_reinit__internal(pConfig, pHPF, /*isNew*/MA_FALSE); + return ma_hpf_reinit__internal(pConfig, NULL, pHPF, /*isNew*/MA_FALSE); } MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -38093,14 +45479,14 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - result = ma_hpf1_process_pcm_frames(&pHPF->hpf1[ihpf1], pFramesOut, pFramesOut, frameCount); + result = ma_hpf1_process_pcm_frames(&pHPF->pHPF1[ihpf1], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - result = ma_hpf2_process_pcm_frames(&pHPF->hpf2[ihpf2], pFramesOut, pFramesOut, frameCount); + result = ma_hpf2_process_pcm_frames(&pHPF->pHPF2[ihpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -38119,11 +45505,11 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutF32, pFramesInF32, ma_get_bytes_per_frame(pHPF->format, pHPF->channels)); for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - ma_hpf1_process_pcm_frame_f32(&pHPF->hpf1[ihpf1], pFramesOutF32, pFramesOutF32); + ma_hpf1_process_pcm_frame_f32(&pHPF->pHPF1[ihpf1], pFramesOutF32, pFramesOutF32); } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - ma_hpf2_process_pcm_frame_f32(&pHPF->hpf2[ihpf2], pFramesOutF32, pFramesOutF32); + ma_hpf2_process_pcm_frame_f32(&pHPF->pHPF2[ihpf2], pFramesOutF32, pFramesOutF32); } pFramesOutF32 += pHPF->channels; @@ -38137,11 +45523,11 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutS16, pFramesInS16, ma_get_bytes_per_frame(pHPF->format, pHPF->channels)); for (ihpf1 = 0; ihpf1 < pHPF->hpf1Count; ihpf1 += 1) { - ma_hpf1_process_pcm_frame_s16(&pHPF->hpf1[ihpf1], pFramesOutS16, pFramesOutS16); + ma_hpf1_process_pcm_frame_s16(&pHPF->pHPF1[ihpf1], pFramesOutS16, pFramesOutS16); } for (ihpf2 = 0; ihpf2 < pHPF->hpf2Count; ihpf2 += 1) { - ma_hpf2_process_pcm_frame_s16(&pHPF->hpf2[ihpf2], pFramesOutS16, pFramesOutS16); + ma_hpf2_process_pcm_frame_s16(&pHPF->pHPF2[ihpf2], pFramesOutS16, pFramesOutS16); } pFramesOutS16 += pHPF->channels; @@ -38221,7 +45607,15 @@ static MA_INLINE ma_biquad_config ma_bpf2__get_biquad_config(const ma_bpf2_confi return bqConfig; } -MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) +MA_API ma_result ma_bpf2_get_heap_size(const ma_bpf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_bpf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_bpf2_init_preallocated(const ma_bpf2_config* pConfig, void* pHeap, ma_bpf2* pBPF) { ma_result result; ma_biquad_config bqConfig; @@ -38237,7 +45631,7 @@ MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) } bqConfig = ma_bpf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pBPF->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pBPF->bq); if (result != MA_SUCCESS) { return result; } @@ -38245,6 +45639,45 @@ MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) return MA_SUCCESS; } +MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf2* pBPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_bpf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_bpf2_init_preallocated(pConfig, pHeap, pBPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBPF->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_bpf2_uninit(ma_bpf2* pBPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pBPF == NULL) { + return; + } + + ma_biquad_uninit(&pBPF->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_bpf2_reinit(const ma_bpf2_config* pConfig, ma_bpf2* pBPF) { ma_result result; @@ -38306,12 +45739,67 @@ MA_API ma_bpf_config ma_bpf_config_init(ma_format format, ma_uint32 channels, ma return config; } -static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* pBPF, ma_bool32 isNew) + +typedef struct +{ + size_t sizeInBytes; + size_t bpf2Offset; +} ma_bpf_heap_layout; + +static ma_result ma_bpf_get_heap_layout(const ma_bpf_config* pConfig, ma_bpf_heap_layout* pHeapLayout) { ma_result result; ma_uint32 bpf2Count; ma_uint32 ibpf2; + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->order > MA_MAX_FILTER_ORDER) { + return MA_INVALID_ARGS; + } + + /* We must have an even number of order. */ + if ((pConfig->order & 0x1) != 0) { + return MA_INVALID_ARGS; + } + + bpf2Count = pConfig->channels / 2; + + pHeapLayout->sizeInBytes = 0; + + /* BPF 2 */ + pHeapLayout->bpf2Offset = pHeapLayout->sizeInBytes; + for (ibpf2 = 0; ibpf2 < bpf2Count; ibpf2 += 1) { + size_t bpf2HeapSizeInBytes; + ma_bpf2_config bpf2Config = ma_bpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, 0.707107); /* <-- The "q" parameter does not matter for the purpose of calculating the heap size. */ + + result = ma_bpf2_get_heap_size(&bpf2Config, &bpf2HeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += sizeof(ma_bpf2) + bpf2HeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF, ma_bool32 isNew) +{ + ma_result result; + ma_uint32 bpf2Count; + ma_uint32 ibpf2; + ma_bpf_heap_layout heapLayout; /* Only used if isNew is true. */ + if (pBPF == NULL || pConfig == NULL) { return MA_INVALID_ARGS; } @@ -38342,8 +45830,6 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p bpf2Count = pConfig->order / 2; - MA_ASSERT(bpf2Count <= ma_countof(pBPF->bpf2)); - /* The filter order can't change between reinits. */ if (!isNew) { if (pBPF->bpf2Count != bpf2Count) { @@ -38351,6 +45837,20 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p } } + if (isNew) { + result = ma_bpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pBPF->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pBPF->pBPF2 = (ma_bpf2*)ma_offset_ptr(pHeap, heapLayout.bpf2Offset); + } else { + MA_ZERO_OBJECT(&heapLayout); + } + for (ibpf2 = 0; ibpf2 < bpf2Count; ibpf2 += 1) { ma_bpf2_config bpf2Config; double q; @@ -38361,9 +45861,14 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p bpf2Config = ma_bpf2_config_init(pConfig->format, pConfig->channels, pConfig->sampleRate, pConfig->cutoffFrequency, q); if (isNew) { - result = ma_bpf2_init(&bpf2Config, &pBPF->bpf2[ibpf2]); + size_t bpf2HeapSizeInBytes; + + result = ma_bpf2_get_heap_size(&bpf2Config, &bpf2HeapSizeInBytes); + if (result == MA_SUCCESS) { + result = ma_bpf2_init_preallocated(&bpf2Config, ma_offset_ptr(pHeap, heapLayout.bpf2Offset + (sizeof(ma_bpf2) * bpf2Count) + (ibpf2 * bpf2HeapSizeInBytes)), &pBPF->pBPF2[ibpf2]); + } } else { - result = ma_bpf2_reinit(&bpf2Config, &pBPF->bpf2[ibpf2]); + result = ma_bpf2_reinit(&bpf2Config, &pBPF->pBPF2[ibpf2]); } if (result != MA_SUCCESS) { @@ -38378,7 +45883,29 @@ static ma_result ma_bpf_reinit__internal(const ma_bpf_config* pConfig, ma_bpf* p return MA_SUCCESS; } -MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF) + +MA_API ma_result ma_bpf_get_heap_size(const ma_bpf_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_bpf_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_bpf_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_bpf_init_preallocated(const ma_bpf_config* pConfig, void* pHeap, ma_bpf* pBPF) { if (pBPF == NULL) { return MA_INVALID_ARGS; @@ -38386,16 +45913,59 @@ MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF) MA_ZERO_OBJECT(pBPF); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + return ma_bpf_reinit__internal(pConfig, pHeap, pBPF, /*isNew*/MA_TRUE); +} + +MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf* pBPF) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_bpf_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; } - return ma_bpf_reinit__internal(pConfig, pBPF, /*isNew*/MA_TRUE); + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_bpf_init_preallocated(pConfig, pHeap, pBPF); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pBPF->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_bpf_uninit(ma_bpf* pBPF, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_uint32 ibpf2; + + if (pBPF == NULL) { + return; + } + + for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { + ma_bpf2_uninit(&pBPF->pBPF2[ibpf2], pAllocationCallbacks); + } + + if (pBPF->_ownsHeap) { + ma_free(pBPF->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_bpf_reinit(const ma_bpf_config* pConfig, ma_bpf* pBPF) { - return ma_bpf_reinit__internal(pConfig, pBPF, /*isNew*/MA_FALSE); + return ma_bpf_reinit__internal(pConfig, NULL, pBPF, /*isNew*/MA_FALSE); } MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -38410,7 +45980,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const /* Faster path for in-place. */ if (pFramesOut == pFramesIn) { for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - result = ma_bpf2_process_pcm_frames(&pBPF->bpf2[ibpf2], pFramesOut, pFramesOut, frameCount); + result = ma_bpf2_process_pcm_frames(&pBPF->pBPF2[ibpf2], pFramesOut, pFramesOut, frameCount); if (result != MA_SUCCESS) { return result; } @@ -38429,7 +45999,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutF32, pFramesInF32, ma_get_bytes_per_frame(pBPF->format, pBPF->channels)); for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - ma_bpf2_process_pcm_frame_f32(&pBPF->bpf2[ibpf2], pFramesOutF32, pFramesOutF32); + ma_bpf2_process_pcm_frame_f32(&pBPF->pBPF2[ibpf2], pFramesOutF32, pFramesOutF32); } pFramesOutF32 += pBPF->channels; @@ -38443,7 +46013,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const MA_COPY_MEMORY(pFramesOutS16, pFramesInS16, ma_get_bytes_per_frame(pBPF->format, pBPF->channels)); for (ibpf2 = 0; ibpf2 < pBPF->bpf2Count; ibpf2 += 1) { - ma_bpf2_process_pcm_frame_s16(&pBPF->bpf2[ibpf2], pFramesOutS16, pFramesOutS16); + ma_bpf2_process_pcm_frame_s16(&pBPF->pBPF2[ibpf2], pFramesOutS16, pFramesOutS16); } pFramesOutS16 += pBPF->channels; @@ -38522,7 +46092,15 @@ static MA_INLINE ma_biquad_config ma_notch2__get_biquad_config(const ma_notch2_c return bqConfig; } -MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFilter) +MA_API ma_result ma_notch2_get_heap_size(const ma_notch2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_notch2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_notch2_init_preallocated(const ma_notch2_config* pConfig, void* pHeap, ma_notch2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -38538,7 +46116,7 @@ MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFil } bqConfig = ma_notch2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -38546,6 +46124,45 @@ MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFil return MA_SUCCESS; } +MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_notch2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_notch2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_notch2_uninit(ma_notch2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_notch2_reinit(const ma_notch2_config* pConfig, ma_notch2* pFilter) { ma_result result; @@ -38651,7 +46268,15 @@ static MA_INLINE ma_biquad_config ma_peak2__get_biquad_config(const ma_peak2_con return bqConfig; } -MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter) +MA_API ma_result ma_peak2_get_heap_size(const ma_peak2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_peak2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_peak2_init_preallocated(const ma_peak2_config* pConfig, void* pHeap, ma_peak2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -38667,7 +46292,7 @@ MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter } bqConfig = ma_peak2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -38675,6 +46300,45 @@ MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter return MA_SUCCESS; } +MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_peak2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_peak2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_peak2_uninit(ma_peak2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_peak2_reinit(const ma_peak2_config* pConfig, ma_peak2* pFilter) { ma_result result; @@ -38777,7 +46441,15 @@ static MA_INLINE ma_biquad_config ma_loshelf2__get_biquad_config(const ma_loshel return bqConfig; } -MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter) +MA_API ma_result ma_loshelf2_get_heap_size(const ma_loshelf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_loshelf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_loshelf2_init_preallocated(const ma_loshelf2_config* pConfig, void* pHeap, ma_loshelf2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -38793,7 +46465,7 @@ MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2 } bqConfig = ma_loshelf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -38801,6 +46473,45 @@ MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2 return MA_SUCCESS; } +MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_loshelf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_loshelf2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_loshelf2_uninit(ma_loshelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_loshelf2_reinit(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter) { ma_result result; @@ -38903,7 +46614,15 @@ static MA_INLINE ma_biquad_config ma_hishelf2__get_biquad_config(const ma_hishel return bqConfig; } -MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter) +MA_API ma_result ma_hishelf2_get_heap_size(const ma_hishelf2_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_biquad_config bqConfig; + bqConfig = ma_hishelf2__get_biquad_config(pConfig); + + return ma_biquad_get_heap_size(&bqConfig, pHeapSizeInBytes); +} + +MA_API ma_result ma_hishelf2_init_preallocated(const ma_hishelf2_config* pConfig, void* pHeap, ma_hishelf2* pFilter) { ma_result result; ma_biquad_config bqConfig; @@ -38919,7 +46638,7 @@ MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2 } bqConfig = ma_hishelf2__get_biquad_config(pConfig); - result = ma_biquad_init(&bqConfig, &pFilter->bq); + result = ma_biquad_init_preallocated(&bqConfig, pHeap, &pFilter->bq); if (result != MA_SUCCESS) { return result; } @@ -38927,6 +46646,45 @@ MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2 return MA_SUCCESS; } +MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf2* pFilter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_hishelf2_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_hishelf2_init_preallocated(pConfig, pHeap, pFilter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pFilter->bq._ownsHeap = MA_TRUE; /* <-- This will cause the biquad to take ownership of the heap and free it when it's uninitialized. */ + return MA_SUCCESS; +} + +MA_API void ma_hishelf2_uninit(ma_hishelf2* pFilter, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pFilter == NULL) { + return; + } + + ma_biquad_uninit(&pFilter->bq, pAllocationCallbacks); /* <-- This will free the heap allocation. */ +} + MA_API ma_result ma_hishelf2_reinit(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter) { ma_result result; @@ -38975,6 +46733,2286 @@ MA_API ma_uint32 ma_hishelf2_get_latency(const ma_hishelf2* pFilter) +/* +Delay +*/ +MA_API ma_delay_config ma_delay_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay) +{ + ma_delay_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + config.sampleRate = sampleRate; + config.delayInFrames = delayInFrames; + config.delayStart = (decay == 0) ? MA_TRUE : MA_FALSE; /* Delay the start if it looks like we're not configuring an echo. */ + config.wet = 1; + config.dry = 1; + config.decay = decay; + + return config; +} + + +MA_API ma_result ma_delay_init(const ma_delay_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay* pDelay) +{ + if (pDelay == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDelay); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->decay < 0 || pConfig->decay > 1) { + return MA_INVALID_ARGS; + } + + pDelay->config = *pConfig; + pDelay->bufferSizeInFrames = pConfig->delayInFrames; + pDelay->cursor = 0; + + pDelay->pBuffer = (float*)ma_malloc((size_t)(pDelay->bufferSizeInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->channels)), pAllocationCallbacks); + if (pDelay->pBuffer == NULL) { + return MA_OUT_OF_MEMORY; + } + + ma_silence_pcm_frames(pDelay->pBuffer, pDelay->bufferSizeInFrames, ma_format_f32, pConfig->channels); + + return MA_SUCCESS; +} + +MA_API void ma_delay_uninit(ma_delay* pDelay, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pDelay == NULL) { + return; + } + + ma_free(pDelay->pBuffer, pAllocationCallbacks); +} + +MA_API ma_result ma_delay_process_pcm_frames(ma_delay* pDelay, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_uint32 iFrame; + ma_uint32 iChannel; + float* pFramesOutF32 = (float*)pFramesOut; + const float* pFramesInF32 = (const float*)pFramesIn; + + if (pDelay == NULL || pFramesOut == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannel = 0; iChannel < pDelay->config.channels; iChannel += 1) { + ma_uint32 iBuffer = (pDelay->cursor * pDelay->config.channels) + iChannel; + + if (pDelay->config.delayStart) { + /* Delayed start. */ + + /* Read */ + pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet; + + /* Feedback */ + pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry); + } else { + /* Immediate start */ + + /* Feedback */ + pDelay->pBuffer[iBuffer] = (pDelay->pBuffer[iBuffer] * pDelay->config.decay) + (pFramesInF32[iChannel] * pDelay->config.dry); + + /* Read */ + pFramesOutF32[iChannel] = pDelay->pBuffer[iBuffer] * pDelay->config.wet; + } + } + + pDelay->cursor = (pDelay->cursor + 1) % pDelay->bufferSizeInFrames; + + pFramesOutF32 += pDelay->config.channels; + pFramesInF32 += pDelay->config.channels; + } + + return MA_SUCCESS; +} + +MA_API void ma_delay_set_wet(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.wet = value; +} + +MA_API float ma_delay_get_wet(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.wet; +} + +MA_API void ma_delay_set_dry(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.dry = value; +} + +MA_API float ma_delay_get_dry(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.dry; +} + +MA_API void ma_delay_set_decay(ma_delay* pDelay, float value) +{ + if (pDelay == NULL) { + return; + } + + pDelay->config.decay = value; +} + +MA_API float ma_delay_get_decay(const ma_delay* pDelay) +{ + if (pDelay == NULL) { + return 0; + } + + return pDelay->config.decay; +} + + +MA_API ma_gainer_config ma_gainer_config_init(ma_uint32 channels, ma_uint32 smoothTimeInFrames) +{ + ma_gainer_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + config.smoothTimeInFrames = smoothTimeInFrames; + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t oldGainsOffset; + size_t newGainsOffset; +} ma_gainer_heap_layout; + +static ma_result ma_gainer_get_heap_layout(const ma_gainer_config* pConfig, ma_gainer_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Old gains. */ + pHeapLayout->oldGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + + /* New gains. */ + pHeapLayout->newGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + + /* Alignment. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + + +MA_API ma_result ma_gainer_get_heap_size(const ma_gainer_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_gainer_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_gainer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + + +MA_API ma_result ma_gainer_init_preallocated(const ma_gainer_config* pConfig, void* pHeap, ma_gainer* pGainer) +{ + ma_result result; + ma_gainer_heap_layout heapLayout; + ma_uint32 iChannel; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pGainer); + + if (pConfig == NULL || pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_gainer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pGainer->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pGainer->pOldGains = (float*)ma_offset_ptr(pHeap, heapLayout.oldGainsOffset); + pGainer->pNewGains = (float*)ma_offset_ptr(pHeap, heapLayout.newGainsOffset); + + pGainer->config = *pConfig; + pGainer->t = (ma_uint32)-1; /* No interpolation by default. */ + + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { + pGainer->pOldGains[iChannel] = 1; + pGainer->pNewGains[iChannel] = 1; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_gainer_init(const ma_gainer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_gainer* pGainer) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_gainer_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap allocation. */ + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_gainer_init_preallocated(pConfig, pHeap, pGainer); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pGainer->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_gainer_uninit(ma_gainer* pGainer, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pGainer == NULL) { + return; + } + + if (pGainer->_ownsHeap) { + ma_free(pGainer->_pHeap, pAllocationCallbacks); + } +} + +static float ma_gainer_calculate_current_gain(const ma_gainer* pGainer, ma_uint32 channel) +{ + float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames; + return ma_mix_f32_fast(pGainer->pOldGains[channel], pGainer->pNewGains[channel], a); +} + +MA_API ma_result ma_gainer_process_pcm_frames(ma_gainer* pGainer, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + ma_uint64 iFrame; + ma_uint32 iChannel; + float* pFramesOutF32 = (float*)pFramesOut; + const float* pFramesInF32 = (const float*)pFramesIn; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + if (pGainer->t >= pGainer->config.smoothTimeInFrames) { + /* Fast path. No gain calculation required. */ + ma_copy_and_apply_volume_factor_per_channel_f32(pFramesOutF32, pFramesInF32, frameCount, pGainer->config.channels, pGainer->pNewGains); + + /* Now that some frames have been processed we need to make sure future changes to the gain are interpolated. */ + if (pGainer->t == (ma_uint32)-1) { + pGainer->t = pGainer->config.smoothTimeInFrames; + } + } else { + /* Slow path. Need to interpolate the gain for each channel individually. */ + + /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */ + if (pFramesOut != NULL && pFramesIn != NULL) { + float a = (float)pGainer->t / pGainer->config.smoothTimeInFrames; + float d = 1.0f / pGainer->config.smoothTimeInFrames; + ma_uint32 channelCount = pGainer->config.channels; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannel = 0; iChannel < channelCount; iChannel += 1) { + pFramesOutF32[iChannel] = pFramesInF32[iChannel] * ma_mix_f32_fast(pGainer->pOldGains[iChannel], pGainer->pNewGains[iChannel], a); + } + + pFramesOutF32 += channelCount; + pFramesInF32 += channelCount; + + a += d; + if (a > 1) { + a = 1; + } + } + } + + pGainer->t = (ma_uint32)ma_min(pGainer->t + frameCount, pGainer->config.smoothTimeInFrames); + + #if 0 /* Reference implementation. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + /* We can allow the input and output buffers to be null in which case we'll just update the internal timer. */ + if (pFramesOut != NULL && pFramesIn != NULL) { + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + pFramesOutF32[iFrame*pGainer->config.channels + iChannel] = pFramesInF32[iFrame*pGainer->config.channels + iChannel] * ma_gainer_calculate_current_gain(pGainer, iChannel); + } + } + + /* Move interpolation time forward, but don't go beyond our smoothing time. */ + pGainer->t = ma_min(pGainer->t + 1, pGainer->config.smoothTimeInFrames); + } + #endif + } + + return MA_SUCCESS; +} + +static void ma_gainer_set_gain_by_index(ma_gainer* pGainer, float newGain, ma_uint32 iChannel) +{ + pGainer->pOldGains[iChannel] = ma_gainer_calculate_current_gain(pGainer, iChannel); + pGainer->pNewGains[iChannel] = newGain; +} + +static void ma_gainer_reset_smoothing_time(ma_gainer* pGainer) +{ + if (pGainer->t == (ma_uint32)-1) { + pGainer->t = pGainer->config.smoothTimeInFrames; /* No smoothing required for initial gains setting. */ + } else { + pGainer->t = 0; + } +} + +MA_API ma_result ma_gainer_set_gain(ma_gainer* pGainer, float newGain) +{ + ma_uint32 iChannel; + + if (pGainer == NULL) { + return MA_INVALID_ARGS; + } + + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + ma_gainer_set_gain_by_index(pGainer, newGain, iChannel); + } + + /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */ + ma_gainer_reset_smoothing_time(pGainer); + + return MA_SUCCESS; +} + +MA_API ma_result ma_gainer_set_gains(ma_gainer* pGainer, float* pNewGains) +{ + ma_uint32 iChannel; + + if (pGainer == NULL || pNewGains == NULL) { + return MA_INVALID_ARGS; + } + + for (iChannel = 0; iChannel < pGainer->config.channels; iChannel += 1) { + ma_gainer_set_gain_by_index(pGainer, pNewGains[iChannel], iChannel); + } + + /* The smoothing time needs to be reset to ensure we always interpolate by the configured smoothing time, but only if it's not the first setting. */ + ma_gainer_reset_smoothing_time(pGainer); + + return MA_SUCCESS; +} + + +MA_API ma_panner_config ma_panner_config_init(ma_format format, ma_uint32 channels) +{ + ma_panner_config config; + + MA_ZERO_OBJECT(&config); + config.format = format; + config.channels = channels; + config.mode = ma_pan_mode_balance; /* Set to balancing mode by default because it's consistent with other audio engines and most likely what the caller is expecting. */ + config.pan = 0; + + return config; +} + + +MA_API ma_result ma_panner_init(const ma_panner_config* pConfig, ma_panner* pPanner) +{ + if (pPanner == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pPanner); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pPanner->format = pConfig->format; + pPanner->channels = pConfig->channels; + pPanner->mode = pConfig->mode; + pPanner->pan = pConfig->pan; + + return MA_SUCCESS; +} + +static void ma_stereo_balance_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan) +{ + ma_uint64 iFrame; + + if (pan > 0) { + float factor = 1.0f - pan; + if (pFramesOut == pFramesIn) { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor; + } + } else { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0] * factor; + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1]; + } + } + } else { + float factor = 1.0f + pan; + if (pFramesOut == pFramesIn) { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor; + } + } else { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + pFramesOut[iFrame*2 + 0] = pFramesIn[iFrame*2 + 0]; + pFramesOut[iFrame*2 + 1] = pFramesIn[iFrame*2 + 1] * factor; + } + } + } +} + +static void ma_stereo_balance_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan) +{ + if (pan == 0) { + /* Fast path. No panning required. */ + if (pFramesOut == pFramesIn) { + /* No-op */ + } else { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } + + return; + } + + switch (format) { + case ma_format_f32: ma_stereo_balance_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break; + + /* Unknown format. Just copy. */ + default: + { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } break; + } +} + + +static void ma_stereo_pan_pcm_frames_f32(float* pFramesOut, const float* pFramesIn, ma_uint64 frameCount, float pan) +{ + ma_uint64 iFrame; + + if (pan > 0) { + float factorL0 = 1.0f - pan; + float factorL1 = 0.0f + pan; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float sample0 = (pFramesIn[iFrame*2 + 0] * factorL0); + float sample1 = (pFramesIn[iFrame*2 + 0] * factorL1) + pFramesIn[iFrame*2 + 1]; + + pFramesOut[iFrame*2 + 0] = sample0; + pFramesOut[iFrame*2 + 1] = sample1; + } + } else { + float factorR0 = 0.0f - pan; + float factorR1 = 1.0f + pan; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float sample0 = pFramesIn[iFrame*2 + 0] + (pFramesIn[iFrame*2 + 1] * factorR0); + float sample1 = (pFramesIn[iFrame*2 + 1] * factorR1); + + pFramesOut[iFrame*2 + 0] = sample0; + pFramesOut[iFrame*2 + 1] = sample1; + } + } +} + +static void ma_stereo_pan_pcm_frames(void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount, ma_format format, float pan) +{ + if (pan == 0) { + /* Fast path. No panning required. */ + if (pFramesOut == pFramesIn) { + /* No-op */ + } else { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } + + return; + } + + switch (format) { + case ma_format_f32: ma_stereo_pan_pcm_frames_f32((float*)pFramesOut, (float*)pFramesIn, frameCount, pan); break; + + /* Unknown format. Just copy. */ + default: + { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, format, 2); + } break; + } +} + +MA_API ma_result ma_panner_process_pcm_frames(ma_panner* pPanner, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + if (pPanner == NULL || pFramesOut == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + if (pPanner->channels == 2) { + /* Stereo case. For now assume channel 0 is left and channel right is 1, but should probably add support for a channel map. */ + if (pPanner->mode == ma_pan_mode_balance) { + ma_stereo_balance_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan); + } else { + ma_stereo_pan_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->pan); + } + } else { + if (pPanner->channels == 1) { + /* Panning has no effect on mono streams. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels); + } else { + /* For now we're not going to support non-stereo set ups. Not sure how I want to handle this case just yet. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pPanner->format, pPanner->channels); + } + } + + return MA_SUCCESS; +} + +MA_API void ma_panner_set_mode(ma_panner* pPanner, ma_pan_mode mode) +{ + if (pPanner == NULL) { + return; + } + + pPanner->mode = mode; +} + +MA_API ma_pan_mode ma_panner_get_mode(const ma_panner* pPanner) +{ + if (pPanner == NULL) { + return ma_pan_mode_balance; + } + + return pPanner->mode; +} + +MA_API void ma_panner_set_pan(ma_panner* pPanner, float pan) +{ + if (pPanner == NULL) { + return; + } + + pPanner->pan = ma_clamp(pan, -1.0f, 1.0f); +} + +MA_API float ma_panner_get_pan(const ma_panner* pPanner) +{ + if (pPanner == NULL) { + return 0; + } + + return pPanner->pan; +} + + + + +MA_API ma_fader_config ma_fader_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + ma_fader_config config; + + MA_ZERO_OBJECT(&config); + config.format = format; + config.channels = channels; + config.sampleRate = sampleRate; + + return config; +} + + +MA_API ma_result ma_fader_init(const ma_fader_config* pConfig, ma_fader* pFader) +{ + if (pFader == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pFader); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* Only f32 is supported for now. */ + if (pConfig->format != ma_format_f32) { + return MA_INVALID_ARGS; + } + + pFader->config = *pConfig; + pFader->volumeBeg = 1; + pFader->volumeEnd = 1; + pFader->lengthInFrames = 0; + pFader->cursorInFrames = 0; + + return MA_SUCCESS; +} + +MA_API ma_result ma_fader_process_pcm_frames(ma_fader* pFader, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + if (pFader == NULL) { + return MA_INVALID_ARGS; + } + + /* + For now we need to clamp frameCount so that the cursor never overflows 32-bits. This is required for + the conversion to a float which we use for the linear interpolation. This might be changed later. + */ + if (frameCount + pFader->cursorInFrames > UINT_MAX) { + frameCount = UINT_MAX - pFader->cursorInFrames; + } + + /* Optimized path if volumeBeg and volumeEnd are equal. */ + if (pFader->volumeBeg == pFader->volumeEnd) { + if (pFader->volumeBeg == 1) { + /* Straight copy. */ + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels); + } else { + /* Copy with volume. */ + ma_copy_and_apply_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd); + } + } else { + /* Slower path. Volumes are different, so may need to do an interpolation. */ + if (pFader->cursorInFrames >= pFader->lengthInFrames) { + /* Fast path. We've gone past the end of the fade period so just apply the end volume to all samples. */ + ma_copy_and_apply_volume_and_clip_pcm_frames(pFramesOut, pFramesIn, frameCount, pFader->config.format, pFader->config.channels, pFader->volumeEnd); + } else { + /* Slow path. This is where we do the actual fading. */ + ma_uint64 iFrame; + ma_uint32 iChannel; + + /* For now we only support f32. Support for other formats will be added later. */ + if (pFader->config.format == ma_format_f32) { + const float* pFramesInF32 = (const float*)pFramesIn; + /* */ float* pFramesOutF32 = ( float*)pFramesOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float a = (ma_uint32)ma_min(pFader->cursorInFrames + iFrame, pFader->lengthInFrames) / (float)((ma_uint32)pFader->lengthInFrames); /* Safe cast due to the frameCount clamp at the top of this function. */ + float volume = ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, a); + + for (iChannel = 0; iChannel < pFader->config.channels; iChannel += 1) { + pFramesOutF32[iFrame*pFader->config.channels + iChannel] = pFramesInF32[iFrame*pFader->config.channels + iChannel] * volume; + } + } + } else { + return MA_NOT_IMPLEMENTED; + } + } + } + + pFader->cursorInFrames += frameCount; + + return MA_SUCCESS; +} + +MA_API void ma_fader_get_data_format(const ma_fader* pFader, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +{ + if (pFader == NULL) { + return; + } + + if (pFormat != NULL) { + *pFormat = pFader->config.format; + } + + if (pChannels != NULL) { + *pChannels = pFader->config.channels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pFader->config.sampleRate; + } +} + +MA_API void ma_fader_set_fade(ma_fader* pFader, float volumeBeg, float volumeEnd, ma_uint64 lengthInFrames) +{ + if (pFader == NULL) { + return; + } + + /* If the volume is negative, use current volume. */ + if (volumeBeg < 0) { + volumeBeg = ma_fader_get_current_volume(pFader); + } + + /* + The length needs to be clamped to 32-bits due to how we convert it to a float for linear + interpolation reasons. I might change this requirement later, but for now it's not important. + */ + if (lengthInFrames > UINT_MAX) { + lengthInFrames = UINT_MAX; + } + + pFader->volumeBeg = volumeBeg; + pFader->volumeEnd = volumeEnd; + pFader->lengthInFrames = lengthInFrames; + pFader->cursorInFrames = 0; /* Reset cursor. */ +} + +MA_API float ma_fader_get_current_volume(ma_fader* pFader) +{ + if (pFader == NULL) { + return 0.0f; + } + + /* The current volume depends on the position of the cursor. */ + if (pFader->cursorInFrames == 0) { + return pFader->volumeBeg; + } else if (pFader->cursorInFrames >= pFader->lengthInFrames) { + return pFader->volumeEnd; + } else { + /* The cursor is somewhere inside the fading period. We can figure this out with a simple linear interpoluation between volumeBeg and volumeEnd based on our cursor position. */ + return ma_mix_f32_fast(pFader->volumeBeg, pFader->volumeEnd, (ma_uint32)pFader->cursorInFrames / (float)((ma_uint32)pFader->lengthInFrames)); /* Safe cast to uint32 because we clamp it in ma_fader_process_pcm_frames(). */ + } +} + + + + + +MA_API ma_vec3f ma_vec3f_init_3f(float x, float y, float z) +{ + ma_vec3f v; + + v.x = x; + v.y = y; + v.z = z; + + return v; +} + +MA_API ma_vec3f ma_vec3f_sub(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_init_3f( + a.x - b.x, + a.y - b.y, + a.z - b.z + ); +} + +MA_API ma_vec3f ma_vec3f_neg(ma_vec3f a) +{ + return ma_vec3f_init_3f( + -a.x, + -a.y, + -a.z + ); +} + +MA_API float ma_vec3f_dot(ma_vec3f a, ma_vec3f b) +{ + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +MA_API float ma_vec3f_len2(ma_vec3f v) +{ + return ma_vec3f_dot(v, v); +} + +MA_API float ma_vec3f_len(ma_vec3f v) +{ + return (float)ma_sqrtd(ma_vec3f_len2(v)); +} + +MA_API float ma_vec3f_dist(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_len(ma_vec3f_sub(a, b)); +} + +MA_API ma_vec3f ma_vec3f_normalize(ma_vec3f v) +{ + float f; + float l = ma_vec3f_len(v); + if (l == 0) { + return ma_vec3f_init_3f(0, 0, 0); + } + + f = 1 / l; + v.x *= f; + v.y *= f; + v.z *= f; + + return v; +} + +MA_API ma_vec3f ma_vec3f_cross(ma_vec3f a, ma_vec3f b) +{ + return ma_vec3f_init_3f( + a.y*b.z - a.z*b.y, + a.z*b.x - a.x*b.z, + a.x*b.y - a.y*b.x + ); +} + + + +static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode, ma_mono_expansion_mode monoExpansionMode); +static ma_bool32 ma_is_spatial_channel_position(ma_channel channelPosition); + + +#ifndef MA_DEFAULT_SPEED_OF_SOUND +#define MA_DEFAULT_SPEED_OF_SOUND 343.3f +#endif + +/* +These vectors represent the direction that speakers are facing from the center point. They're used +for panning in the spatializer. Must be normalized. +*/ +static ma_vec3f g_maChannelDirections[MA_CHANNEL_POSITION_COUNT] = { + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_NONE */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_MONO */ + {-0.7071f, 0.0f, -0.7071f }, /* MA_CHANNEL_FRONT_LEFT */ + {+0.7071f, 0.0f, -0.7071f }, /* MA_CHANNEL_FRONT_RIGHT */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_FRONT_CENTER */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_LFE */ + {-0.7071f, 0.0f, +0.7071f }, /* MA_CHANNEL_BACK_LEFT */ + {+0.7071f, 0.0f, +0.7071f }, /* MA_CHANNEL_BACK_RIGHT */ + {-0.3162f, 0.0f, -0.9487f }, /* MA_CHANNEL_FRONT_LEFT_CENTER */ + {+0.3162f, 0.0f, -0.9487f }, /* MA_CHANNEL_FRONT_RIGHT_CENTER */ + { 0.0f, 0.0f, +1.0f }, /* MA_CHANNEL_BACK_CENTER */ + {-1.0f, 0.0f, 0.0f }, /* MA_CHANNEL_SIDE_LEFT */ + {+1.0f, 0.0f, 0.0f }, /* MA_CHANNEL_SIDE_RIGHT */ + { 0.0f, +1.0f, 0.0f }, /* MA_CHANNEL_TOP_CENTER */ + {-0.5774f, +0.5774f, -0.5774f }, /* MA_CHANNEL_TOP_FRONT_LEFT */ + { 0.0f, +0.7071f, -0.7071f }, /* MA_CHANNEL_TOP_FRONT_CENTER */ + {+0.5774f, +0.5774f, -0.5774f }, /* MA_CHANNEL_TOP_FRONT_RIGHT */ + {-0.5774f, +0.5774f, +0.5774f }, /* MA_CHANNEL_TOP_BACK_LEFT */ + { 0.0f, +0.7071f, +0.7071f }, /* MA_CHANNEL_TOP_BACK_CENTER */ + {+0.5774f, +0.5774f, +0.5774f }, /* MA_CHANNEL_TOP_BACK_RIGHT */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_0 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_1 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_2 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_3 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_4 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_5 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_6 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_7 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_8 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_9 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_10 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_11 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_12 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_13 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_14 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_15 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_16 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_17 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_18 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_19 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_20 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_21 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_22 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_23 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_24 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_25 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_26 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_27 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_28 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_29 */ + { 0.0f, 0.0f, -1.0f }, /* MA_CHANNEL_AUX_30 */ + { 0.0f, 0.0f, -1.0f } /* MA_CHANNEL_AUX_31 */ +}; + +static ma_vec3f ma_get_channel_direction(ma_channel channel) +{ + if (channel >= MA_CHANNEL_POSITION_COUNT) { + return ma_vec3f_init_3f(0, 0, -1); + } else { + return g_maChannelDirections[channel]; + } +} + + + +static float ma_attenuation_inverse(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return minDistance / (minDistance + rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance)); +} + +static float ma_attenuation_linear(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return 1 - rolloff * (ma_clamp(distance, minDistance, maxDistance) - minDistance) / (maxDistance - minDistance); +} + +static float ma_attenuation_exponential(float distance, float minDistance, float maxDistance, float rolloff) +{ + if (minDistance >= maxDistance) { + return 1; /* To avoid division by zero. Do not attenuate. */ + } + + return (float)ma_powd(ma_clamp(distance, minDistance, maxDistance) / minDistance, -rolloff); +} + + +/* +Dopper Effect calculation taken from the OpenAL spec, with two main differences: + + 1) The source to listener vector will have already been calcualted at an earlier step so we can + just use that directly. We need only the position of the source relative to the origin. + + 2) We don't scale by a frequency because we actually just want the ratio which we'll plug straight + into the resampler directly. +*/ +static float ma_doppler_pitch(ma_vec3f relativePosition, ma_vec3f sourceVelocity, ma_vec3f listenVelocity, float speedOfSound, float dopplerFactor) +{ + float len; + float vls; + float vss; + + len = ma_vec3f_len(relativePosition); + + /* + There's a case where the position of the source will be right on top of the listener in which + case the length will be 0 and we'll end up with a division by zero. We can just return a ratio + of 1.0 in this case. This is not considered in the OpenAL spec, but is necessary. + */ + if (len == 0) { + return 1.0; + } + + vls = ma_vec3f_dot(relativePosition, listenVelocity) / len; + vss = ma_vec3f_dot(relativePosition, sourceVelocity) / len; + + vls = ma_min(vls, speedOfSound / dopplerFactor); + vss = ma_min(vss, speedOfSound / dopplerFactor); + + return (speedOfSound - dopplerFactor*vls) / (speedOfSound - dopplerFactor*vss); +} + + +static void ma_get_default_channel_map_for_spatializer(ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channelCount) +{ + /* + Special case for stereo. Want to default the left and right speakers to side left and side + right so that they're facing directly down the X axis rather than slightly forward. Not + doing this will result in sounds being quieter when behind the listener. This might + actually be good for some scenerios, but I don't think it's an appropriate default because + it can be a bit unexpected. + */ + if (channelCount == 2) { + pChannelMap[0] = MA_CHANNEL_SIDE_LEFT; + pChannelMap[1] = MA_CHANNEL_SIDE_RIGHT; + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channelCount); + } +} + + +MA_API ma_spatializer_listener_config ma_spatializer_listener_config_init(ma_uint32 channelsOut) +{ + ma_spatializer_listener_config config; + + MA_ZERO_OBJECT(&config); + config.channelsOut = channelsOut; + config.pChannelMapOut = NULL; + config.handedness = ma_handedness_right; + config.worldUp = ma_vec3f_init_3f(0, 1, 0); + config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterGain = 0; + config.speedOfSound = 343.3f; /* Same as OpenAL. Used for doppler effect. */ + + return config; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapOutOffset; +} ma_spatializer_listener_heap_layout; + +static ma_result ma_spatializer_listener_get_heap_layout(const ma_spatializer_listener_config* pConfig, ma_spatializer_listener_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel map. We always need this, even for passthroughs. */ + pHeapLayout->channelMapOutOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(*pConfig->pChannelMapOut) * pConfig->channelsOut); + + return MA_SUCCESS; +} + + +MA_API ma_result ma_spatializer_listener_get_heap_size(const ma_spatializer_listener_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_spatializer_listener_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_spatializer_listener_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_listener_init_preallocated(const ma_spatializer_listener_config* pConfig, void* pHeap, ma_spatializer_listener* pListener) +{ + ma_result result; + ma_spatializer_listener_heap_layout heapLayout; + + if (pListener == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pListener); + + result = ma_spatializer_listener_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pListener->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pListener->config = *pConfig; + pListener->position = ma_vec3f_init_3f(0, 0, 0); + pListener->direction = ma_vec3f_init_3f(0, 0, -1); + pListener->velocity = ma_vec3f_init_3f(0, 0, 0); + pListener->isEnabled = MA_TRUE; + + /* Swap the forward direction if we're left handed (it was initialized based on right handed). */ + if (pListener->config.handedness == ma_handedness_left) { + pListener->direction = ma_vec3f_neg(pListener->direction); + } + + + /* We must always have a valid channel map. */ + pListener->config.pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset); + + /* Use a slightly different default channel map for stereo. */ + if (pConfig->pChannelMapOut == NULL) { + ma_get_default_channel_map_for_spatializer(pListener->config.pChannelMapOut, pConfig->channelsOut, pConfig->channelsOut); + } else { + ma_channel_map_copy_or_default(pListener->config.pChannelMapOut, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelsOut); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_listener_init(const ma_spatializer_listener_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer_listener* pListener) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_spatializer_listener_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_spatializer_listener_init_preallocated(pConfig, pHeap, pListener); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pListener->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_spatializer_listener_uninit(ma_spatializer_listener* pListener, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pListener == NULL) { + return; + } + + if (pListener->_ownsHeap) { + ma_free(pListener->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_channel* ma_spatializer_listener_get_channel_map(ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return NULL; + } + + return pListener->config.pChannelMapOut; +} + +MA_API void ma_spatializer_listener_set_cone(ma_spatializer_listener* pListener, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pListener == NULL) { + return; + } + + pListener->config.coneInnerAngleInRadians = innerAngleInRadians; + pListener->config.coneOuterAngleInRadians = outerAngleInRadians; + pListener->config.coneOuterGain = outerGain; +} + +MA_API void ma_spatializer_listener_get_cone(const ma_spatializer_listener* pListener, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pListener == NULL) { + return; + } + + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = pListener->config.coneInnerAngleInRadians; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = pListener->config.coneOuterAngleInRadians; + } + + if (pOuterGain != NULL) { + *pOuterGain = pListener->config.coneOuterGain; + } +} + +MA_API void ma_spatializer_listener_set_position(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->position = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_position(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pListener->position; +} + +MA_API void ma_spatializer_listener_set_direction(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->direction = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_direction(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return pListener->direction; +} + +MA_API void ma_spatializer_listener_set_velocity(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->velocity = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_velocity(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pListener->velocity; +} + +MA_API void ma_spatializer_listener_set_speed_of_sound(ma_spatializer_listener* pListener, float speedOfSound) +{ + if (pListener == NULL) { + return; + } + + pListener->config.speedOfSound = speedOfSound; +} + +MA_API float ma_spatializer_listener_get_speed_of_sound(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return 0; + } + + return pListener->config.speedOfSound; +} + +MA_API void ma_spatializer_listener_set_world_up(ma_spatializer_listener* pListener, float x, float y, float z) +{ + if (pListener == NULL) { + return; + } + + pListener->config.worldUp = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_listener_get_world_up(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return ma_vec3f_init_3f(0, 1, 0); + } + + return pListener->config.worldUp; +} + +MA_API void ma_spatializer_listener_set_enabled(ma_spatializer_listener* pListener, ma_bool32 isEnabled) +{ + if (pListener == NULL) { + return; + } + + pListener->isEnabled = isEnabled; +} + +MA_API ma_bool32 ma_spatializer_listener_is_enabled(const ma_spatializer_listener* pListener) +{ + if (pListener == NULL) { + return MA_FALSE; + } + + return pListener->isEnabled; +} + + + + +MA_API ma_spatializer_config ma_spatializer_config_init(ma_uint32 channelsIn, ma_uint32 channelsOut) +{ + ma_spatializer_config config; + + MA_ZERO_OBJECT(&config); + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; + config.pChannelMapIn = NULL; + config.attenuationModel = ma_attenuation_model_inverse; + config.positioning = ma_positioning_absolute; + config.handedness = ma_handedness_right; + config.minGain = 0; + config.maxGain = 1; + config.minDistance = 1; + config.maxDistance = MA_FLT_MAX; + config.rolloff = 1; + config.coneInnerAngleInRadians = 6.283185f; /* 360 degrees. */ + config.coneOuterAngleInRadians = 6.283185f; /* 360 degress. */ + config.coneOuterGain = 0.0f; + config.dopplerFactor = 1; + config.directionalAttenuationFactor = 1; + config.gainSmoothTimeInFrames = 360; /* 7.5ms @ 48K. */ + + return config; +} + + +static ma_gainer_config ma_spatializer_gainer_config_init(const ma_spatializer_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + return ma_gainer_config_init(pConfig->channelsOut, pConfig->gainSmoothTimeInFrames); +} + +static ma_result ma_spatializer_validate_config(const ma_spatializer_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapInOffset; + size_t newChannelGainsOffset; + size_t gainerOffset; +} ma_spatializer_heap_layout; + +static ma_result ma_spatializer_get_heap_layout(const ma_spatializer_config* pConfig, ma_spatializer_heap_layout* pHeapLayout) +{ + ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_spatializer_validate_config(pConfig); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel map. */ + pHeapLayout->channelMapInOffset = MA_SIZE_MAX; /* <-- MA_SIZE_MAX indicates no allocation necessary. */ + if (pConfig->pChannelMapIn != NULL) { + pHeapLayout->channelMapInOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(*pConfig->pChannelMapIn) * pConfig->channelsIn); + } + + /* New channel gains for output. */ + pHeapLayout->newChannelGainsOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(float) * pConfig->channelsOut); + + /* Gainer. */ + { + size_t gainerHeapSizeInBytes; + ma_gainer_config gainerConfig; + + gainerConfig = ma_spatializer_gainer_config_init(pConfig); + + result = ma_gainer_get_heap_size(&gainerConfig, &gainerHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->gainerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(gainerHeapSizeInBytes); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_get_heap_size(const ma_spatializer_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_spatializer_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; /* Safety. */ + + result = ma_spatializer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + + +MA_API ma_result ma_spatializer_init_preallocated(const ma_spatializer_config* pConfig, void* pHeap, ma_spatializer* pSpatializer) +{ + ma_result result; + ma_spatializer_heap_layout heapLayout; + ma_gainer_config gainerConfig; + + if (pSpatializer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSpatializer); + + if (pConfig == NULL || pHeap == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_spatializer_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pSpatializer->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pSpatializer->channelsIn = pConfig->channelsIn; + pSpatializer->channelsOut = pConfig->channelsOut; + pSpatializer->attenuationModel = pConfig->attenuationModel; + pSpatializer->positioning = pConfig->positioning; + pSpatializer->handedness = pConfig->handedness; + pSpatializer->minGain = pConfig->minGain; + pSpatializer->maxGain = pConfig->maxGain; + pSpatializer->minDistance = pConfig->minDistance; + pSpatializer->maxDistance = pConfig->maxDistance; + pSpatializer->rolloff = pConfig->rolloff; + pSpatializer->coneInnerAngleInRadians = pConfig->coneInnerAngleInRadians; + pSpatializer->coneOuterAngleInRadians = pConfig->coneOuterAngleInRadians; + pSpatializer->coneOuterGain = pConfig->coneOuterGain; + pSpatializer->dopplerFactor = pConfig->dopplerFactor; + pSpatializer->directionalAttenuationFactor = pConfig->directionalAttenuationFactor; + pSpatializer->gainSmoothTimeInFrames = pConfig->gainSmoothTimeInFrames; + pSpatializer->position = ma_vec3f_init_3f(0, 0, 0); + pSpatializer->direction = ma_vec3f_init_3f(0, 0, -1); + pSpatializer->velocity = ma_vec3f_init_3f(0, 0, 0); + pSpatializer->dopplerPitch = 1; + + /* Swap the forward direction if we're left handed (it was initialized based on right handed). */ + if (pSpatializer->handedness == ma_handedness_left) { + pSpatializer->direction = ma_vec3f_neg(pSpatializer->direction); + } + + /* Channel map. This will be on the heap. */ + if (pConfig->pChannelMapIn != NULL) { + pSpatializer->pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset); + ma_channel_map_copy_or_default(pSpatializer->pChannelMapIn, pSpatializer->channelsIn, pConfig->pChannelMapIn, pSpatializer->channelsIn); + } + + /* New channel gains for output channels. */ + pSpatializer->pNewChannelGainsOut = (float*)ma_offset_ptr(pHeap, heapLayout.newChannelGainsOffset); + + /* Gainer. */ + gainerConfig = ma_spatializer_gainer_config_init(pConfig); + + result = ma_gainer_init_preallocated(&gainerConfig, ma_offset_ptr(pHeap, heapLayout.gainerOffset), &pSpatializer->gainer); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the gainer. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_spatializer_init(const ma_spatializer_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_spatializer* pSpatializer) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + /* We'll need a heap allocation to retrieve the size. */ + result = ma_spatializer_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_spatializer_init_preallocated(pConfig, pHeap, pSpatializer); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pSpatializer->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_spatializer_uninit(ma_spatializer* pSpatializer, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pSpatializer == NULL) { + return; + } + + ma_gainer_uninit(&pSpatializer->gainer, pAllocationCallbacks); + + if (pSpatializer->_ownsHeap) { + ma_free(pSpatializer->_pHeap, pAllocationCallbacks); + } +} + +static float ma_calculate_angular_gain(ma_vec3f dirA, ma_vec3f dirB, float coneInnerAngleInRadians, float coneOuterAngleInRadians, float coneOuterGain) +{ + /* + Angular attenuation. + + Unlike distance gain, the math for this is not specified by the OpenAL spec so we'll just go ahead and figure + this out for ourselves at the expense of possibly being inconsistent with other implementations. + + To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We + just need to get the direction from the source to the listener and then do a dot product against that and the + direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer + angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. + */ + if (coneInnerAngleInRadians < 6.283185f) { + float angularGain = 1; + float cutoffInner = (float)ma_cosd(coneInnerAngleInRadians*0.5f); + float cutoffOuter = (float)ma_cosd(coneOuterAngleInRadians*0.5f); + float d; + + d = ma_vec3f_dot(dirA, dirB); + + if (d > cutoffInner) { + /* It's inside the inner angle. */ + angularGain = 1; + } else { + /* It's outside the inner angle. */ + if (d > cutoffOuter) { + /* It's between the inner and outer angle. We need to linearly interpolate between 1 and coneOuterGain. */ + angularGain = ma_mix_f32(coneOuterGain, 1, (d - cutoffOuter) / (cutoffInner - cutoffOuter)); + } else { + /* It's outside the outer angle. */ + angularGain = coneOuterGain; + } + } + + /*printf("d = %f; cutoffInner = %f; cutoffOuter = %f; angularGain = %f\n", d, cutoffInner, cutoffOuter, angularGain);*/ + return angularGain; + } else { + /* Inner angle is 360 degrees so no need to do any attenuation. */ + return 1; + } +} + +MA_API ma_result ma_spatializer_process_pcm_frames(ma_spatializer* pSpatializer, ma_spatializer_listener* pListener, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +{ + ma_channel* pChannelMapIn = pSpatializer->pChannelMapIn; + ma_channel* pChannelMapOut = pListener->config.pChannelMapOut; + + if (pSpatializer == NULL) { + return MA_INVALID_ARGS; + } + + /* If we're not spatializing we need to run an optimized path. */ + if (c89atomic_load_i32(&pSpatializer->attenuationModel) == ma_attenuation_model_none) { + if (ma_spatializer_listener_is_enabled(pListener)) { + /* No attenuation is required, but we'll need to do some channel conversion. */ + if (pSpatializer->channelsIn == pSpatializer->channelsOut) { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, pSpatializer->channelsIn); + } else { + ma_channel_map_apply_f32((float*)pFramesOut, pChannelMapOut, pSpatializer->channelsOut, (const float*)pFramesIn, pChannelMapIn, pSpatializer->channelsIn, frameCount, ma_channel_mix_mode_rectangular, ma_mono_expansion_mode_default); /* Safe casts to float* because f32 is the only supported format. */ + } + } else { + /* The listener is disabled. Output silence. */ + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, pSpatializer->channelsOut); + } + + /* + We're not doing attenuation so don't bother with doppler for now. I'm not sure if this is + the correct thinking so might need to review this later. + */ + pSpatializer->dopplerPitch = 1; + } else { + /* + Let's first determine which listener the sound is closest to. Need to keep in mind that we + might not have a world or any listeners, in which case we just spatializer based on the + listener being positioned at the origin (0, 0, 0). + */ + ma_vec3f relativePosNormalized; + ma_vec3f relativePos; /* The position relative to the listener. */ + ma_vec3f relativeDir; /* The direction of the sound, relative to the listener. */ + ma_vec3f listenerVel; /* The volocity of the listener. For doppler pitch calculation. */ + float speedOfSound; + float distance = 0; + float gain = 1; + ma_uint32 iChannel; + const ma_uint32 channelsOut = pSpatializer->channelsOut; + const ma_uint32 channelsIn = pSpatializer->channelsIn; + float minDistance = ma_spatializer_get_min_distance(pSpatializer); + float maxDistance = ma_spatializer_get_max_distance(pSpatializer); + float rolloff = ma_spatializer_get_rolloff(pSpatializer); + float dopplerFactor = ma_spatializer_get_doppler_factor(pSpatializer); + + /* + We'll need the listener velocity for doppler pitch calculations. The speed of sound is + defined by the listener, so we'll grab that here too. + */ + if (pListener != NULL) { + listenerVel = pListener->velocity; + speedOfSound = pListener->config.speedOfSound; + } else { + listenerVel = ma_vec3f_init_3f(0, 0, 0); + speedOfSound = MA_DEFAULT_SPEED_OF_SOUND; + } + + if (pListener == NULL || ma_spatializer_get_positioning(pSpatializer) == ma_positioning_relative) { + /* There's no listener or we're using relative positioning. */ + relativePos = pSpatializer->position; + relativeDir = pSpatializer->direction; + } else { + /* + We've found a listener and we're using absolute positioning. We need to transform the + sound's position and direction so that it's relative to listener. Later on we'll use + this for determining the factors to apply to each channel to apply the panning effect. + */ + ma_spatializer_get_relative_position_and_direction(pSpatializer, pListener, &relativePos, &relativeDir); + } + + distance = ma_vec3f_len(relativePos); + + /* We've gathered the data, so now we can apply some spatialization. */ + switch (ma_spatializer_get_attenuation_model(pSpatializer)) { + case ma_attenuation_model_inverse: + { + gain = ma_attenuation_inverse(distance, minDistance, maxDistance, rolloff); + } break; + case ma_attenuation_model_linear: + { + gain = ma_attenuation_linear(distance, minDistance, maxDistance, rolloff); + } break; + case ma_attenuation_model_exponential: + { + gain = ma_attenuation_exponential(distance, minDistance, maxDistance, rolloff); + } break; + case ma_attenuation_model_none: + default: + { + gain = 1; + } break; + } + + /* Normalize the position. */ + if (distance > 0.001f) { + float distanceInv = 1/distance; + relativePosNormalized = relativePos; + relativePosNormalized.x *= distanceInv; + relativePosNormalized.y *= distanceInv; + relativePosNormalized.z *= distanceInv; + } else { + distance = 0; + relativePosNormalized = ma_vec3f_init_3f(0, 0, 0); + } + + /* + Angular attenuation. + + Unlike distance gain, the math for this is not specified by the OpenAL spec so we'll just go ahead and figure + this out for ourselves at the expense of possibly being inconsistent with other implementations. + + To do cone attenuation, I'm just using the same math that we'd use to implement a basic spotlight in OpenGL. We + just need to get the direction from the source to the listener and then do a dot product against that and the + direction of the spotlight. Then we just compare that dot product against the cosine of the inner and outer + angles. If the dot product is greater than the the outer angle, we just use coneOuterGain. If it's less than + the inner angle, we just use a gain of 1. Otherwise we linearly interpolate between 1 and coneOuterGain. + */ + if (distance > 0) { + /* Source anglular gain. */ + float spatializerConeInnerAngle; + float spatializerConeOuterAngle; + float spatializerConeOuterGain; + ma_spatializer_get_cone(pSpatializer, &spatializerConeInnerAngle, &spatializerConeOuterAngle, &spatializerConeOuterGain); + + gain *= ma_calculate_angular_gain(relativeDir, ma_vec3f_neg(relativePosNormalized), spatializerConeInnerAngle, spatializerConeOuterAngle, spatializerConeOuterGain); + + /* + We're supporting angular gain on the listener as well for those who want to reduce the volume of sounds that + are positioned behind the listener. On default settings, this will have no effect. + */ + if (pListener != NULL && pListener->config.coneInnerAngleInRadians < 6.283185f) { + ma_vec3f listenerDirection; + float listenerInnerAngle; + float listenerOuterAngle; + float listenerOuterGain; + + if (pListener->config.handedness == ma_handedness_right) { + listenerDirection = ma_vec3f_init_3f(0, 0, -1); + } else { + listenerDirection = ma_vec3f_init_3f(0, 0, +1); + } + + listenerInnerAngle = pListener->config.coneInnerAngleInRadians; + listenerOuterAngle = pListener->config.coneOuterAngleInRadians; + listenerOuterGain = pListener->config.coneOuterGain; + + gain *= ma_calculate_angular_gain(listenerDirection, relativePosNormalized, listenerInnerAngle, listenerOuterAngle, listenerOuterGain); + } + } else { + /* The sound is right on top of the listener. Don't do any angular attenuation. */ + } + + + /* Clamp the gain. */ + gain = ma_clamp(gain, ma_spatializer_get_min_gain(pSpatializer), ma_spatializer_get_max_gain(pSpatializer)); + + /* + Panning. This is where we'll apply the gain and convert to the output channel count. We have an optimized path for + when we're converting to a mono stream. In that case we don't really need to do any panning - we just apply the + gain to the final output. + */ + /*printf("distance=%f; gain=%f\n", distance, gain);*/ + + /* We must have a valid channel map here to ensure we spatialize properly. */ + MA_ASSERT(pChannelMapOut != NULL); + + /* + We're not converting to mono so we'll want to apply some panning. This is where the feeling of something being + to the left, right, infront or behind the listener is calculated. I'm just using a basic model here. Note that + the code below is not based on any specific algorithm. I'm just implementing this off the top of my head and + seeing how it goes. There might be better ways to do this. + + To determine the direction of the sound relative to a speaker I'm using dot products. Each speaker is given a + direction. For example, the left channel in a stereo system will be -1 on the X axis and the right channel will + be +1 on the X axis. A dot product is performed against the direction vector of the channel and the normalized + position of the sound. + */ + for (iChannel = 0; iChannel < channelsOut; iChannel += 1) { + pSpatializer->pNewChannelGainsOut[iChannel] = gain; + } + + /* + Convert to our output channel count. If the listener is disabled we just output silence here. We cannot ignore + the whole section of code here because we need to update some internal spatialization state. + */ + if (ma_spatializer_listener_is_enabled(pListener)) { + ma_channel_map_apply_f32((float*)pFramesOut, pChannelMapOut, channelsOut, (const float*)pFramesIn, pChannelMapIn, channelsIn, frameCount, ma_channel_mix_mode_rectangular, ma_mono_expansion_mode_default); + } else { + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, pSpatializer->channelsOut); + } + + /* + Calculate our per-channel gains. We do this based on the normalized relative position of the sound and it's + relation to the direction of the channel. + */ + if (distance > 0) { + ma_vec3f unitPos = relativePos; + float distanceInv = 1/distance; + unitPos.x *= distanceInv; + unitPos.y *= distanceInv; + unitPos.z *= distanceInv; + + for (iChannel = 0; iChannel < channelsOut; iChannel += 1) { + ma_channel channelOut; + float d; + float dMin; + + channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannel); + if (ma_is_spatial_channel_position(channelOut)) { + d = ma_mix_f32_fast(1, ma_vec3f_dot(unitPos, ma_get_channel_direction(channelOut)), ma_spatializer_get_directional_attenuation_factor(pSpatializer)); + } else { + d = 1; /* It's not a spatial channel so there's no real notion of direction. */ + } + + /* + In my testing, if the panning effect is too aggressive it makes spatialization feel uncomfortable. + The "dMin" variable below is used to control the aggressiveness of the panning effect. When set to + 0, panning will be most extreme and any sounds that are positioned on the opposite side of the + speaker will be completely silent from that speaker. Not only does this feel uncomfortable, it + doesn't even remotely represent the real world at all because sounds that come from your right side + are still clearly audible from your left side. Setting "dMin" to 1 will result in no panning at + all, which is also not ideal. By setting it to something greater than 0, the spatialization effect + becomes much less dramatic and a lot more bearable. + + Summary: 0 = more extreme panning; 1 = no panning. + */ + dMin = 0.2f; /* TODO: Consider making this configurable. */ + + /* + At this point, "d" will be positive if the sound is on the same side as the channel and negative if + it's on the opposite side. It will be in the range of -1..1. There's two ways I can think of to + calculate a panning value. The first is to simply convert it to 0..1, however this has a problem + which I'm not entirely happy with. Considering a stereo system, when a sound is positioned right + in front of the listener it'll result in each speaker getting a gain of 0.5. I don't know if I like + the idea of having a scaling factor of 0.5 being applied to a sound when it's sitting right in front + of the listener. I would intuitively expect that to be played at full volume, or close to it. + + The second idea I think of is to only apply a reduction in gain when the sound is on the opposite + side of the speaker. That is, reduce the gain only when the dot product is negative. The problem + with this is that there will not be any attenuation as the sound sweeps around the 180 degrees + where the dot product is positive. The idea with this option is that you leave the gain at 1 when + the sound is being played on the same side as the speaker and then you just reduce the volume when + the sound is on the other side. + + The summarize, I think the first option should give a better sense of spatialization, but the second + option is better for preserving the sound's power. + + UPDATE: In my testing, I find the first option to sound better. You can feel the sense of space a + bit better, but you can also hear the reduction in volume when it's right in front. + */ + #if 1 + { + /* + Scale the dot product from -1..1 to 0..1. Will result in a sound directly in front losing power + by being played at 0.5 gain. + */ + d = (d + 1) * 0.5f; /* -1..1 to 0..1 */ + d = ma_max(d, dMin); + pSpatializer->pNewChannelGainsOut[iChannel] *= d; + } + #else + { + /* + Only reduce the volume of the sound if it's on the opposite side. This path keeps the volume more + consistent, but comes at the expense of a worse sense of space and positioning. + */ + if (d < 0) { + d += 1; /* Move into the positive range. */ + d = ma_max(d, dMin); + channelGainsOut[iChannel] *= d; + } + } + #endif + } + } else { + /* Assume the sound is right on top of us. Don't do any panning. */ + } + + /* Now we need to apply the volume to each channel. This needs to run through the gainer to ensure we get a smooth volume transition. */ + ma_gainer_set_gains(&pSpatializer->gainer, pSpatializer->pNewChannelGainsOut); + ma_gainer_process_pcm_frames(&pSpatializer->gainer, pFramesOut, pFramesOut, frameCount); + + /* + Before leaving we'll want to update our doppler pitch so that the caller can apply some + pitch shifting if they desire. Note that we need to negate the relative position here + because the doppler calculation needs to be source-to-listener, but ours is listener-to- + source. + */ + if (dopplerFactor > 0) { + pSpatializer->dopplerPitch = ma_doppler_pitch(ma_vec3f_sub(pListener->position, pSpatializer->position), pSpatializer->velocity, listenerVel, speedOfSound, dopplerFactor); + } else { + pSpatializer->dopplerPitch = 1; + } + } + + return MA_SUCCESS; +} + +MA_API ma_uint32 ma_spatializer_get_input_channels(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->channelsIn; +} + +MA_API ma_uint32 ma_spatializer_get_output_channels(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return pSpatializer->channelsOut; +} + +MA_API void ma_spatializer_set_attenuation_model(ma_spatializer* pSpatializer, ma_attenuation_model attenuationModel) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_i32(&pSpatializer->attenuationModel, attenuationModel); +} + +MA_API ma_attenuation_model ma_spatializer_get_attenuation_model(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_attenuation_model_none; + } + + return (ma_attenuation_model)c89atomic_load_i32(&pSpatializer->attenuationModel); +} + +MA_API void ma_spatializer_set_positioning(ma_spatializer* pSpatializer, ma_positioning positioning) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_i32(&pSpatializer->positioning, positioning); +} + +MA_API ma_positioning ma_spatializer_get_positioning(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_positioning_absolute; + } + + return (ma_positioning)c89atomic_load_i32(&pSpatializer->positioning); +} + +MA_API void ma_spatializer_set_rolloff(ma_spatializer* pSpatializer, float rolloff) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->rolloff, rolloff); +} + +MA_API float ma_spatializer_get_rolloff(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->rolloff); +} + +MA_API void ma_spatializer_set_min_gain(ma_spatializer* pSpatializer, float minGain) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->minGain, minGain); +} + +MA_API float ma_spatializer_get_min_gain(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->minGain); +} + +MA_API void ma_spatializer_set_max_gain(ma_spatializer* pSpatializer, float maxGain) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->maxGain, maxGain); +} + +MA_API float ma_spatializer_get_max_gain(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->maxGain); +} + +MA_API void ma_spatializer_set_min_distance(ma_spatializer* pSpatializer, float minDistance) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->minDistance, minDistance); +} + +MA_API float ma_spatializer_get_min_distance(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->minDistance); +} + +MA_API void ma_spatializer_set_max_distance(ma_spatializer* pSpatializer, float maxDistance) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->maxDistance, maxDistance); +} + +MA_API float ma_spatializer_get_max_distance(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSpatializer->maxDistance); +} + +MA_API void ma_spatializer_set_cone(ma_spatializer* pSpatializer, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->coneInnerAngleInRadians, innerAngleInRadians); + c89atomic_exchange_f32(&pSpatializer->coneOuterAngleInRadians, outerAngleInRadians); + c89atomic_exchange_f32(&pSpatializer->coneOuterGain, outerGain); +} + +MA_API void ma_spatializer_get_cone(const ma_spatializer* pSpatializer, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pSpatializer == NULL) { + return; + } + + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = c89atomic_load_f32(&pSpatializer->coneInnerAngleInRadians); + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = c89atomic_load_f32(&pSpatializer->coneOuterAngleInRadians); + } + + if (pOuterGain != NULL) { + *pOuterGain = c89atomic_load_f32(&pSpatializer->coneOuterGain); + } +} + +MA_API void ma_spatializer_set_doppler_factor(ma_spatializer* pSpatializer, float dopplerFactor) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->dopplerFactor, dopplerFactor); +} + +MA_API float ma_spatializer_get_doppler_factor(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 1; + } + + return c89atomic_load_f32(&pSpatializer->dopplerFactor); +} + +MA_API void ma_spatializer_set_directional_attenuation_factor(ma_spatializer* pSpatializer, float directionalAttenuationFactor) +{ + if (pSpatializer == NULL) { + return; + } + + c89atomic_exchange_f32(&pSpatializer->directionalAttenuationFactor, directionalAttenuationFactor); +} + +MA_API float ma_spatializer_get_directional_attenuation_factor(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return 1; + } + + return c89atomic_load_f32(&pSpatializer->directionalAttenuationFactor); +} + +MA_API void ma_spatializer_set_position(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->position = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_position(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pSpatializer->position; +} + +MA_API void ma_spatializer_set_direction(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->direction = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_direction(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return pSpatializer->direction; +} + +MA_API void ma_spatializer_set_velocity(ma_spatializer* pSpatializer, float x, float y, float z) +{ + if (pSpatializer == NULL) { + return; + } + + pSpatializer->velocity = ma_vec3f_init_3f(x, y, z); +} + +MA_API ma_vec3f ma_spatializer_get_velocity(const ma_spatializer* pSpatializer) +{ + if (pSpatializer == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return pSpatializer->velocity; +} + +MA_API void ma_spatializer_get_relative_position_and_direction(const ma_spatializer* pSpatializer, const ma_spatializer_listener* pListener, ma_vec3f* pRelativePos, ma_vec3f* pRelativeDir) +{ + if (pRelativePos != NULL) { + pRelativePos->x = 0; + pRelativePos->y = 0; + pRelativePos->z = 0; + } + + if (pRelativeDir != NULL) { + pRelativeDir->x = 0; + pRelativeDir->y = 0; + pRelativeDir->z = -1; + } + + if (pSpatializer == NULL) { + return; + } + + if (pListener == NULL || ma_spatializer_get_positioning(pSpatializer) == ma_positioning_relative) { + /* There's no listener or we're using relative positioning. */ + if (pRelativePos != NULL) { + *pRelativePos = pSpatializer->position; + } + if (pRelativeDir != NULL) { + *pRelativeDir = pSpatializer->direction; + } + } else { + ma_vec3f v; + ma_vec3f axisX; + ma_vec3f axisY; + ma_vec3f axisZ; + float m[4][4]; + + /* + We need to calcualte the right vector from our forward and up vectors. This is done with + a cross product. + */ + axisZ = ma_vec3f_normalize(pListener->direction); /* Normalization required here because we can't trust the caller. */ + axisX = ma_vec3f_normalize(ma_vec3f_cross(axisZ, pListener->config.worldUp)); /* Normalization required here because the world up vector may not be perpendicular with the forward vector. */ + + /* + The calculation of axisX above can result in a zero-length vector if the listener is + looking straight up on the Y axis. We'll need to fall back to a +X in this case so that + the calculations below don't fall apart. This is where a quaternion based listener and + sound orientation would come in handy. + */ + if (ma_vec3f_len2(axisX) == 0) { + axisX = ma_vec3f_init_3f(1, 0, 0); + } + + axisY = ma_vec3f_cross(axisX, axisZ); /* No normalization is required here because axisX and axisZ are unit length and perpendicular. */ + + /* + We need to swap the X axis if we're left handed because otherwise the cross product above + will have resulted in it pointing in the wrong direction (right handed was assumed in the + cross products above). + */ + if (pListener->config.handedness == ma_handedness_left) { + axisX = ma_vec3f_neg(axisX); + } + + /* Lookat. */ + m[0][0] = axisX.x; m[1][0] = axisX.y; m[2][0] = axisX.z; m[3][0] = -ma_vec3f_dot(axisX, pListener->position); + m[0][1] = axisY.x; m[1][1] = axisY.y; m[2][1] = axisY.z; m[3][1] = -ma_vec3f_dot(axisY, pListener->position); + m[0][2] = -axisZ.x; m[1][2] = -axisZ.y; m[2][2] = -axisZ.z; m[3][2] = -ma_vec3f_dot(ma_vec3f_neg(axisZ), pListener->position); + m[0][3] = 0; m[1][3] = 0; m[2][3] = 0; m[3][3] = 1; + + /* + Multiply the lookat matrix by the spatializer position to transform it to listener + space. This allows calculations to work based on the sound being relative to the + origin which makes things simpler. + */ + if (pRelativePos != NULL) { + v = pSpatializer->position; + pRelativePos->x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z + m[3][0] * 1; + pRelativePos->y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z + m[3][1] * 1; + pRelativePos->z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z + m[3][2] * 1; + } + + /* + The direction of the sound needs to also be transformed so that it's relative to the + rotation of the listener. + */ + if (pRelativeDir != NULL) { + v = pSpatializer->direction; + pRelativeDir->x = m[0][0] * v.x + m[1][0] * v.y + m[2][0] * v.z; + pRelativeDir->y = m[0][1] * v.x + m[1][1] * v.y + m[2][1] * v.z; + pRelativeDir->z = m[0][2] * v.x + m[1][2] * v.y + m[2][2] * v.z; + } + } +} + + + + /************************************************************************************************************************************************************** Resampling @@ -38994,6 +49032,16 @@ MA_API ma_linear_resampler_config ma_linear_resampler_config_init(ma_format form return config; } + +typedef struct +{ + size_t sizeInBytes; + size_t x0Offset; + size_t x1Offset; + size_t lpfOffset; +} ma_linear_resampler_heap_layout; + + static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* pResampler, ma_uint32 oldSampleRateOut, ma_uint32 newSampleRateOut) { /* @@ -39012,7 +49060,7 @@ static void ma_linear_resampler_adjust_timer_for_new_rate(ma_linear_resampler* p pResampler->inTimeFrac = pResampler->inTimeFrac % pResampler->config.sampleRateOut; } -static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) +static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pResampler, void* pHeap, ma_linear_resampler_heap_layout* pHeapLayout, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_bool32 isResamplerAlreadyInitialized) { ma_result result; ma_uint32 gcf; @@ -39056,7 +49104,7 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes if (isResamplerAlreadyInitialized) { result = ma_lpf_reinit(&lpfConfig, &pResampler->lpf); } else { - result = ma_lpf_init(&lpfConfig, &pResampler->lpf); + result = ma_lpf_init_preallocated(&lpfConfig, ma_offset_ptr(pHeap, pHeapLayout->lpfOffset), &pResampler->lpf); } if (result != MA_SUCCESS) { @@ -39073,9 +49121,88 @@ static ma_result ma_linear_resampler_set_rate_internal(ma_linear_resampler* pRes return MA_SUCCESS; } -MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, ma_linear_resampler* pResampler) +static ma_result ma_linear_resampler_get_heap_layout(const ma_linear_resampler_config* pConfig, ma_linear_resampler_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* x0 */ + pHeapLayout->x0Offset = pHeapLayout->sizeInBytes; + if (pConfig->format == ma_format_f32) { + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + } else { + pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + } + + /* x1 */ + pHeapLayout->x1Offset = pHeapLayout->sizeInBytes; + if (pConfig->format == ma_format_f32) { + pHeapLayout->sizeInBytes += sizeof(float) * pConfig->channels; + } else { + pHeapLayout->sizeInBytes += sizeof(ma_int16) * pConfig->channels; + } + + /* LPF */ + pHeapLayout->lpfOffset = pHeapLayout->sizeInBytes; + { + ma_result result; + size_t lpfHeapSizeInBytes; + ma_lpf_config lpfConfig = ma_lpf_config_init(pConfig->format, pConfig->channels, 1, 1, pConfig->lpfOrder); /* Sample rate and cutoff frequency do not matter. */ + + result = ma_lpf_get_heap_size(&lpfConfig, &lpfHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += lpfHeapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_get_heap_size(const ma_linear_resampler_config* pConfig, size_t* pHeapSizeInBytes) { ma_result result; + ma_linear_resampler_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_linear_resampler_init_preallocated(const ma_linear_resampler_config* pConfig, void* pHeap, ma_linear_resampler* pResampler) +{ + ma_result result; + ma_linear_resampler_heap_layout heapLayout; if (pResampler == NULL) { return MA_INVALID_ARGS; @@ -39083,18 +49210,26 @@ MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pCon MA_ZERO_OBJECT(pResampler); - if (pConfig == NULL) { - return MA_INVALID_ARGS; - } - - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; + result = ma_linear_resampler_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } pResampler->config = *pConfig; + pResampler->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + if (pConfig->format == ma_format_f32) { + pResampler->x0.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x0Offset); + pResampler->x1.f32 = (float*)ma_offset_ptr(pHeap, heapLayout.x1Offset); + } else { + pResampler->x0.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x0Offset); + pResampler->x1.s16 = (ma_int16*)ma_offset_ptr(pHeap, heapLayout.x1Offset); + } + /* Setting the rate will set up the filter and time advances for us. */ - result = ma_linear_resampler_set_rate_internal(pResampler, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); + result = ma_linear_resampler_set_rate_internal(pResampler, pHeap, &heapLayout, pConfig->sampleRateIn, pConfig->sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_FALSE); if (result != MA_SUCCESS) { return result; } @@ -39105,11 +49240,47 @@ MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pCon return MA_SUCCESS; } -MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler) +MA_API ma_result ma_linear_resampler_init(const ma_linear_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_linear_resampler* pResampler) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_linear_resampler_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_linear_resampler_init_preallocated(pConfig, pHeap, pResampler); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pResampler->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks) { if (pResampler == NULL) { return; } + + ma_lpf_uninit(&pResampler->lpf, pAllocationCallbacks); + + if (pResampler->_ownsHeap) { + ma_free(pResampler->_pHeap, pAllocationCallbacks); + } } static MA_INLINE ma_int16 ma_linear_resampler_mix_s16(ma_int16 x, ma_int16 y, ma_int32 a, const ma_int32 shift) @@ -39139,7 +49310,7 @@ static void ma_linear_resampler_interpolate_frame_s16(ma_linear_resampler* pResa a = (pResampler->inTimeFrac << shift) / pResampler->config.sampleRateOut; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { ma_int16 s = ma_linear_resampler_mix_s16(pResampler->x0.s16[c], pResampler->x1.s16[c], a, shift); pFrameOut[c] = s; @@ -39158,7 +49329,7 @@ static void ma_linear_resampler_interpolate_frame_f32(ma_linear_resampler* pResa a = (float)pResampler->inTimeFrac / pResampler->config.sampleRateOut; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); for (c = 0; c < channels; c += 1) { float s = ma_mix_f32_fast(pResampler->x0.f32[c], pResampler->x1.f32[c], a); pFrameOut[c] = s; @@ -39505,7 +49676,7 @@ MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pRe MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { - return ma_linear_resampler_set_rate_internal(pResampler, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); + return ma_linear_resampler_set_rate_internal(pResampler, NULL, NULL, sampleRateIn, sampleRateOut, /* isResamplerAlreadyInitialized = */ MA_TRUE); } MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut) @@ -39513,6 +49684,14 @@ MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResamp ma_uint32 n; ma_uint32 d; + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (ratioInOut <= 0) { + return MA_INVALID_ARGS; + } + d = 1000; n = (ma_uint32)(ratioInOut * d); @@ -39525,19 +49704,42 @@ MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResamp return ma_linear_resampler_set_rate(pResampler, n, d); } - -MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount) +MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler) { - ma_uint64 inputFrameCount; - if (pResampler == NULL) { return 0; } - if (outputFrameCount == 0) { + return 1 + ma_lpf_get_latency(&pResampler->lpf); +} + +MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) +{ + if (pResampler == NULL) { return 0; } + return ma_linear_resampler_get_input_latency(pResampler) * pResampler->config.sampleRateOut / pResampler->config.sampleRateIn; +} + +MA_API ma_result ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + ma_uint64 inputFrameCount; + + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (outputFrameCount == 0) { + return MA_SUCCESS; + } + /* Any whole input frames are consumed before the first output frame is generated. */ inputFrameCount = pResampler->inTimeInt; outputFrameCount -= 1; @@ -39546,17 +49748,25 @@ MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_lin inputFrameCount += outputFrameCount * pResampler->inAdvanceInt; inputFrameCount += (pResampler->inTimeFrac + (outputFrameCount * pResampler->inAdvanceFrac)) / pResampler->config.sampleRateOut; - return inputFrameCount; + *pInputFrameCount = inputFrameCount; + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount) +MA_API ma_result ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) { ma_uint64 outputFrameCount; ma_uint64 preliminaryInputFrameCountFromFrac; ma_uint64 preliminaryInputFrameCount; + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + if (pResampler == NULL) { - return 0; + return MA_INVALID_ARGS; } /* @@ -39583,45 +49793,157 @@ MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_li outputFrameCount += 1; } - return outputFrameCount; + *pOutputFrameCount = outputFrameCount; + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler) +MA_API ma_result ma_linear_resampler_reset(ma_linear_resampler* pResampler) { + ma_uint32 iChannel; + if (pResampler == NULL) { - return 0; + return MA_INVALID_ARGS; } - return 1 + ma_lpf_get_latency(&pResampler->lpf); + /* Timers need to be cleared back to zero. */ + pResampler->inTimeInt = 1; /* Set this to one to force an input sample to always be loaded for the first output frame. */ + pResampler->inTimeFrac = 0; + + /* Cached samples need to be cleared. */ + if (pResampler->config.format == ma_format_f32) { + for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { + pResampler->x0.f32[iChannel] = 0; + pResampler->x1.f32[iChannel] = 0; + } + } else { + for (iChannel = 0; iChannel < pResampler->config.channels; iChannel += 1) { + pResampler->x0.s16[iChannel] = 0; + pResampler->x1.s16[iChannel] = 0; + } + } + + /* The low pass filter needs to have it's cache reset. */ + ma_lpf_clear_cache(&pResampler->lpf); + + return MA_SUCCESS; } -MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) + + +/* Linear resampler backend vtable. */ +static ma_linear_resampler_config ma_resampling_backend_get_config__linear(const ma_resampler_config* pConfig) { - if (pResampler == NULL) { - return 0; - } + ma_linear_resampler_config linearConfig; - return ma_linear_resampler_get_input_latency(pResampler) * pResampler->config.sampleRateOut / pResampler->config.sampleRateIn; + linearConfig = ma_linear_resampler_config_init(pConfig->format, pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut); + linearConfig.lpfOrder = pConfig->linear.lpfOrder; + + return linearConfig; } - -#if defined(ma_speex_resampler_h) -#define MA_HAS_SPEEX_RESAMPLER - -static ma_result ma_result_from_speex_err(int err) +static ma_result ma_resampling_backend_get_heap_size__linear(void* pUserData, const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes) { - switch (err) - { - case RESAMPLER_ERR_SUCCESS: return MA_SUCCESS; - case RESAMPLER_ERR_ALLOC_FAILED: return MA_OUT_OF_MEMORY; - case RESAMPLER_ERR_BAD_STATE: return MA_ERROR; - case RESAMPLER_ERR_INVALID_ARG: return MA_INVALID_ARGS; - case RESAMPLER_ERR_PTR_OVERLAP: return MA_INVALID_ARGS; - case RESAMPLER_ERR_OVERFLOW: return MA_ERROR; - default: return MA_ERROR; - } + ma_linear_resampler_config linearConfig; + + (void)pUserData; + + linearConfig = ma_resampling_backend_get_config__linear(pConfig); + + return ma_linear_resampler_get_heap_size(&linearConfig, pHeapSizeInBytes); } -#endif /* ma_speex_resampler_h */ + +static ma_result ma_resampling_backend_init__linear(void* pUserData, const ma_resampler_config* pConfig, void* pHeap, ma_resampling_backend** ppBackend) +{ + ma_resampler* pResampler = (ma_resampler*)pUserData; + ma_result result; + ma_linear_resampler_config linearConfig; + + (void)pUserData; + + linearConfig = ma_resampling_backend_get_config__linear(pConfig); + + result = ma_linear_resampler_init_preallocated(&linearConfig, pHeap, &pResampler->state.linear); + if (result != MA_SUCCESS) { + return result; + } + + *ppBackend = &pResampler->state.linear; + + return MA_SUCCESS; +} + +static void ma_resampling_backend_uninit__linear(void* pUserData, ma_resampling_backend* pBackend, const ma_allocation_callbacks* pAllocationCallbacks) +{ + (void)pUserData; + + ma_linear_resampler_uninit((ma_linear_resampler*)pBackend, pAllocationCallbacks); +} + +static ma_result ma_resampling_backend_process__linear(void* pUserData, ma_resampling_backend* pBackend, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) +{ + (void)pUserData; + + return ma_linear_resampler_process_pcm_frames((ma_linear_resampler*)pBackend, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); +} + +static ma_result ma_resampling_backend_set_rate__linear(void* pUserData, ma_resampling_backend* pBackend, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) +{ + (void)pUserData; + + return ma_linear_resampler_set_rate((ma_linear_resampler*)pBackend, sampleRateIn, sampleRateOut); +} + +static ma_uint64 ma_resampling_backend_get_input_latency__linear(void* pUserData, const ma_resampling_backend* pBackend) +{ + (void)pUserData; + + return ma_linear_resampler_get_input_latency((const ma_linear_resampler*)pBackend); +} + +static ma_uint64 ma_resampling_backend_get_output_latency__linear(void* pUserData, const ma_resampling_backend* pBackend) +{ + (void)pUserData; + + return ma_linear_resampler_get_output_latency((const ma_linear_resampler*)pBackend); +} + +static ma_result ma_resampling_backend_get_required_input_frame_count__linear(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + (void)pUserData; + + return ma_linear_resampler_get_required_input_frame_count((const ma_linear_resampler*)pBackend, outputFrameCount, pInputFrameCount); +} + +static ma_result ma_resampling_backend_get_expected_output_frame_count__linear(void* pUserData, const ma_resampling_backend* pBackend, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + (void)pUserData; + + return ma_linear_resampler_get_expected_output_frame_count((const ma_linear_resampler*)pBackend, inputFrameCount, pOutputFrameCount); +} + +static ma_result ma_resampling_backend_reset__linear(void* pUserData, ma_resampling_backend* pBackend) +{ + (void)pUserData; + + return ma_linear_resampler_reset((ma_linear_resampler*)pBackend); +} + +static ma_resampling_backend_vtable g_ma_linear_resampler_vtable = +{ + ma_resampling_backend_get_heap_size__linear, + ma_resampling_backend_init__linear, + ma_resampling_backend_uninit__linear, + ma_resampling_backend_process__linear, + ma_resampling_backend_set_rate__linear, + ma_resampling_backend_get_input_latency__linear, + ma_resampling_backend_get_output_latency__linear, + ma_resampling_backend_get_required_input_frame_count__linear, + ma_resampling_backend_get_expected_output_frame_count__linear, + ma_resampling_backend_reset__linear +}; + + MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut, ma_resample_algorithm algorithm) { @@ -39636,15 +49958,74 @@ MA_API ma_resampler_config ma_resampler_config_init(ma_format format, ma_uint32 /* Linear. */ config.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.linear.lpfNyquistFactor = 1; - - /* Speex. */ - config.speex.quality = 3; /* Cannot leave this as 0 as that is actually a valid value for Speex resampling quality. */ return config; } -MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resampler* pResampler) +static ma_result ma_resampler_get_vtable(const ma_resampler_config* pConfig, ma_resampler* pResampler, ma_resampling_backend_vtable** ppVTable, void** ppUserData) +{ + MA_ASSERT(pConfig != NULL); + MA_ASSERT(ppVTable != NULL); + MA_ASSERT(ppUserData != NULL); + + /* Safety. */ + *ppVTable = NULL; + *ppUserData = NULL; + + switch (pConfig->algorithm) + { + case ma_resample_algorithm_linear: + { + *ppVTable = &g_ma_linear_resampler_vtable; + *ppUserData = pResampler; + } break; + + case ma_resample_algorithm_custom: + { + *ppVTable = pConfig->pBackendVTable; + *ppUserData = pConfig->pBackendUserData; + } break; + + default: return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resampler_get_heap_size(const ma_resampler_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_resampling_backend_vtable* pVTable; + void* pVTableUserData; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_resampler_get_vtable(pConfig, NULL, &pVTable, &pVTableUserData); + if (result != MA_SUCCESS) { + return result; + } + + if (pVTable == NULL || pVTable->onGetHeapSize == NULL) { + return MA_NOT_IMPLEMENTED; + } + + result = pVTable->onGetHeapSize(pVTableUserData, pConfig, pHeapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resampler_init_preallocated(const ma_resampler_config* pConfig, void* pHeap, ma_resampler* pResampler) { ma_result result; @@ -39658,291 +50039,75 @@ MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, ma_resamp return MA_INVALID_ARGS; } - if (pConfig->format != ma_format_f32 && pConfig->format != ma_format_s16) { - return MA_INVALID_ARGS; + pResampler->_pHeap = pHeap; + pResampler->format = pConfig->format; + pResampler->channels = pConfig->channels; + pResampler->sampleRateIn = pConfig->sampleRateIn; + pResampler->sampleRateOut = pConfig->sampleRateOut; + + result = ma_resampler_get_vtable(pConfig, pResampler, &pResampler->pBackendVTable, &pResampler->pBackendUserData); + if (result != MA_SUCCESS) { + return result; } - pResampler->config = *pConfig; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onInit == NULL) { + return MA_NOT_IMPLEMENTED; /* onInit not implemented. */ + } - switch (pConfig->algorithm) - { - case ma_resample_algorithm_linear: - { - ma_linear_resampler_config linearConfig; - linearConfig = ma_linear_resampler_config_init(pConfig->format, pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut); - linearConfig.lpfOrder = pConfig->linear.lpfOrder; - linearConfig.lpfNyquistFactor = pConfig->linear.lpfNyquistFactor; - - result = ma_linear_resampler_init(&linearConfig, &pResampler->state.linear); - if (result != MA_SUCCESS) { - return result; - } - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - int speexErr; - pResampler->state.speex.pSpeexResamplerState = speex_resampler_init(pConfig->channels, pConfig->sampleRateIn, pConfig->sampleRateOut, pConfig->speex.quality, &speexErr); - if (pResampler->state.speex.pSpeexResamplerState == NULL) { - return ma_result_from_speex_err(speexErr); - } - #else - /* Speex resampler not available. */ - return MA_NO_BACKEND; - #endif - } break; - - default: return MA_INVALID_ARGS; + result = pResampler->pBackendVTable->onInit(pResampler->pBackendUserData, pConfig, pHeap, &pResampler->pBackend); + if (result != MA_SUCCESS) { + return result; } return MA_SUCCESS; } -MA_API void ma_resampler_uninit(ma_resampler* pResampler) +MA_API ma_result ma_resampler_init(const ma_resampler_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_resampler* pResampler) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_resampler_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_resampler_init_preallocated(pConfig, pHeap, pResampler); + if (result != MA_SUCCESS) { + return result; + } + + pResampler->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_resampler_uninit(ma_resampler* pResampler, const ma_allocation_callbacks* pAllocationCallbacks) { if (pResampler == NULL) { return; } - if (pResampler->config.algorithm == ma_resample_algorithm_linear) { - ma_linear_resampler_uninit(&pResampler->state.linear); + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onUninit == NULL) { + return; } -#if defined(MA_HAS_SPEEX_RESAMPLER) - if (pResampler->config.algorithm == ma_resample_algorithm_speex) { - speex_resampler_destroy((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); + pResampler->pBackendVTable->onUninit(pResampler->pBackendUserData, pResampler->pBackend, pAllocationCallbacks); + + if (pResampler->_ownsHeap) { + ma_free(pResampler->_pHeap, pAllocationCallbacks); } -#endif } -static ma_result ma_resampler_process_pcm_frames__read__linear(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - return ma_linear_resampler_process_pcm_frames(&pResampler->state.linear, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); -} - -#if defined(MA_HAS_SPEEX_RESAMPLER) -static ma_result ma_resampler_process_pcm_frames__read__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - int speexErr; - ma_uint64 frameCountOut; - ma_uint64 frameCountIn; - ma_uint64 framesProcessedOut; - ma_uint64 framesProcessedIn; - unsigned int framesPerIteration = UINT_MAX; - - MA_ASSERT(pResampler != NULL); - MA_ASSERT(pFramesOut != NULL); - MA_ASSERT(pFrameCountOut != NULL); - MA_ASSERT(pFrameCountIn != NULL); - - /* - Reading from the Speex resampler requires a bit of dancing around for a few reasons. The first thing is that it's frame counts - are in unsigned int's whereas ours is in ma_uint64. We therefore need to run the conversion in a loop. The other, more complicated - problem, is that we need to keep track of the input time, similar to what we do with the linear resampler. The reason we need to - do this is for ma_resampler_get_required_input_frame_count() and ma_resampler_get_expected_output_frame_count(). - */ - frameCountOut = *pFrameCountOut; - frameCountIn = *pFrameCountIn; - framesProcessedOut = 0; - framesProcessedIn = 0; - - while (framesProcessedOut < frameCountOut && framesProcessedIn < frameCountIn) { - unsigned int frameCountInThisIteration; - unsigned int frameCountOutThisIteration; - const void* pFramesInThisIteration; - void* pFramesOutThisIteration; - - frameCountInThisIteration = framesPerIteration; - if ((ma_uint64)frameCountInThisIteration > (frameCountIn - framesProcessedIn)) { - frameCountInThisIteration = (unsigned int)(frameCountIn - framesProcessedIn); - } - - frameCountOutThisIteration = framesPerIteration; - if ((ma_uint64)frameCountOutThisIteration > (frameCountOut - framesProcessedOut)) { - frameCountOutThisIteration = (unsigned int)(frameCountOut - framesProcessedOut); - } - - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels)); - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels)); - - if (pResampler->config.format == ma_format_f32) { - speexErr = speex_resampler_process_interleaved_float((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, (const float*)pFramesInThisIteration, &frameCountInThisIteration, (float*)pFramesOutThisIteration, &frameCountOutThisIteration); - } else if (pResampler->config.format == ma_format_s16) { - speexErr = speex_resampler_process_interleaved_int((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, (const spx_int16_t*)pFramesInThisIteration, &frameCountInThisIteration, (spx_int16_t*)pFramesOutThisIteration, &frameCountOutThisIteration); - } else { - /* Format not supported. Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_OPERATION; - } - - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return ma_result_from_speex_err(speexErr); - } - - framesProcessedIn += frameCountInThisIteration; - framesProcessedOut += frameCountOutThisIteration; - } - - *pFrameCountOut = framesProcessedOut; - *pFrameCountIn = framesProcessedIn; - - return MA_SUCCESS; -} -#endif - -static ma_result ma_resampler_process_pcm_frames__read(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - MA_ASSERT(pFramesOut != NULL); - - /* pFramesOut is not NULL, which means we must have a capacity. */ - if (pFrameCountOut == NULL) { - return MA_INVALID_ARGS; - } - - /* It doesn't make sense to not have any input frames to process. */ - if (pFrameCountIn == NULL || pFramesIn == NULL) { - return MA_INVALID_ARGS; - } - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_resampler_process_pcm_frames__read__linear(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_resampler_process_pcm_frames__read__speex(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_ARGS; -} - - -static ma_result ma_resampler_process_pcm_frames__seek__linear(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - - /* Seeking is supported natively by the linear resampler. */ - return ma_linear_resampler_process_pcm_frames(&pResampler->state.linear, pFramesIn, pFrameCountIn, NULL, pFrameCountOut); -} - -#if defined(MA_HAS_SPEEX_RESAMPLER) -static ma_result ma_resampler_process_pcm_frames__seek__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - /* The generic seek method is implemented in on top of ma_resampler_process_pcm_frames__read() by just processing into a dummy buffer. */ - float devnull[4096]; - ma_uint64 totalOutputFramesToProcess; - ma_uint64 totalOutputFramesProcessed; - ma_uint64 totalInputFramesProcessed; - ma_uint32 bpf; - ma_result result; - - MA_ASSERT(pResampler != NULL); - - totalOutputFramesProcessed = 0; - totalInputFramesProcessed = 0; - bpf = ma_get_bytes_per_frame(pResampler->config.format, pResampler->config.channels); - - if (pFrameCountOut != NULL) { - /* Seek by output frames. */ - totalOutputFramesToProcess = *pFrameCountOut; - } else { - /* Seek by input frames. */ - MA_ASSERT(pFrameCountIn != NULL); - totalOutputFramesToProcess = ma_resampler_get_expected_output_frame_count(pResampler, *pFrameCountIn); - } - - if (pFramesIn != NULL) { - /* Process input data. */ - MA_ASSERT(pFrameCountIn != NULL); - while (totalOutputFramesProcessed < totalOutputFramesToProcess && totalInputFramesProcessed < *pFrameCountIn) { - ma_uint64 inputFramesToProcessThisIteration = (*pFrameCountIn - totalInputFramesProcessed); - ma_uint64 outputFramesToProcessThisIteration = (totalOutputFramesToProcess - totalOutputFramesProcessed); - if (outputFramesToProcessThisIteration > sizeof(devnull) / bpf) { - outputFramesToProcessThisIteration = sizeof(devnull) / bpf; - } - - result = ma_resampler_process_pcm_frames__read(pResampler, ma_offset_ptr(pFramesIn, totalInputFramesProcessed*bpf), &inputFramesToProcessThisIteration, ma_offset_ptr(devnull, totalOutputFramesProcessed*bpf), &outputFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - return result; - } - - totalOutputFramesProcessed += outputFramesToProcessThisIteration; - totalInputFramesProcessed += inputFramesToProcessThisIteration; - } - } else { - /* Don't process input data - just update timing and filter state as if zeroes were passed in. */ - while (totalOutputFramesProcessed < totalOutputFramesToProcess) { - ma_uint64 inputFramesToProcessThisIteration = 16384; - ma_uint64 outputFramesToProcessThisIteration = (totalOutputFramesToProcess - totalOutputFramesProcessed); - if (outputFramesToProcessThisIteration > sizeof(devnull) / bpf) { - outputFramesToProcessThisIteration = sizeof(devnull) / bpf; - } - - result = ma_resampler_process_pcm_frames__read(pResampler, NULL, &inputFramesToProcessThisIteration, ma_offset_ptr(devnull, totalOutputFramesProcessed*bpf), &outputFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - return result; - } - - totalOutputFramesProcessed += outputFramesToProcessThisIteration; - totalInputFramesProcessed += inputFramesToProcessThisIteration; - } - } - - - if (pFrameCountIn != NULL) { - *pFrameCountIn = totalInputFramesProcessed; - } - if (pFrameCountOut != NULL) { - *pFrameCountOut = totalOutputFramesProcessed; - } - - return MA_SUCCESS; -} -#endif - -static ma_result ma_resampler_process_pcm_frames__seek(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) -{ - MA_ASSERT(pResampler != NULL); - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_resampler_process_pcm_frames__seek__linear(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_resampler_process_pcm_frames__seek__speex(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); - #else - break; - #endif - }; - - default: break; - } - - /* Should never hit this. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_ARGS; -} - - MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { if (pResampler == NULL) { @@ -39953,17 +50118,17 @@ MA_API ma_result ma_resampler_process_pcm_frames(ma_resampler* pResampler, const return MA_INVALID_ARGS; } - if (pFramesOut != NULL) { - /* Reading. */ - return ma_resampler_process_pcm_frames__read(pResampler, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Seeking. */ - return ma_resampler_process_pcm_frames__seek(pResampler, pFramesIn, pFrameCountIn, pFrameCountOut); + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onProcess == NULL) { + return MA_NOT_IMPLEMENTED; } + + return pResampler->pBackendVTable->onProcess(pResampler->pBackendUserData, pResampler->pBackend, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); } MA_API ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut) { + ma_result result; + if (pResampler == NULL) { return MA_INVALID_ARGS; } @@ -39972,137 +50137,44 @@ MA_API ma_result ma_resampler_set_rate(ma_resampler* pResampler, ma_uint32 sampl return MA_INVALID_ARGS; } - pResampler->config.sampleRateIn = sampleRateIn; - pResampler->config.sampleRateOut = sampleRateOut; - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_set_rate(&pResampler->state.linear, sampleRateIn, sampleRateOut); - } break; - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return ma_result_from_speex_err(speex_resampler_set_rate((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, sampleRateIn, sampleRateOut)); - #else - break; - #endif - }; - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onSetRate == NULL) { + return MA_NOT_IMPLEMENTED; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return MA_INVALID_OPERATION; + result = pResampler->pBackendVTable->onSetRate(pResampler->pBackendUserData, pResampler->pBackend, sampleRateIn, sampleRateOut); + if (result != MA_SUCCESS) { + return result; + } + + pResampler->sampleRateIn = sampleRateIn; + pResampler->sampleRateOut = sampleRateOut; + + return MA_SUCCESS; } MA_API ma_result ma_resampler_set_rate_ratio(ma_resampler* pResampler, float ratio) { + ma_uint32 n; + ma_uint32 d; + if (pResampler == NULL) { return MA_INVALID_ARGS; } - if (pResampler->config.algorithm == ma_resample_algorithm_linear) { - return ma_linear_resampler_set_rate_ratio(&pResampler->state.linear, ratio); - } else { - /* Getting here means the backend does not have native support for setting the rate as a ratio so we just do it generically. */ - ma_uint32 n; - ma_uint32 d; - - d = 1000; - n = (ma_uint32)(ratio * d); - - if (n == 0) { - return MA_INVALID_ARGS; /* Ratio too small. */ - } - - MA_ASSERT(n != 0); - - return ma_resampler_set_rate(pResampler, n, d); - } -} - -MA_API ma_uint64 ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount) -{ - if (pResampler == NULL) { - return 0; + if (ratio <= 0) { + return MA_INVALID_ARGS; } - if (outputFrameCount == 0) { - return 0; + d = 1000; + n = (ma_uint32)(ratio * d); + + if (n == 0) { + return MA_INVALID_ARGS; /* Ratio too small. */ } - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_required_input_frame_count(&pResampler->state.linear, outputFrameCount); - } + MA_ASSERT(n != 0); - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - spx_uint64_t count; - int speexErr = ma_speex_resampler_get_required_input_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, outputFrameCount, &count); - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return 0; - } - - return (ma_uint64)count; - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; -} - -MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount) -{ - if (pResampler == NULL) { - return 0; /* Invalid args. */ - } - - if (inputFrameCount == 0) { - return 0; - } - - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_expected_output_frame_count(&pResampler->state.linear, inputFrameCount); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - spx_uint64_t count; - int speexErr = ma_speex_resampler_get_expected_output_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, inputFrameCount, &count); - if (speexErr != RESAMPLER_ERR_SUCCESS) { - return 0; - } - - return (ma_uint64)count; - #else - break; - #endif - } - - default: break; - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return ma_resampler_set_rate(pResampler, n, d); } MA_API ma_uint64 ma_resampler_get_input_latency(const ma_resampler* pResampler) @@ -40111,28 +50183,11 @@ MA_API ma_uint64 ma_resampler_get_input_latency(const ma_resampler* pResampler) return 0; } - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_input_latency(&pResampler->state.linear); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return (ma_uint64)ma_speex_resampler_get_input_latency((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); - #else - break; - #endif - } - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetInputLatency == NULL) { + return 0; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return pResampler->pBackendVTable->onGetInputLatency(pResampler->pBackendUserData, pResampler->pBackend); } MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) @@ -40141,28 +50196,62 @@ MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) return 0; } - switch (pResampler->config.algorithm) - { - case ma_resample_algorithm_linear: - { - return ma_linear_resampler_get_output_latency(&pResampler->state.linear); - } - - case ma_resample_algorithm_speex: - { - #if defined(MA_HAS_SPEEX_RESAMPLER) - return (ma_uint64)ma_speex_resampler_get_output_latency((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState); - #else - break; - #endif - } - - default: break; + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetOutputLatency == NULL) { + return 0; } - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return pResampler->pBackendVTable->onGetOutputLatency(pResampler->pBackendUserData, pResampler->pBackend); +} + +MA_API ma_result ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetRequiredInputFrameCount == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pResampler->pBackendVTable->onGetRequiredInputFrameCount(pResampler->pBackendUserData, pResampler->pBackend, outputFrameCount, pInputFrameCount); +} + +MA_API ma_result ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onGetExpectedOutputFrameCount == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pResampler->pBackendVTable->onGetExpectedOutputFrameCount(pResampler->pBackendUserData, pResampler->pBackend, inputFrameCount, pOutputFrameCount); +} + +MA_API ma_result ma_resampler_reset(ma_resampler* pResampler) +{ + if (pResampler == NULL) { + return MA_INVALID_ARGS; + } + + if (pResampler->pBackendVTable == NULL || pResampler->pBackendVTable->onReset == NULL) { + return MA_NOT_IMPLEMENTED; + } + + return pResampler->pBackendVTable->onReset(pResampler->pBackendUserData, pResampler->pBackend); } /************************************************************************************************************************************************************** @@ -40283,17 +50372,13 @@ MA_API ma_channel_converter_config ma_channel_converter_config_init(ma_format fo { ma_channel_converter_config config; - /* Channel counts need to be clamped. */ - channelsIn = ma_min(channelsIn, ma_countof(config.channelMapIn)); - channelsOut = ma_min(channelsOut, ma_countof(config.channelMapOut)); - MA_ZERO_OBJECT(&config); - config.format = format; - config.channelsIn = channelsIn; - config.channelsOut = channelsOut; - ma_channel_map_copy_or_default(config.channelMapIn, pChannelMapIn, channelsIn); - ma_channel_map_copy_or_default(config.channelMapOut, pChannelMapOut, channelsOut); - config.mixingMode = mixingMode; + config.format = format; + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; + config.pChannelMapIn = pChannelMapIn; + config.pChannelMapOut = pChannelMapOut; + config.mixingMode = mixingMode; return config; } @@ -40324,320 +50409,862 @@ static ma_bool32 ma_is_spatial_channel_position(ma_channel channelPosition) return MA_FALSE; } -MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, ma_channel_converter* pConverter) + +static ma_bool32 ma_channel_map_is_passthrough(const ma_channel* pChannelMapIn, ma_uint32 channelsIn, const ma_channel* pChannelMapOut, ma_uint32 channelsOut) +{ + if (channelsOut == channelsIn) { + return ma_channel_map_is_equal(pChannelMapOut, pChannelMapIn, channelsOut); + } else { + return MA_FALSE; /* Channel counts differ, so cannot be a passthrough. */ + } +} + +static ma_channel_conversion_path ma_channel_map_get_conversion_path(const ma_channel* pChannelMapIn, ma_uint32 channelsIn, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, ma_channel_mix_mode mode) +{ + if (ma_channel_map_is_passthrough(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut)) { + return ma_channel_conversion_path_passthrough; + } + + if (channelsOut == 1 && (pChannelMapOut == NULL || pChannelMapOut[0] == MA_CHANNEL_MONO)) { + return ma_channel_conversion_path_mono_out; + } + + if (channelsIn == 1 && (pChannelMapIn == NULL || pChannelMapIn[0] == MA_CHANNEL_MONO)) { + return ma_channel_conversion_path_mono_in; + } + + if (mode == ma_channel_mix_mode_custom_weights) { + return ma_channel_conversion_path_weights; + } + + /* + We can use a simple shuffle if both channel maps have the same channel count and all channel + positions are present in both. + */ + if (channelsIn == channelsOut) { + ma_uint32 iChannelIn; + ma_bool32 areAllChannelPositionsPresent = MA_TRUE; + for (iChannelIn = 0; iChannelIn < channelsIn; ++iChannelIn) { + ma_bool32 isInputChannelPositionInOutput = MA_FALSE; + if (ma_channel_map_contains_channel_position(channelsOut, pChannelMapOut, ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn))) { + isInputChannelPositionInOutput = MA_TRUE; + break; + } + + if (!isInputChannelPositionInOutput) { + areAllChannelPositionsPresent = MA_FALSE; + break; + } + } + + if (areAllChannelPositionsPresent) { + return ma_channel_conversion_path_shuffle; + } + } + + /* Getting here means we'll need to use weights. */ + return ma_channel_conversion_path_weights; +} + + +static ma_result ma_channel_map_build_shuffle_table(const ma_channel* pChannelMapIn, ma_uint32 channelCountIn, const ma_channel* pChannelMapOut, ma_uint32 channelCountOut, ma_uint8* pShuffleTable) { ma_uint32 iChannelIn; ma_uint32 iChannelOut; + if (pShuffleTable == NULL || channelCountIn == 0 || channelCountOut == 0) { + return MA_INVALID_ARGS; + } + + /* + When building the shuffle table we just do a 1:1 mapping based on the first occurance of a channel. If the + input channel has more than one occurance of a channel position, the second one will be ignored. + */ + for (iChannelOut = 0; iChannelOut < channelCountOut; iChannelOut += 1) { + ma_channel channelOut; + + /* Default to MA_CHANNEL_INDEX_NULL so that if a mapping is not found it'll be set appropriately. */ + pShuffleTable[iChannelOut] = MA_CHANNEL_INDEX_NULL; + + channelOut = ma_channel_map_get_channel(pChannelMapOut, channelCountOut, iChannelOut); + for (iChannelIn = 0; iChannelIn < channelCountIn; iChannelIn += 1) { + ma_channel channelIn; + + channelIn = ma_channel_map_get_channel(pChannelMapIn, channelCountIn, iChannelIn); + if (channelOut == channelIn) { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + break; + } + + /* + Getting here means the channels don't exactly match, but we are going to support some + relaxed matching for practicality. If, for example, there are two stereo channel maps, + but one uses front left/right and the other uses side left/right, it makes logical + sense to just map these. The way we'll do it is we'll check if there is a logical + corresponding mapping, and if so, apply it, but we will *not* break from the loop, + thereby giving the loop a chance to find an exact match later which will take priority. + */ + switch (channelOut) + { + /* Left channels. */ + case MA_CHANNEL_FRONT_LEFT: + case MA_CHANNEL_SIDE_LEFT: + { + switch (channelIn) { + case MA_CHANNEL_FRONT_LEFT: + case MA_CHANNEL_SIDE_LEFT: + { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + } break; + } + } break; + + /* Right channels. */ + case MA_CHANNEL_FRONT_RIGHT: + case MA_CHANNEL_SIDE_RIGHT: + { + switch (channelIn) { + case MA_CHANNEL_FRONT_RIGHT: + case MA_CHANNEL_SIDE_RIGHT: + { + pShuffleTable[iChannelOut] = (ma_uint8)iChannelIn; + } break; + } + } break; + + default: break; + } + } + } + + return MA_SUCCESS; +} + + +static void ma_channel_map_apply_shuffle_table_u8(ma_uint8* pFramesOut, ma_uint32 channelsOut, const ma_uint8* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_s16(ma_int16* pFramesOut, ma_uint32 channelsOut, const ma_int16* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_s24(ma_uint8* pFramesOut, ma_uint32 channelsOut, const ma_uint8* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut*3 + 0] = pFramesIn[iChannelIn*3 + 0]; + pFramesOut[iChannelOut*3 + 1] = pFramesIn[iChannelIn*3 + 1]; + pFramesOut[iChannelOut*3 + 2] = pFramesIn[iChannelIn*3 + 2]; + } else { + pFramesOut[iChannelOut*3 + 0] = 0; + } pFramesOut[iChannelOut*3 + 1] = 0; + } pFramesOut[iChannelOut*3 + 2] = 0; + + pFramesOut += channelsOut*3; + pFramesIn += channelsIn*3; + } +} + +static void ma_channel_map_apply_shuffle_table_s32(ma_int32* pFramesOut, ma_uint32 channelsOut, const ma_int32* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static void ma_channel_map_apply_shuffle_table_f32(float* pFramesOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_uint8 iChannelIn = pShuffleTable[iChannelOut]; + if (iChannelIn < channelsIn) { /* For safety, and to deal with MA_CHANNEL_INDEX_NULL. */ + pFramesOut[iChannelOut] = pFramesIn[iChannelIn]; + } else { + pFramesOut[iChannelOut] = 0; + } + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } +} + +static ma_result ma_channel_map_apply_shuffle_table(void* pFramesOut, ma_uint32 channelsOut, const void* pFramesIn, ma_uint32 channelsIn, ma_uint64 frameCount, const ma_uint8* pShuffleTable, ma_format format) +{ + if (pFramesOut == NULL || pFramesIn == NULL || channelsOut == 0 || pShuffleTable == NULL) { + return MA_INVALID_ARGS; + } + + switch (format) + { + case ma_format_u8: + { + ma_channel_map_apply_shuffle_table_u8((ma_uint8*)pFramesOut, channelsOut, (const ma_uint8*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s16: + { + ma_channel_map_apply_shuffle_table_s16((ma_int16*)pFramesOut, channelsOut, (const ma_int16*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s24: + { + ma_channel_map_apply_shuffle_table_s24((ma_uint8*)pFramesOut, channelsOut, (const ma_uint8*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_s32: + { + ma_channel_map_apply_shuffle_table_s32((ma_int32*)pFramesOut, channelsOut, (const ma_int32*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + case ma_format_f32: + { + ma_channel_map_apply_shuffle_table_f32((float*)pFramesOut, channelsOut, (const float*)pFramesIn, channelsIn, frameCount, pShuffleTable); + } break; + + default: return MA_INVALID_ARGS; /* Unknown format. */ + } + + return MA_SUCCESS; +} + +static ma_result ma_channel_map_apply_mono_out_f32(float* pFramesOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount) +{ + ma_uint64 iFrame; + ma_uint32 iChannelIn; + ma_uint32 accumulationCount; + + if (pFramesOut == NULL || pFramesIn == NULL || channelsIn == 0) { + return MA_INVALID_ARGS; + } + + /* In this case the output stream needs to be the average of all channels, ignoring NONE. */ + + /* A quick pre-processing step to get the accumulation counter since we're ignoring NONE channels. */ + accumulationCount = 0; + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + if (ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn) != MA_CHANNEL_NONE) { + accumulationCount += 1; + } + } + + if (accumulationCount > 0) { /* <-- Prevent a division by zero. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + float accumulation = 0; + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + if (channelIn != MA_CHANNEL_NONE) { + accumulation += pFramesIn[iChannelIn]; + } + } + + pFramesOut[0] = accumulation / accumulationCount; + pFramesOut += 1; + pFramesIn += channelsIn; + } + } else { + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, 1); + } + + return MA_SUCCESS; +} + +static ma_result ma_channel_map_apply_mono_in_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, ma_uint64 frameCount, ma_mono_expansion_mode monoExpansionMode) +{ + ma_uint64 iFrame; + ma_uint32 iChannelOut; + + if (pFramesOut == NULL || channelsOut == 0 || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + /* Note that the MA_CHANNEL_NONE channel must be ignored in all cases. */ + switch (monoExpansionMode) + { + case ma_mono_expansion_mode_average: + { + float weight; + ma_uint32 validChannelCount = 0; + + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + validChannelCount += 1; + } + } + + weight = 1.0f / validChannelCount; + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + pFramesOut[iChannelOut] = pFramesIn[0] * weight; + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + } break; + + case ma_mono_expansion_mode_stereo_only: + { + if (channelsOut >= 2) { + ma_uint32 iChannelLeft = (ma_uint32)-1; + ma_uint32 iChannelRight = (ma_uint32)-1; + + /* + We first need to find our stereo channels. We prefer front-left and front-right, but + if they're not available, we'll also try side-left and side-right. If neither are + available we'll fall through to the default case below. + */ + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut == MA_CHANNEL_SIDE_LEFT) { + iChannelLeft = iChannelOut; + } + if (channelOut == MA_CHANNEL_SIDE_RIGHT) { + iChannelRight = iChannelOut; + } + } + + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut == MA_CHANNEL_FRONT_LEFT) { + iChannelLeft = iChannelOut; + } + if (channelOut == MA_CHANNEL_FRONT_RIGHT) { + iChannelRight = iChannelOut; + } + } + + + if (iChannelLeft != (ma_uint32)-1 && iChannelRight != (ma_uint32)-1) { + /* We found our stereo channels so we can duplicate the signal across those channels. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + if (iChannelOut == iChannelLeft || iChannelOut == iChannelRight) { + pFramesOut[iChannelOut] = pFramesIn[0]; + } else { + pFramesOut[iChannelOut] = 0.0f; + } + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + + break; /* Get out of the switch. */ + } else { + /* Fallthrough. Does not have left and right channels. */ + goto default_handler; + } + } else { + /* Fallthrough. Does not have stereo channels. */ + goto default_handler; + } + }; /* Fallthrough. See comments above. */ + + case ma_mono_expansion_mode_duplicate: + default: + { + default_handler: + { + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + if (channelOut != MA_CHANNEL_NONE) { + pFramesOut[iChannelOut] = pFramesIn[0]; + } + } + + pFramesOut += channelsOut; + pFramesIn += 1; + } + } + } break; + } + + return MA_SUCCESS; +} + +static void ma_channel_map_apply_f32(float* pFramesOut, const ma_channel* pChannelMapOut, ma_uint32 channelsOut, const float* pFramesIn, const ma_channel* pChannelMapIn, ma_uint32 channelsIn, ma_uint64 frameCount, ma_channel_mix_mode mode, ma_mono_expansion_mode monoExpansionMode) +{ + ma_channel_conversion_path conversionPath = ma_channel_map_get_conversion_path(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, mode); + + /* Optimized Path: Passthrough */ + if (conversionPath == ma_channel_conversion_path_passthrough) { + ma_copy_pcm_frames(pFramesOut, pFramesIn, frameCount, ma_format_f32, channelsOut); + return; + } + + /* Special Path: Mono Output. */ + if (conversionPath == ma_channel_conversion_path_mono_out) { + ma_channel_map_apply_mono_out_f32(pFramesOut, pFramesIn, pChannelMapIn, channelsIn, frameCount); + return; + } + + /* Special Path: Mono Input. */ + if (conversionPath == ma_channel_conversion_path_mono_in) { + ma_channel_map_apply_mono_in_f32(pFramesOut, pChannelMapOut, channelsOut, pFramesIn, frameCount, monoExpansionMode); + return; + } + + /* Getting here means we aren't running on an optimized conversion path. */ + if (channelsOut <= MA_MAX_CHANNELS) { + ma_result result; + + if (mode == ma_channel_mix_mode_simple) { + ma_channel shuffleTable[MA_MAX_CHANNELS]; + + result = ma_channel_map_build_shuffle_table(pChannelMapIn, channelsIn, pChannelMapOut, channelsOut, shuffleTable); + if (result != MA_SUCCESS) { + return; + } + + result = ma_channel_map_apply_shuffle_table(pFramesOut, channelsOut, pFramesIn, channelsIn, frameCount, shuffleTable, ma_format_f32); + if (result != MA_SUCCESS) { + return; + } + } else { + ma_uint32 iFrame; + ma_uint32 iChannelOut; + ma_uint32 iChannelIn; + float weights[32][32]; /* Do not use MA_MAX_CHANNELS here! */ + + /* + If we have a small enough number of channels, pre-compute the weights. Otherwise we'll just need to + fall back to a slower path because otherwise we'll run out of stack space. + */ + if (channelsIn <= ma_countof(weights) && channelsOut <= ma_countof(weights)) { + /* Pre-compute weights. */ + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + weights[iChannelOut][iChannelIn] = ma_calculate_channel_position_rectangular_weight(channelOut, channelIn); + } + } + + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + float accumulation = 0; + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + accumulation += pFramesIn[iChannelIn] * weights[iChannelOut][iChannelIn]; + } + + pFramesOut[iChannelOut] = accumulation; + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } + } else { + /* Cannot pre-compute weights because not enough room in stack-allocated buffer. */ + for (iFrame = 0; iFrame < frameCount; iFrame += 1) { + for (iChannelOut = 0; iChannelOut < channelsOut; iChannelOut += 1) { + float accumulation = 0; + ma_channel channelOut = ma_channel_map_get_channel(pChannelMapOut, channelsOut, iChannelOut); + + for (iChannelIn = 0; iChannelIn < channelsIn; iChannelIn += 1) { + ma_channel channelIn = ma_channel_map_get_channel(pChannelMapIn, channelsIn, iChannelIn); + accumulation += pFramesIn[iChannelIn] * ma_calculate_channel_position_rectangular_weight(channelOut, channelIn); + } + + pFramesOut[iChannelOut] = accumulation; + } + + pFramesOut += channelsOut; + pFramesIn += channelsIn; + } + } + } + } else { + /* Fall back to silence. If you hit this, what are you doing with so many channels?! */ + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, channelsOut); + } +} + + +typedef struct +{ + size_t sizeInBytes; + size_t channelMapInOffset; + size_t channelMapOutOffset; + size_t shuffleTableOffset; + size_t weightsOffset; +} ma_channel_converter_heap_layout; + +static ma_channel_conversion_path ma_channel_converter_config_get_conversion_path(const ma_channel_converter_config* pConfig) +{ + return ma_channel_map_get_conversion_path(pConfig->pChannelMapIn, pConfig->channelsIn, pConfig->pChannelMapOut, pConfig->channelsOut, pConfig->mixingMode); +} + +static ma_result ma_channel_converter_get_heap_layout(const ma_channel_converter_config* pConfig, ma_channel_converter_heap_layout* pHeapLayout) +{ + ma_channel_conversion_path conversionPath; + + MA_ASSERT(pHeapLayout != NULL); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + if (!ma_channel_map_is_valid(pConfig->pChannelMapIn, pConfig->channelsIn)) { + return MA_INVALID_ARGS; + } + + if (!ma_channel_map_is_valid(pConfig->pChannelMapOut, pConfig->channelsOut)) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Input channel map. Only need to allocate this if we have an input channel map (otherwise default channel map is assumed). */ + pHeapLayout->channelMapInOffset = pHeapLayout->sizeInBytes; + if (pConfig->pChannelMapIn != NULL) { + pHeapLayout->sizeInBytes += sizeof(ma_channel) * pConfig->channelsIn; + } + + /* Output channel map. Only need to allocate this if we have an output channel map (otherwise default channel map is assumed). */ + pHeapLayout->channelMapOutOffset = pHeapLayout->sizeInBytes; + if (pConfig->pChannelMapOut != NULL) { + pHeapLayout->sizeInBytes += sizeof(ma_channel) * pConfig->channelsOut; + } + + /* Alignment for the next section. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + /* Whether or not we use weights of a shuffle table depends on the channel map themselves and the algorithm we've chosen. */ + conversionPath = ma_channel_converter_config_get_conversion_path(pConfig); + + /* Shuffle table */ + pHeapLayout->shuffleTableOffset = pHeapLayout->sizeInBytes; + if (conversionPath == ma_channel_conversion_path_shuffle) { + pHeapLayout->sizeInBytes += sizeof(ma_uint8) * pConfig->channelsOut; + } + + /* Weights */ + pHeapLayout->weightsOffset = pHeapLayout->sizeInBytes; + if (conversionPath == ma_channel_conversion_path_weights) { + pHeapLayout->sizeInBytes += sizeof(float*) * pConfig->channelsIn; + pHeapLayout->sizeInBytes += sizeof(float ) * pConfig->channelsIn * pConfig->channelsOut; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_get_heap_size(const ma_channel_converter_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_channel_converter_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_channel_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_init_preallocated(const ma_channel_converter_config* pConfig, void* pHeap, ma_channel_converter* pConverter) +{ + ma_result result; + ma_channel_converter_heap_layout heapLayout; + if (pConverter == NULL) { return MA_INVALID_ARGS; } MA_ZERO_OBJECT(pConverter); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_channel_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - /* Basic validation for channel counts. */ - if (pConfig->channelsIn < MA_MIN_CHANNELS || pConfig->channelsIn > MA_MAX_CHANNELS || - pConfig->channelsOut < MA_MIN_CHANNELS || pConfig->channelsOut > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } - - if (!ma_channel_map_valid(pConfig->channelsIn, pConfig->channelMapIn)) { - return MA_INVALID_ARGS; /* Invalid input channel map. */ - } - if (!ma_channel_map_valid(pConfig->channelsOut, pConfig->channelMapOut)) { - return MA_INVALID_ARGS; /* Invalid output channel map. */ - } + pConverter->_pHeap = pHeap; + MA_ZERO_MEMORY(pConverter->_pHeap, heapLayout.sizeInBytes); pConverter->format = pConfig->format; pConverter->channelsIn = pConfig->channelsIn; pConverter->channelsOut = pConfig->channelsOut; - ma_channel_map_copy_or_default(pConverter->channelMapIn, pConfig->channelMapIn, pConfig->channelsIn); - ma_channel_map_copy_or_default(pConverter->channelMapOut, pConfig->channelMapOut, pConfig->channelsOut); pConverter->mixingMode = pConfig->mixingMode; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = pConfig->weights[iChannelIn][iChannelOut]; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(pConfig->weights[iChannelIn][iChannelOut]); + if (pConfig->pChannelMapIn != NULL) { + pConverter->pChannelMapIn = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapInOffset); + ma_channel_map_copy_or_default(pConverter->pChannelMapIn, pConfig->channelsIn, pConfig->pChannelMapIn, pConfig->channelsIn); + } else { + pConverter->pChannelMapIn = NULL; /* Use default channel map. */ + } + + if (pConfig->pChannelMapOut != NULL) { + pConverter->pChannelMapOut = (ma_channel*)ma_offset_ptr(pHeap, heapLayout.channelMapOutOffset); + ma_channel_map_copy_or_default(pConverter->pChannelMapOut, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelsOut); + } else { + pConverter->pChannelMapOut = NULL; /* Use default channel map. */ + } + + pConverter->conversionPath = ma_channel_converter_config_get_conversion_path(pConfig); + + if (pConverter->conversionPath == ma_channel_conversion_path_shuffle) { + pConverter->pShuffleTable = (ma_uint8*)ma_offset_ptr(pHeap, heapLayout.shuffleTableOffset); + ma_channel_map_build_shuffle_table(pConverter->pChannelMapIn, pConverter->channelsIn, pConverter->pChannelMapOut, pConverter->channelsOut, pConverter->pShuffleTable); + } + + if (pConverter->conversionPath == ma_channel_conversion_path_weights) { + ma_uint32 iChannelIn; + ma_uint32 iChannelOut; + + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32 = (float** )ma_offset_ptr(pHeap, heapLayout.weightsOffset); + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + pConverter->weights.f32[iChannelIn] = (float*)ma_offset_ptr(pHeap, heapLayout.weightsOffset + ((sizeof(float*) * pConverter->channelsIn) + (sizeof(float) * pConverter->channelsOut * iChannelIn))); + } + } else { + pConverter->weights.s16 = (ma_int32**)ma_offset_ptr(pHeap, heapLayout.weightsOffset); + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + pConverter->weights.s16[iChannelIn] = (ma_int32*)ma_offset_ptr(pHeap, heapLayout.weightsOffset + ((sizeof(ma_int32*) * pConverter->channelsIn) + (sizeof(ma_int32) * pConverter->channelsOut * iChannelIn))); } } - } - - - /* If the input and output channels and channel maps are the same we should use a passthrough. */ - if (pConverter->channelsIn == pConverter->channelsOut) { - if (ma_channel_map_equal(pConverter->channelsIn, pConverter->channelMapIn, pConverter->channelMapOut)) { - pConverter->isPassthrough = MA_TRUE; - } - if (ma_channel_map_blank(pConverter->channelsIn, pConverter->channelMapIn) || ma_channel_map_blank(pConverter->channelsOut, pConverter->channelMapOut)) { - pConverter->isPassthrough = MA_TRUE; - } - } - - - /* - We can use a simple case for expanding the mono channel. This will used when expanding a mono input into any output so long - as no LFE is present in the output. - */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsIn == 1 && pConverter->channelMapIn[0] == MA_CHANNEL_MONO) { - /* Optimal case if no LFE is in the output channel map. */ - pConverter->isSimpleMonoExpansion = MA_TRUE; - if (ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->channelMapOut, MA_CHANNEL_LFE)) { - pConverter->isSimpleMonoExpansion = MA_FALSE; - } - } - } - - /* Another optimized case is stereo to mono. */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsOut == 1 && pConverter->channelMapOut[0] == MA_CHANNEL_MONO && pConverter->channelsIn == 2) { - /* Optimal case if no LFE is in the input channel map. */ - pConverter->isStereoToMono = MA_TRUE; - if (ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->channelMapIn, MA_CHANNEL_LFE)) { - pConverter->isStereoToMono = MA_FALSE; - } - } - } - - - /* - Here is where we do a bit of pre-processing to know how each channel should be combined to make up the output. Rules: - - 1) If it's a passthrough, do nothing - it's just a simple memcpy(). - 2) If the channel counts are the same and every channel position in the input map is present in the output map, use a - simple shuffle. An example might be different 5.1 channel layouts. - 3) Otherwise channels are blended based on spatial locality. - */ - if (!pConverter->isPassthrough) { - if (pConverter->channelsIn == pConverter->channelsOut) { - ma_bool32 areAllChannelPositionsPresent = MA_TRUE; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_bool32 isInputChannelPositionInOutput = MA_FALSE; - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->channelMapIn[iChannelIn] == pConverter->channelMapOut[iChannelOut]) { - isInputChannelPositionInOutput = MA_TRUE; - break; - } - } - - if (!isInputChannelPositionInOutput) { - areAllChannelPositionsPresent = MA_FALSE; - break; + /* Silence our weights by default. */ + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; iChannelOut += 1) { + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32[iChannelIn][iChannelOut] = 0.0f; + } else { + pConverter->weights.s16[iChannelIn][iChannelOut] = 0; } } + } - if (areAllChannelPositionsPresent) { - pConverter->isSimpleShuffle = MA_TRUE; + /* + We now need to fill out our weights table. This is determined by the mixing mode. + */ + switch (pConverter->mixingMode) + { + case ma_channel_mix_mode_custom_weights: + { + if (pConfig->ppWeights == NULL) { + return MA_INVALID_ARGS; /* Config specified a custom weights mixing mode, but no custom weights have been specified. */ + } - /* - All the router will be doing is rearranging channels which means all we need to do is use a shuffling table which is just - a mapping between the index of the input channel to the index of the output channel. - */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - if (pConverter->channelMapIn[iChannelIn] == pConverter->channelMapOut[iChannelOut]) { - pConverter->shuffleTable[iChannelIn] = (ma_uint8)iChannelOut; - break; + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; iChannelIn += 1) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; iChannelOut += 1) { + float weight = pConfig->ppWeights[iChannelIn][iChannelOut]; + + if (pConverter->format == ma_format_f32) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } else { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); } } } - } - } - } + } break; - - /* - Here is where weights are calculated. Note that we calculate the weights at all times, even when using a passthrough and simple - shuffling. We use different algorithms for calculating weights depending on our mixing mode. - - In simple mode we don't do any blending (except for converting between mono, which is done in a later step). Instead we just - map 1:1 matching channels. In this mode, if no channels in the input channel map correspond to anything in the output channel - map, nothing will be heard! - */ - - /* In all cases we need to make sure all channels that are present in both channel maps have a 1:1 mapping. */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosIn == channelPosOut) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = 1; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = (1 << MA_CHANNEL_CONVERTER_FIXED_POINT_SHIFT); - } - } - } - } - - /* - The mono channel is accumulated on all other channels, except LFE. Make sure in this loop we exclude output mono channels since - they were handled in the pass above. - */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn == MA_CHANNEL_MONO) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosOut != MA_CHANNEL_NONE && channelPosOut != MA_CHANNEL_MONO && channelPosOut != MA_CHANNEL_LFE) { + case ma_channel_mix_mode_simple: + { + /* In simple mode, excess channels need to be silenced or dropped. */ + ma_uint32 iChannel; + for (iChannel = 0; iChannel < ma_min(pConverter->channelsIn, pConverter->channelsOut); iChannel += 1) { if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = 1; + if (pConverter->weights.f32[iChannel][iChannel] == 0) { + pConverter->weights.f32[iChannel][iChannel] = 1; + } } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = (1 << MA_CHANNEL_CONVERTER_FIXED_POINT_SHIFT); - } - } - } - } - } - - /* The output mono channel is the average of all non-none, non-mono and non-lfe input channels. */ - { - ma_uint32 len = 0; - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) { - len += 1; - } - } - - if (len > 0) { - float monoWeight = 1.0f / len; - - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (channelPosOut == MA_CHANNEL_MONO) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; - - if (channelPosIn != MA_CHANNEL_NONE && channelPosIn != MA_CHANNEL_MONO && channelPosIn != MA_CHANNEL_LFE) { - if (pConverter->format == ma_format_f32) { - pConverter->weights.f32[iChannelIn][iChannelOut] = monoWeight; - } else { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(monoWeight); - } + if (pConverter->weights.s16[iChannel][iChannel] == 0) { + pConverter->weights.s16[iChannel][iChannel] = ma_channel_converter_float_to_fixed(1); } } } - } - } - } + } break; + case ma_channel_mix_mode_rectangular: + default: + { + /* Unmapped input channels. */ + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pConverter->pChannelMapIn[iChannelIn]; - /* Input and output channels that are not present on the other side need to be blended in based on spatial locality. */ - switch (pConverter->mixingMode) - { - case ma_channel_mix_mode_rectangular: - { - /* Unmapped input channels. */ - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; + if (ma_is_spatial_channel_position(channelPosIn)) { + if (!ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->pChannelMapOut, channelPosIn)) { + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pConverter->pChannelMapOut[iChannelOut]; - if (ma_is_spatial_channel_position(channelPosIn)) { - if (!ma_channel_map_contains_channel_position(pConverter->channelsOut, pConverter->channelMapOut, channelPosIn)) { - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; - - if (ma_is_spatial_channel_position(channelPosOut)) { - float weight = 0; - if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { - weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); - } - - /* Only apply the weight if we haven't already got some contribution from the respective channels. */ - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { - pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + if (ma_is_spatial_channel_position(channelPosOut)) { + float weight = 0; + if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { + weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); } - } else { - if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + + /* Only apply the weight if we haven't already got some contribution from the respective channels. */ + if (pConverter->format == ma_format_f32) { + if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } + } else { + if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + } } } } } } } - } - /* Unmapped output channels. */ - for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { - ma_channel channelPosOut = pConverter->channelMapOut[iChannelOut]; + /* Unmapped output channels. */ + for (iChannelOut = 0; iChannelOut < pConverter->channelsOut; ++iChannelOut) { + ma_channel channelPosOut = pConverter->pChannelMapOut[iChannelOut]; - if (ma_is_spatial_channel_position(channelPosOut)) { - if (!ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->channelMapIn, channelPosOut)) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_channel channelPosIn = pConverter->channelMapIn[iChannelIn]; + if (ma_is_spatial_channel_position(channelPosOut)) { + if (!ma_channel_map_contains_channel_position(pConverter->channelsIn, pConverter->pChannelMapIn, channelPosOut)) { + for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { + ma_channel channelPosIn = pConverter->pChannelMapIn[iChannelIn]; - if (ma_is_spatial_channel_position(channelPosIn)) { - float weight = 0; - if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { - weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); - } - - /* Only apply the weight if we haven't already got some contribution from the respective channels. */ - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { - pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + if (ma_is_spatial_channel_position(channelPosIn)) { + float weight = 0; + if (pConverter->mixingMode == ma_channel_mix_mode_rectangular) { + weight = ma_calculate_channel_position_rectangular_weight(channelPosIn, channelPosOut); } - } else { - if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { - pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + + /* Only apply the weight if we haven't already got some contribution from the respective channels. */ + if (pConverter->format == ma_format_f32) { + if (pConverter->weights.f32[iChannelIn][iChannelOut] == 0) { + pConverter->weights.f32[iChannelIn][iChannelOut] = weight; + } + } else { + if (pConverter->weights.s16[iChannelIn][iChannelOut] == 0) { + pConverter->weights.s16[iChannelIn][iChannelOut] = ma_channel_converter_float_to_fixed(weight); + } } } } } } } - } - } break; - - case ma_channel_mix_mode_simple: - { - /* In simple mode, excess channels need to be silenced or dropped. */ - ma_uint32 iChannel; - for (iChannel = 0; iChannel < ma_min(pConverter->channelsIn, pConverter->channelsOut); iChannel += 1) { - if (pConverter->format == ma_format_f32) { - if (pConverter->weights.f32[iChannel][iChannel] == 0) { - pConverter->weights.f32[iChannel][iChannel] = 1; - } - } else { - if (pConverter->weights.s16[iChannel][iChannel] == 0) { - pConverter->weights.s16[iChannel][iChannel] = ma_channel_converter_float_to_fixed(1); - } - } - } - } break; - - case ma_channel_mix_mode_custom_weights: - default: - { - /* Fallthrough. */ - } break; + } break; + } } - return MA_SUCCESS; } -MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter) +MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_channel_converter* pConverter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_channel_converter_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_channel_converter_init_preallocated(pConfig, pHeap, pConverter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pConverter->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_channel_converter_uninit(ma_channel_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks) { if (pConverter == NULL) { return; } + + if (pConverter->_ownsHeap) { + ma_free(pConverter->_pHeap, pAllocationCallbacks); + } } static ma_result ma_channel_converter_process_pcm_frames__passthrough(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) @@ -40650,103 +51277,17 @@ static ma_result ma_channel_converter_process_pcm_frames__passthrough(ma_channel return MA_SUCCESS; } -static ma_result ma_channel_converter_process_pcm_frames__simple_shuffle(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__shuffle(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { - ma_uint32 iFrame; - ma_uint32 iChannelIn; - MA_ASSERT(pConverter != NULL); MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesIn != NULL); MA_ASSERT(pConverter->channelsIn == pConverter->channelsOut); - switch (pConverter->format) - { - case ma_format_u8: - { - /* */ ma_uint8* pFramesOutU8 = ( ma_uint8*)pFramesOut; - const ma_uint8* pFramesInU8 = (const ma_uint8*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutU8[pConverter->shuffleTable[iChannelIn]] = pFramesInU8[iChannelIn]; - } - - pFramesOutU8 += pConverter->channelsOut; - pFramesInU8 += pConverter->channelsIn; - } - } break; - - case ma_format_s16: - { - /* */ ma_int16* pFramesOutS16 = ( ma_int16*)pFramesOut; - const ma_int16* pFramesInS16 = (const ma_int16*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutS16[pConverter->shuffleTable[iChannelIn]] = pFramesInS16[iChannelIn]; - } - - pFramesOutS16 += pConverter->channelsOut; - pFramesInS16 += pConverter->channelsIn; - } - } break; - - case ma_format_s24: - { - /* */ ma_uint8* pFramesOutS24 = ( ma_uint8*)pFramesOut; - const ma_uint8* pFramesInS24 = (const ma_uint8*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - ma_uint32 iChannelOut = pConverter->shuffleTable[iChannelIn]; - pFramesOutS24[iChannelOut*3 + 0] = pFramesInS24[iChannelIn*3 + 0]; - pFramesOutS24[iChannelOut*3 + 1] = pFramesInS24[iChannelIn*3 + 1]; - pFramesOutS24[iChannelOut*3 + 2] = pFramesInS24[iChannelIn*3 + 2]; - } - - pFramesOutS24 += pConverter->channelsOut*3; - pFramesInS24 += pConverter->channelsIn*3; - } - } break; - - case ma_format_s32: - { - /* */ ma_int32* pFramesOutS32 = ( ma_int32*)pFramesOut; - const ma_int32* pFramesInS32 = (const ma_int32*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutS32[pConverter->shuffleTable[iChannelIn]] = pFramesInS32[iChannelIn]; - } - - pFramesOutS32 += pConverter->channelsOut; - pFramesInS32 += pConverter->channelsIn; - } - } break; - - case ma_format_f32: - { - /* */ float* pFramesOutF32 = ( float*)pFramesOut; - const float* pFramesInF32 = (const float*)pFramesIn; - - for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannelIn = 0; iChannelIn < pConverter->channelsIn; ++iChannelIn) { - pFramesOutF32[pConverter->shuffleTable[iChannelIn]] = pFramesInF32[iChannelIn]; - } - - pFramesOutF32 += pConverter->channelsOut; - pFramesInF32 += pConverter->channelsIn; - } - } break; - - default: return MA_INVALID_OPERATION; /* Unknown format. */ - } - - return MA_SUCCESS; + return ma_channel_map_apply_shuffle_table(pFramesOut, pConverter->channelsOut, pFramesIn, pConverter->channelsIn, frameCount, pConverter->pShuffleTable, pConverter->format); } -static ma_result ma_channel_converter_process_pcm_frames__simple_mono_expansion(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__mono_in(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { ma_uint64 iFrame; @@ -40846,14 +51387,14 @@ static ma_result ma_channel_converter_process_pcm_frames__simple_mono_expansion( return MA_SUCCESS; } -static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_channel_converter_process_pcm_frames__mono_out(ma_channel_converter* pConverter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount) { ma_uint64 iFrame; + ma_uint32 iChannel; MA_ASSERT(pConverter != NULL); MA_ASSERT(pFramesOut != NULL); MA_ASSERT(pFramesIn != NULL); - MA_ASSERT(pConverter->channelsIn == 2); MA_ASSERT(pConverter->channelsOut == 1); switch (pConverter->format) @@ -40864,7 +51405,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_uint8* pFramesInU8 = (const ma_uint8*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutU8[iFrame] = ma_clip_u8((ma_int16)((ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*2+0]) + ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*2+1])) / 2)); + ma_int32 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += ma_pcm_sample_u8_to_s16_no_scale(pFramesInU8[iFrame*pConverter->channelsIn + iChannel]); + } + + pFramesOutU8[iFrame] = ma_clip_u8(t / pConverter->channelsOut); } } break; @@ -40874,7 +51420,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_int16* pFramesInS16 = (const ma_int16*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutS16[iFrame] = (ma_int16)(((ma_int32)pFramesInS16[iFrame*2+0] + (ma_int32)pFramesInS16[iFrame*2+1]) / 2); + ma_int32 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInS16[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutS16[iFrame] = (ma_int16)(t / pConverter->channelsIn); } } break; @@ -40884,9 +51435,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_uint8* pFramesInS24 = (const ma_uint8*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - ma_int64 s24_0 = ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*2+0)*3]); - ma_int64 s24_1 = ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*2+1)*3]); - ma_pcm_sample_s32_to_s24_no_scale((s24_0 + s24_1) / 2, &pFramesOutS24[iFrame*3]); + ma_int64 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += ma_pcm_sample_s24_to_s32_no_scale(&pFramesInS24[(iFrame*pConverter->channelsIn + iChannel)*3]); + } + + ma_pcm_sample_s32_to_s24_no_scale(t / pConverter->channelsIn, &pFramesOutS24[iFrame*3]); } } break; @@ -40896,7 +51450,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const ma_int32* pFramesInS32 = (const ma_int32*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutS32[iFrame] = (ma_int16)(((ma_int32)pFramesInS32[iFrame*2+0] + (ma_int32)pFramesInS32[iFrame*2+1]) / 2); + ma_int64 t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInS32[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutS32[iFrame] = (ma_int32)(t / pConverter->channelsIn); } } break; @@ -40906,7 +51465,12 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan const float* pFramesInF32 = (const float*)pFramesIn; for (iFrame = 0; iFrame < frameCount; ++iFrame) { - pFramesOutF32[iFrame] = (pFramesInF32[iFrame*2+0] + pFramesInF32[iFrame*2+1]) * 0.5f; + float t = 0; + for (iChannel = 0; iChannel < pConverter->channelsIn; iChannel += 1) { + t += pFramesInF32[iFrame*pConverter->channelsIn + iChannel]; + } + + pFramesOutF32[iFrame] = t / pConverter->channelsIn; } } break; @@ -41037,19 +51601,42 @@ MA_API ma_result ma_channel_converter_process_pcm_frames(ma_channel_converter* p return MA_SUCCESS; } - if (pConverter->isPassthrough) { - return ma_channel_converter_process_pcm_frames__passthrough(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isSimpleShuffle) { - return ma_channel_converter_process_pcm_frames__simple_shuffle(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isSimpleMonoExpansion) { - return ma_channel_converter_process_pcm_frames__simple_mono_expansion(pConverter, pFramesOut, pFramesIn, frameCount); - } else if (pConverter->isStereoToMono) { - return ma_channel_converter_process_pcm_frames__stereo_to_mono(pConverter, pFramesOut, pFramesIn, frameCount); - } else { - return ma_channel_converter_process_pcm_frames__weights(pConverter, pFramesOut, pFramesIn, frameCount); + switch (pConverter->conversionPath) + { + case ma_channel_conversion_path_passthrough: return ma_channel_converter_process_pcm_frames__passthrough(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_mono_out: return ma_channel_converter_process_pcm_frames__mono_out(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_mono_in: return ma_channel_converter_process_pcm_frames__mono_in(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_shuffle: return ma_channel_converter_process_pcm_frames__shuffle(pConverter, pFramesOut, pFramesIn, frameCount); + case ma_channel_conversion_path_weights: + default: + { + return ma_channel_converter_process_pcm_frames__weights(pConverter, pFramesOut, pFramesIn, frameCount); + } } } +MA_API ma_result ma_channel_converter_get_input_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + ma_channel_map_copy_or_default(pChannelMap, channelMapCap, pConverter->pChannelMapIn, pConverter->channelsIn); + + return MA_SUCCESS; +} + +MA_API ma_result ma_channel_converter_get_output_channel_map(const ma_channel_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + ma_channel_map_copy_or_default(pChannelMap, channelMapCap, pConverter->pChannelMapOut, pConverter->channelsOut); + + return MA_SUCCESS; +} + /************************************************************************************************************************************************************** @@ -41063,14 +51650,10 @@ MA_API ma_data_converter_config ma_data_converter_config_init_default() config.ditherMode = ma_dither_mode_none; config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.allowDynamicSampleRate = MA_FALSE; /* Disable dynamic sample rates by default because dynamic rate adjustments should be quite rare and it allows an optimization for cases when the in and out sample rates are the same. */ + config.allowDynamicSampleRate = MA_FALSE; /* Disable dynamic sample rates by default because dynamic rate adjustments should be quite rare and it allows an optimization for cases when the in and out sample rates are the same. */ /* Linear resampling defaults. */ config.resampling.linear.lpfOrder = 1; - config.resampling.linear.lpfNyquistFactor = 1; - - /* Speex resampling defaults. */ - config.resampling.speex.quality = 3; return config; } @@ -41080,18 +51663,168 @@ MA_API ma_data_converter_config ma_data_converter_config_init(ma_format formatIn ma_data_converter_config config = ma_data_converter_config_init_default(); config.formatIn = formatIn; config.formatOut = formatOut; - config.channelsIn = ma_min(channelsIn, MA_MAX_CHANNELS); - config.channelsOut = ma_min(channelsOut, MA_MAX_CHANNELS); + config.channelsIn = channelsIn; + config.channelsOut = channelsOut; config.sampleRateIn = sampleRateIn; config.sampleRateOut = sampleRateOut; return config; } -MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, ma_data_converter* pConverter) + +typedef struct +{ + size_t sizeInBytes; + size_t channelConverterOffset; + size_t resamplerOffset; +} ma_data_converter_heap_layout; + +static ma_bool32 ma_data_converter_config_is_resampler_required(const ma_data_converter_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + return pConfig->allowDynamicSampleRate || pConfig->sampleRateIn != pConfig->sampleRateOut; +} + +static ma_format ma_data_converter_config_get_mid_format(const ma_data_converter_config* pConfig) +{ + MA_ASSERT(pConfig != NULL); + + /* + We want to avoid as much data conversion as possible. The channel converter and linear + resampler both support s16 and f32 natively. We need to decide on the format to use for this + stage. We call this the mid format because it's used in the middle stage of the conversion + pipeline. If the output format is either s16 or f32 we use that one. If that is not the case it + will do the same thing for the input format. If it's neither we just use f32. If we are using a + custom resampling backend, we can only guarantee that f32 will be supported so we'll be forced + to use that if resampling is required. + */ + if (ma_data_converter_config_is_resampler_required(pConfig) && pConfig->resampling.algorithm != ma_resample_algorithm_linear) { + return ma_format_f32; /* <-- Force f32 since that is the only one we can guarantee will be supported by the resampler. */ + } else { + /* */ if (pConfig->formatOut == ma_format_s16 || pConfig->formatOut == ma_format_f32) { + return pConfig->formatOut; + } else if (pConfig->formatIn == ma_format_s16 || pConfig->formatIn == ma_format_f32) { + return pConfig->formatIn; + } else { + return ma_format_f32; + } + } +} + +static ma_channel_converter_config ma_channel_converter_config_init_from_data_converter_config(const ma_data_converter_config* pConfig) +{ + ma_channel_converter_config channelConverterConfig; + + MA_ASSERT(pConfig != NULL); + + channelConverterConfig = ma_channel_converter_config_init(ma_data_converter_config_get_mid_format(pConfig), pConfig->channelsIn, pConfig->pChannelMapIn, pConfig->channelsOut, pConfig->pChannelMapOut, pConfig->channelMixMode); + channelConverterConfig.ppWeights = pConfig->ppChannelWeights; + + return channelConverterConfig; +} + +static ma_resampler_config ma_resampler_config_init_from_data_converter_config(const ma_data_converter_config* pConfig) +{ + ma_resampler_config resamplerConfig; + ma_uint32 resamplerChannels; + + MA_ASSERT(pConfig != NULL); + + /* The resampler is the most expensive part of the conversion process, so we need to do it at the stage where the channel count is at it's lowest. */ + if (pConfig->channelsIn < pConfig->channelsOut) { + resamplerChannels = pConfig->channelsIn; + } else { + resamplerChannels = pConfig->channelsOut; + } + + resamplerConfig = ma_resampler_config_init(ma_data_converter_config_get_mid_format(pConfig), resamplerChannels, pConfig->sampleRateIn, pConfig->sampleRateOut, pConfig->resampling.algorithm); + resamplerConfig.linear = pConfig->resampling.linear; + resamplerConfig.pBackendVTable = pConfig->resampling.pBackendVTable; + resamplerConfig.pBackendUserData = pConfig->resampling.pBackendUserData; + + return resamplerConfig; +} + +static ma_result ma_data_converter_get_heap_layout(const ma_data_converter_config* pConfig, ma_data_converter_heap_layout* pHeapLayout) { ma_result result; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channelsIn == 0 || pConfig->channelsOut == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Channel converter. */ + pHeapLayout->channelConverterOffset = pHeapLayout->sizeInBytes; + { + size_t heapSizeInBytes; + ma_channel_converter_config channelConverterConfig = ma_channel_converter_config_init_from_data_converter_config(pConfig); + + result = ma_channel_converter_get_heap_size(&channelConverterConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += heapSizeInBytes; + } + + /* Resampler. */ + pHeapLayout->resamplerOffset = pHeapLayout->sizeInBytes; + if (ma_data_converter_config_is_resampler_required(pConfig)) { + size_t heapSizeInBytes; + ma_resampler_config resamplerConfig = ma_resampler_config_init_from_data_converter_config(pConfig); + + result = ma_resampler_get_heap_size(&resamplerConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes += heapSizeInBytes; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_get_heap_size(const ma_data_converter_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_data_converter_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_data_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_init_preallocated(const ma_data_converter_config* pConfig, void* pHeap, ma_data_converter* pConverter) +{ + ma_result result; + ma_data_converter_heap_layout heapLayout; ma_format midFormat; + ma_bool32 isResamplingRequired; if (pConverter == NULL) { return MA_INVALID_ARGS; @@ -41099,82 +51832,52 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, MA_ZERO_OBJECT(pConverter); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_data_converter_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - pConverter->config = *pConfig; + pConverter->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); - /* Basic validation. */ - if (pConfig->channelsIn < MA_MIN_CHANNELS || pConfig->channelsOut < MA_MIN_CHANNELS || - pConfig->channelsIn > MA_MAX_CHANNELS || pConfig->channelsOut > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pConverter->formatIn = pConfig->formatIn; + pConverter->formatOut = pConfig->formatOut; + pConverter->channelsIn = pConfig->channelsIn; + pConverter->channelsOut = pConfig->channelsOut; + pConverter->sampleRateIn = pConfig->sampleRateIn; + pConverter->sampleRateOut = pConfig->sampleRateOut; + pConverter->ditherMode = pConfig->ditherMode; /* - We want to avoid as much data conversion as possible. The channel converter and resampler both support s16 and f32 natively. We need to decide - on the format to use for this stage. We call this the mid format because it's used in the middle stage of the conversion pipeline. If the output - format is either s16 or f32 we use that one. If that is not the case it will do the same thing for the input format. If it's neither we just - use f32. + Determine if resampling is required. We need to do this so we can determine an appropriate + mid format to use. If resampling is required, the mid format must be ma_format_f32 since + that is the only one that is guaranteed to supported by custom resampling backends. */ - /* */ if (pConverter->config.formatOut == ma_format_s16 || pConverter->config.formatOut == ma_format_f32) { - midFormat = pConverter->config.formatOut; - } else if (pConverter->config.formatIn == ma_format_s16 || pConverter->config.formatIn == ma_format_f32) { - midFormat = pConverter->config.formatIn; - } else { - midFormat = ma_format_f32; - } + isResamplingRequired = ma_data_converter_config_is_resampler_required(pConfig); + midFormat = ma_data_converter_config_get_mid_format(pConfig); + /* Channel converter. We always initialize this, but we check if it configures itself as a passthrough to determine whether or not it's needed. */ { - ma_uint32 iChannelIn; - ma_uint32 iChannelOut; - ma_channel_converter_config channelConverterConfig; + ma_channel_converter_config channelConverterConfig = ma_channel_converter_config_init_from_data_converter_config(pConfig); - channelConverterConfig = ma_channel_converter_config_init(midFormat, pConverter->config.channelsIn, pConverter->config.channelMapIn, pConverter->config.channelsOut, pConverter->config.channelMapOut, pConverter->config.channelMixMode); - - /* Channel weights. */ - for (iChannelIn = 0; iChannelIn < pConverter->config.channelsIn; iChannelIn += 1) { - for (iChannelOut = 0; iChannelOut < pConverter->config.channelsOut; iChannelOut += 1) { - channelConverterConfig.weights[iChannelIn][iChannelOut] = pConverter->config.channelWeights[iChannelIn][iChannelOut]; - } - } - - result = ma_channel_converter_init(&channelConverterConfig, &pConverter->channelConverter); + result = ma_channel_converter_init_preallocated(&channelConverterConfig, ma_offset_ptr(pHeap, heapLayout.channelConverterOffset), &pConverter->channelConverter); if (result != MA_SUCCESS) { return result; } /* If the channel converter is not a passthrough we need to enable it. Otherwise we can skip it. */ - if (pConverter->channelConverter.isPassthrough == MA_FALSE) { + if (pConverter->channelConverter.conversionPath != ma_channel_conversion_path_passthrough) { pConverter->hasChannelConverter = MA_TRUE; } } - /* Always enable dynamic sample rates if the input sample rate is different because we're always going to need a resampler in this case anyway. */ - if (pConverter->config.resampling.allowDynamicSampleRate == MA_FALSE) { - pConverter->config.resampling.allowDynamicSampleRate = pConverter->config.sampleRateIn != pConverter->config.sampleRateOut; - } - /* Resampler. */ - if (pConverter->config.resampling.allowDynamicSampleRate) { - ma_resampler_config resamplerConfig; - ma_uint32 resamplerChannels; + if (isResamplingRequired) { + ma_resampler_config resamplerConfig = ma_resampler_config_init_from_data_converter_config(pConfig); - /* The resampler is the most expensive part of the conversion process, so we need to do it at the stage where the channel count is at it's lowest. */ - if (pConverter->config.channelsIn < pConverter->config.channelsOut) { - resamplerChannels = pConverter->config.channelsIn; - } else { - resamplerChannels = pConverter->config.channelsOut; - } - - resamplerConfig = ma_resampler_config_init(midFormat, resamplerChannels, pConverter->config.sampleRateIn, pConverter->config.sampleRateOut, pConverter->config.resampling.algorithm); - resamplerConfig.linear.lpfOrder = pConverter->config.resampling.linear.lpfOrder; - resamplerConfig.linear.lpfNyquistFactor = pConverter->config.resampling.linear.lpfNyquistFactor; - resamplerConfig.speex.quality = pConverter->config.resampling.speex.quality; - - result = ma_resampler_init(&resamplerConfig, &pConverter->resampler); + result = ma_resampler_init_preallocated(&resamplerConfig, ma_offset_ptr(pHeap, heapLayout.resamplerOffset), &pConverter->resampler); if (result != MA_SUCCESS) { return result; } @@ -41186,7 +51889,7 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, /* We can simplify pre- and post-format conversion if we have neither channel conversion nor resampling. */ if (pConverter->hasChannelConverter == MA_FALSE && pConverter->hasResampler == MA_FALSE) { /* We have neither channel conversion nor resampling so we'll only need one of pre- or post-format conversion, or none if the input and output formats are the same. */ - if (pConverter->config.formatIn == pConverter->config.formatOut) { + if (pConverter->formatIn == pConverter->formatOut) { /* The formats are the same so we can just pass through. */ pConverter->hasPreFormatConversion = MA_FALSE; pConverter->hasPostFormatConversion = MA_FALSE; @@ -41197,10 +51900,10 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, } } else { /* We have a channel converter and/or resampler so we'll need channel conversion based on the mid format. */ - if (pConverter->config.formatIn != midFormat) { - pConverter->hasPreFormatConversion = MA_TRUE; + if (pConverter->formatIn != midFormat) { + pConverter->hasPreFormatConversion = MA_TRUE; } - if (pConverter->config.formatOut != midFormat) { + if (pConverter->formatOut != midFormat) { pConverter->hasPostFormatConversion = MA_TRUE; } } @@ -41213,17 +51916,86 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, pConverter->isPassthrough = MA_TRUE; } + + /* We now need to determine our execution path. */ + if (pConverter->isPassthrough) { + pConverter->executionPath = ma_data_converter_execution_path_passthrough; + } else { + if (pConverter->channelsIn < pConverter->channelsOut) { + /* Do resampling first, if necessary. */ + MA_ASSERT(pConverter->hasChannelConverter == MA_TRUE); + + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_resample_first; + } else { + pConverter->executionPath = ma_data_converter_execution_path_channels_only; + } + } else { + /* Do channel conversion first, if necessary. */ + if (pConverter->hasChannelConverter) { + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_channels_first; + } else { + pConverter->executionPath = ma_data_converter_execution_path_channels_only; + } + } else { + /* Channel routing not required. */ + if (pConverter->hasResampler) { + pConverter->executionPath = ma_data_converter_execution_path_resample_only; + } else { + pConverter->executionPath = ma_data_converter_execution_path_format_only; + } + } + } + } + return MA_SUCCESS; } -MA_API void ma_data_converter_uninit(ma_data_converter* pConverter) +MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_converter* pConverter) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_data_converter_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_data_converter_init_preallocated(pConfig, pHeap, pConverter); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pConverter->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_data_converter_uninit(ma_data_converter* pConverter, const ma_allocation_callbacks* pAllocationCallbacks) { if (pConverter == NULL) { return; } if (pConverter->hasResampler) { - ma_resampler_uninit(&pConverter->resampler); + ma_resampler_uninit(&pConverter->resampler, pAllocationCallbacks); + } + + ma_channel_converter_uninit(&pConverter->channelConverter, pAllocationCallbacks); + + if (pConverter->_ownsHeap) { + ma_free(pConverter->_pHeap, pAllocationCallbacks); } } @@ -41249,9 +52021,9 @@ static ma_result ma_data_converter_process_pcm_frames__passthrough(ma_data_conve if (pFramesOut != NULL) { if (pFramesIn != NULL) { - ma_copy_memory_64(pFramesOut, pFramesIn, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_copy_memory_64(pFramesOut, pFramesIn, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { - ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } } @@ -41287,9 +52059,9 @@ static ma_result ma_data_converter_process_pcm_frames__format_only(ma_data_conve if (pFramesOut != NULL) { if (pFramesIn != NULL) { - ma_convert_pcm_frames_format(pFramesOut, pConverter->config.formatOut, pFramesIn, pConverter->config.formatIn, frameCount, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOut, pConverter->formatOut, pFramesIn, pConverter->formatIn, frameCount, pConverter->channelsIn, pConverter->ditherMode); } else { - ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + ma_zero_memory_64(pFramesOut, frameCount * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } } @@ -41329,20 +52101,20 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv while (framesProcessedOut < frameCountOut) { ma_uint8 pTempBufferOut[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - const ma_uint32 tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + const ma_uint32 tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); const void* pFramesInThisIteration; /* */ void* pFramesOutThisIteration; ma_uint64 frameCountInThisIteration; ma_uint64 frameCountOutThisIteration; if (pFramesIn != NULL) { - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } else { pFramesInThisIteration = NULL; } if (pFramesOut != NULL) { - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { pFramesOutThisIteration = NULL; } @@ -41350,7 +52122,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv /* Do a pre format conversion if necessary. */ if (pConverter->hasPreFormatConversion) { ma_uint8 pTempBufferIn[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - const ma_uint32 tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + const ma_uint32 tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); frameCountInThisIteration = (frameCountIn - framesProcessedIn); if (frameCountInThisIteration > tempBufferInCap) { @@ -41364,7 +52136,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv } if (pFramesInThisIteration != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.config.format, pFramesInThisIteration, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.format, pFramesInThisIteration, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); } else { MA_ZERO_MEMORY(pTempBufferIn, sizeof(pTempBufferIn)); } @@ -41405,7 +52177,7 @@ static ma_result ma_data_converter_process_pcm_frames__resample_with_format_conv /* If we are doing a post format conversion we need to do that now. */ if (pConverter->hasPostFormatConversion) { if (pFramesOutThisIteration != NULL) { - ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->config.formatOut, pTempBufferOut, pConverter->resampler.config.format, frameCountOutThisIteration, pConverter->resampler.config.channels, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->formatOut, pTempBufferOut, pConverter->resampler.format, frameCountOutThisIteration, pConverter->resampler.channels, pConverter->ditherMode); } } @@ -41482,13 +52254,13 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con ma_uint64 frameCountThisIteration; if (pFramesIn != NULL) { - pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessed * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pFramesInThisIteration = ma_offset_ptr(pFramesIn, framesProcessed * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } else { pFramesInThisIteration = NULL; } if (pFramesOut != NULL) { - pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessed * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pFramesOutThisIteration = ma_offset_ptr(pFramesOut, framesProcessed * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } else { pFramesOutThisIteration = NULL; } @@ -41510,7 +52282,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con } if (pFramesInThisIteration != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pFramesInThisIteration, pConverter->config.formatIn, frameCountThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pFramesInThisIteration, pConverter->formatIn, frameCountThisIteration, pConverter->channelsIn, pConverter->ditherMode); } else { MA_ZERO_MEMORY(pTempBufferIn, sizeof(pTempBufferIn)); } @@ -41544,7 +52316,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con /* If we are doing a post format conversion we need to do that now. */ if (pConverter->hasPostFormatConversion) { if (pFramesOutThisIteration != NULL) { - ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->config.formatOut, pTempBufferOut, pConverter->channelConverter.format, frameCountThisIteration, pConverter->channelConverter.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pFramesOutThisIteration, pConverter->formatOut, pTempBufferOut, pConverter->channelConverter.format, frameCountThisIteration, pConverter->channelConverter.channelsOut, pConverter->ditherMode); } } @@ -41562,7 +52334,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_only(ma_data_con return MA_SUCCESS; } -static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) +static ma_result ma_data_converter_process_pcm_frames__resample_first(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut) { ma_result result; ma_uint64 frameCountIn; @@ -41577,9 +52349,9 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ ma_uint64 tempBufferOutCap; MA_ASSERT(pConverter != NULL); - MA_ASSERT(pConverter->resampler.config.format == pConverter->channelConverter.format); - MA_ASSERT(pConverter->resampler.config.channels == pConverter->channelConverter.channelsIn); - MA_ASSERT(pConverter->resampler.config.channels < pConverter->channelConverter.channelsOut); + MA_ASSERT(pConverter->resampler.format == pConverter->channelConverter.format); + MA_ASSERT(pConverter->resampler.channels == pConverter->channelConverter.channelsIn); + MA_ASSERT(pConverter->resampler.channels < pConverter->channelConverter.channelsOut); frameCountIn = 0; if (pFrameCountIn != NULL) { @@ -41594,8 +52366,8 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ framesProcessedIn = 0; framesProcessedOut = 0; - tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); - tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); + tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsOut); while (framesProcessedOut < frameCountOut) { @@ -41607,10 +52379,10 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ void* pChannelsBufferOut; if (pFramesIn != NULL) { - pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } if (pFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } /* Run input data through the resampler and output it to the temporary buffer. */ @@ -41635,16 +52407,31 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ } /* We need to ensure we don't try to process too many input frames that we run out of room in the output buffer. If this happens we'll end up glitching. */ + + /* + We need to try to predict how many input frames will be required for the resampler. If the + resampler can tell us, we'll use that. Otherwise we'll need to make a best guess. The further + off we are from this, the more wasted format conversions we'll end up doing. + */ + #if 1 { - ma_uint64 requiredInputFrameCount = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration); + ma_uint64 requiredInputFrameCount; + + result = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration, &requiredInputFrameCount); + if (result != MA_SUCCESS) { + /* Fall back to a best guess. */ + requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; + } + if (frameCountInThisIteration > requiredInputFrameCount) { frameCountInThisIteration = requiredInputFrameCount; } } + #endif if (pConverter->hasPreFormatConversion) { if (pFramesIn != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.config.format, pRunningFramesIn, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->resampler.format, pRunningFramesIn, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); pResampleBufferIn = pTempBufferIn; } else { pResampleBufferIn = NULL; @@ -41677,7 +52464,7 @@ static ma_result ma_data_converter_process_pcm_frames__resampling_first(ma_data_ /* Finally we do post format conversion. */ if (pConverter->hasPostFormatConversion) { - ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->config.formatOut, pChannelsBufferOut, pConverter->channelConverter.format, frameCountOutThisIteration, pConverter->channelConverter.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->formatOut, pChannelsBufferOut, pConverter->channelConverter.format, frameCountOutThisIteration, pConverter->channelConverter.channelsOut, pConverter->ditherMode); } } @@ -41718,9 +52505,9 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co ma_uint64 tempBufferOutCap; MA_ASSERT(pConverter != NULL); - MA_ASSERT(pConverter->resampler.config.format == pConverter->channelConverter.format); - MA_ASSERT(pConverter->resampler.config.channels == pConverter->channelConverter.channelsOut); - MA_ASSERT(pConverter->resampler.config.channels < pConverter->channelConverter.channelsIn); + MA_ASSERT(pConverter->resampler.format == pConverter->channelConverter.format); + MA_ASSERT(pConverter->resampler.channels == pConverter->channelConverter.channelsOut); + MA_ASSERT(pConverter->resampler.channels <= pConverter->channelConverter.channelsIn); frameCountIn = 0; if (pFrameCountIn != NULL) { @@ -41737,7 +52524,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co tempBufferInCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsIn); tempBufferMidCap = sizeof(pTempBufferIn) / ma_get_bytes_per_frame(pConverter->channelConverter.format, pConverter->channelConverter.channelsOut); - tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.config.format, pConverter->resampler.config.channels); + tempBufferOutCap = sizeof(pTempBufferOut) / ma_get_bytes_per_frame(pConverter->resampler.format, pConverter->resampler.channels); while (framesProcessedOut < frameCountOut) { ma_uint64 frameCountInThisIteration; @@ -41748,22 +52535,67 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co void* pResampleBufferOut; if (pFramesIn != NULL) { - pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->config.formatIn, pConverter->config.channelsIn)); + pRunningFramesIn = ma_offset_ptr(pFramesIn, framesProcessedIn * ma_get_bytes_per_frame(pConverter->formatIn, pConverter->channelsIn)); } if (pFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->config.formatOut, pConverter->config.channelsOut)); + pRunningFramesOut = ma_offset_ptr(pFramesOut, framesProcessedOut * ma_get_bytes_per_frame(pConverter->formatOut, pConverter->channelsOut)); } - /* Run input data through the channel converter and output it to the temporary buffer. */ - frameCountInThisIteration = (frameCountIn - framesProcessedIn); + /* + Before doing any processing we need to determine how many frames we should try processing + this iteration, for both input and output. The resampler requires us to perform format and + channel conversion before passing any data into it. If we get our input count wrong, we'll + end up peforming redundant pre-processing. This isn't the end of the world, but it does + result in some inefficiencies proportionate to how far our estimates are off. + If the resampler has a means to calculate exactly how much we'll need, we'll use that. + Otherwise we'll make a best guess. In order to do this, we'll need to calculate the output + frame count first. + */ + frameCountOutThisIteration = (frameCountOut - framesProcessedOut); + if (frameCountOutThisIteration > tempBufferMidCap) { + frameCountOutThisIteration = tempBufferMidCap; + } + + if (pConverter->hasPostFormatConversion) { + if (frameCountOutThisIteration > tempBufferOutCap) { + frameCountOutThisIteration = tempBufferOutCap; + } + } + + /* Now that we have the output frame count we can determine the input frame count. */ + frameCountInThisIteration = (frameCountIn - framesProcessedIn); if (pConverter->hasPreFormatConversion) { if (frameCountInThisIteration > tempBufferInCap) { frameCountInThisIteration = tempBufferInCap; } + } + if (frameCountInThisIteration > tempBufferMidCap) { + frameCountInThisIteration = tempBufferMidCap; + } + + #if 1 + { + ma_uint64 requiredInputFrameCount; + + result = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration, &requiredInputFrameCount); + if (result != MA_SUCCESS) { + /* Fall back to a best guess. */ + requiredInputFrameCount = (frameCountOutThisIteration * pConverter->resampler.sampleRateIn) / pConverter->resampler.sampleRateOut; + } + + if (frameCountInThisIteration > requiredInputFrameCount) { + frameCountInThisIteration = requiredInputFrameCount; + } + } + #endif + + + /* Pre format conversion. */ + if (pConverter->hasPreFormatConversion) { if (pRunningFramesIn != NULL) { - ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pRunningFramesIn, pConverter->config.formatIn, frameCountInThisIteration, pConverter->config.channelsIn, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pTempBufferIn, pConverter->channelConverter.format, pRunningFramesIn, pConverter->formatIn, frameCountInThisIteration, pConverter->channelsIn, pConverter->ditherMode); pChannelsBufferIn = pTempBufferIn; } else { pChannelsBufferIn = NULL; @@ -41772,43 +52604,15 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co pChannelsBufferIn = pRunningFramesIn; } - /* - We can't convert more frames than will fit in the output buffer. We shouldn't actually need to do this check because the channel count is always reduced - in this case which means we should always have capacity, but I'm leaving it here just for safety for future maintenance. - */ - if (frameCountInThisIteration > tempBufferMidCap) { - frameCountInThisIteration = tempBufferMidCap; - } - - /* - Make sure we don't read any more input frames than we need to fill the output frame count. If we do this we will end up in a situation where we lose some - input samples and will end up glitching. - */ - frameCountOutThisIteration = (frameCountOut - framesProcessedOut); - if (frameCountOutThisIteration > tempBufferMidCap) { - frameCountOutThisIteration = tempBufferMidCap; - } - - if (pConverter->hasPostFormatConversion) { - ma_uint64 requiredInputFrameCount; - - if (frameCountOutThisIteration > tempBufferOutCap) { - frameCountOutThisIteration = tempBufferOutCap; - } - - requiredInputFrameCount = ma_resampler_get_required_input_frame_count(&pConverter->resampler, frameCountOutThisIteration); - if (frameCountInThisIteration > requiredInputFrameCount) { - frameCountInThisIteration = requiredInputFrameCount; - } - } + /* Channel conversion. */ result = ma_channel_converter_process_pcm_frames(&pConverter->channelConverter, pTempBufferMid, pChannelsBufferIn, frameCountInThisIteration); if (result != MA_SUCCESS) { return result; } - /* At this point we have converted the channels to the output channel count which we now need to resample. */ + /* Resampling. */ if (pConverter->hasPostFormatConversion) { pResampleBufferOut = pTempBufferOut; } else { @@ -41820,13 +52624,15 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co return result; } - /* Finally we can do the post format conversion. */ + + /* Post format conversion. */ if (pConverter->hasPostFormatConversion) { if (pRunningFramesOut != NULL) { - ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->config.formatOut, pResampleBufferOut, pConverter->resampler.config.format, frameCountOutThisIteration, pConverter->config.channelsOut, pConverter->config.ditherMode); + ma_convert_pcm_frames_format(pRunningFramesOut, pConverter->formatOut, pResampleBufferOut, pConverter->resampler.format, frameCountOutThisIteration, pConverter->channelsOut, pConverter->ditherMode); } } + framesProcessedIn += frameCountInThisIteration; framesProcessedOut += frameCountOutThisIteration; @@ -41854,46 +52660,15 @@ MA_API ma_result ma_data_converter_process_pcm_frames(ma_data_converter* pConver return MA_INVALID_ARGS; } - if (pConverter->isPassthrough) { - return ma_data_converter_process_pcm_frames__passthrough(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - - /* - Here is where the real work is done. Getting here means we're not using a passthrough and we need to move the data through each of the relevant stages. The order - of our stages depends on the input and output channel count. If the input channels is less than the output channels we want to do sample rate conversion first so - that it has less work (resampling is the most expensive part of format conversion). - */ - if (pConverter->config.channelsIn < pConverter->config.channelsOut) { - /* Do resampling first, if necessary. */ - MA_ASSERT(pConverter->hasChannelConverter == MA_TRUE); - - if (pConverter->hasResampler) { - /* Resampling first. */ - return ma_data_converter_process_pcm_frames__resampling_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Resampling not required. */ - return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } else { - /* Do channel conversion first, if necessary. */ - if (pConverter->hasChannelConverter) { - if (pConverter->hasResampler) { - /* Channel routing first. */ - return ma_data_converter_process_pcm_frames__channels_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* Resampling not required. */ - return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } else { - /* Channel routing not required. */ - if (pConverter->hasResampler) { - /* Resampling only. */ - return ma_data_converter_process_pcm_frames__resample_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } else { - /* No channel routing nor resampling required. Just format conversion. */ - return ma_data_converter_process_pcm_frames__format_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); - } - } + switch (pConverter->executionPath) + { + case ma_data_converter_execution_path_passthrough: return ma_data_converter_process_pcm_frames__passthrough(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_format_only: return ma_data_converter_process_pcm_frames__format_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_channels_only: return ma_data_converter_process_pcm_frames__channels_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_resample_only: return ma_data_converter_process_pcm_frames__resample_only(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_resample_first: return ma_data_converter_process_pcm_frames__resample_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + case ma_data_converter_execution_path_channels_first: return ma_data_converter_process_pcm_frames__channels_first(pConverter, pFramesIn, pFrameCountIn, pFramesOut, pFrameCountOut); + default: return MA_INVALID_OPERATION; /* Should never hit this. */ } } @@ -41923,32 +52698,6 @@ MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, return ma_resampler_set_rate_ratio(&pConverter->resampler, ratioInOut); } -MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount) -{ - if (pConverter == NULL) { - return 0; - } - - if (pConverter->hasResampler) { - return ma_resampler_get_required_input_frame_count(&pConverter->resampler, outputFrameCount); - } else { - return outputFrameCount; /* 1:1 */ - } -} - -MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount) -{ - if (pConverter == NULL) { - return 0; - } - - if (pConverter->hasResampler) { - return ma_resampler_get_expected_output_frame_count(&pConverter->resampler, inputFrameCount); - } else { - return inputFrameCount; /* 1:1 */ - } -} - MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter) { if (pConverter == NULL) { @@ -41975,6 +52724,90 @@ MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* p return 0; /* No latency without a resampler. */ } +MA_API ma_result ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount, ma_uint64* pInputFrameCount) +{ + if (pInputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pInputFrameCount = 0; + + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasResampler) { + return ma_resampler_get_required_input_frame_count(&pConverter->resampler, outputFrameCount, pInputFrameCount); + } else { + *pInputFrameCount = outputFrameCount; /* 1:1 */ + return MA_SUCCESS; + } +} + +MA_API ma_result ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount, ma_uint64* pOutputFrameCount) +{ + if (pOutputFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + *pOutputFrameCount = 0; + + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasResampler) { + return ma_resampler_get_expected_output_frame_count(&pConverter->resampler, inputFrameCount, pOutputFrameCount); + } else { + *pOutputFrameCount = inputFrameCount; /* 1:1 */ + return MA_SUCCESS; + } +} + +MA_API ma_result ma_data_converter_get_input_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasChannelConverter) { + ma_channel_converter_get_output_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pConverter->channelsOut); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_get_output_channel_map(const ma_data_converter* pConverter, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pConverter == NULL || pChannelMap == NULL) { + return MA_INVALID_ARGS; + } + + if (pConverter->hasChannelConverter) { + ma_channel_converter_get_input_channel_map(&pConverter->channelConverter, pChannelMap, channelMapCap); + } else { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pConverter->channelsIn); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_converter_reset(ma_data_converter* pConverter) +{ + if (pConverter == NULL) { + return MA_INVALID_ARGS; + } + + /* There's nothing to do if we're not resampling. */ + if (pConverter->hasResampler == MA_FALSE) { + return MA_SUCCESS; + } + + return ma_resampler_reset(&pConverter->resampler); +} + /************************************************************************************************************************************************************** @@ -41982,7 +52815,32 @@ MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* p Channel Maps **************************************************************************************************************************************************************/ -MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_uint32 channelIndex) +static ma_channel ma_channel_map_init_standard_channel(ma_standard_channel_map standardChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex); + +MA_API ma_channel ma_channel_map_get_channel(const ma_channel* pChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) +{ + if (pChannelMap == NULL) { + return ma_channel_map_init_standard_channel(ma_standard_channel_map_default, channelCount, channelIndex); + } else { + if (channelIndex >= channelCount) { + return MA_CHANNEL_NONE; + } + + return pChannelMap[channelIndex]; + } +} + +MA_API void ma_channel_map_init_blank(ma_channel* pChannelMap, ma_uint32 channels) +{ + if (pChannelMap == NULL) { + return; + } + + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channels); +} + + +static ma_channel ma_channel_map_init_standard_channel_microsoft(ma_uint32 channelCount, ma_uint32 channelIndex) { if (channelCount == 0 || channelIndex >= channelCount) { return MA_CHANNEL_NONE; @@ -41991,7 +52849,7 @@ MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_ /* This is the Microsoft channel map. Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config */ switch (channelCount) { - case 0: return MA_CHANNEL_NONE; + case 0: return MA_CHANNEL_NONE; case 1: { @@ -42096,645 +52954,619 @@ MA_API ma_channel ma_channel_map_get_default_channel(ma_uint32 channelCount, ma_ return MA_CHANNEL_NONE; } -MA_API ma_channel ma_channel_map_get_channel(const ma_channel* pChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) +static ma_channel ma_channel_map_init_standard_channel_alsa(ma_uint32 channelCount, ma_uint32 channelIndex) { - if (pChannelMap == NULL) { - return ma_channel_map_get_default_channel(channelCount, channelIndex); - } else { - if (channelIndex >= channelCount) { - return MA_CHANNEL_NONE; - } - - return pChannelMap[channelIndex]; - } -} - - -MA_API void ma_channel_map_init_blank(ma_uint32 channels, ma_channel* pChannelMap) -{ - if (pChannelMap == NULL) { - return; - } - - MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channels); -} - -static void ma_get_standard_channel_map_microsoft(ma_uint32 channels, ma_channel* pChannelMap) -{ - /* Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config */ - switch (channels) + switch (channelCount) { + case 0: return MA_CHANNEL_NONE; + case 1: { - pChannelMap[0] = MA_CHANNEL_MONO; + return MA_CHANNEL_MONO; } break; case 2: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } } break; - case 3: /* Not defined, but best guess. */ + case 3: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } } break; case 4: { -#ifndef MA_USE_QUAD_MICROSOFT_CHANNEL_MAP - /* Surround. Using the Surround profile has the advantage of the 3rd channel (MA_CHANNEL_FRONT_CENTER) mapping nicely with higher channel counts. */ - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_CENTER; -#else - /* Quad. */ - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; -#endif + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + case 6: return MA_CHANNEL_BACK_CENTER; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + case 6: return MA_CHANNEL_SIDE_LEFT; + case 7: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_rfc3551(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_CENTER; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_SIDE_LEFT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_FRONT_RIGHT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + } + } break; + } + + if (channelCount > 6) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 6)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_flac(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_LEFT; + case 5: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_CENTER; + case 5: return MA_CHANNEL_SIDE_LEFT; + case 6: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_LFE; + case 4: return MA_CHANNEL_BACK_LEFT; + case 5: return MA_CHANNEL_BACK_RIGHT; + case 6: return MA_CHANNEL_SIDE_LEFT; + case 7: return MA_CHANNEL_SIDE_RIGHT; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_vorbis(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + case 6: return MA_CHANNEL_LFE; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_LEFT; + case 6: return MA_CHANNEL_BACK_RIGHT; + case 7: return MA_CHANNEL_LFE; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_sound4(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 5: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + } + } break; + + case 6: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_BACK_LEFT; + case 4: return MA_CHANNEL_BACK_RIGHT; + case 5: return MA_CHANNEL_LFE; + } + } break; + + case 7: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_CENTER; + case 6: return MA_CHANNEL_LFE; + } + } break; + + case 8: + default: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_CENTER; + case 2: return MA_CHANNEL_FRONT_RIGHT; + case 3: return MA_CHANNEL_SIDE_LEFT; + case 4: return MA_CHANNEL_SIDE_RIGHT; + case 5: return MA_CHANNEL_BACK_LEFT; + case 6: return MA_CHANNEL_BACK_RIGHT; + case 7: return MA_CHANNEL_LFE; + } + } break; + } + + if (channelCount > 8) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 8)); + } + } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; +} + +static ma_channel ma_channel_map_init_standard_channel_sndio(ma_uint32 channelCount, ma_uint32 channelIndex) +{ + switch (channelCount) + { + case 0: return MA_CHANNEL_NONE; + + case 1: + { + return MA_CHANNEL_MONO; + } break; + + case 2: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + } + } break; + + case 3: /* No defined, but best guess. */ + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_FRONT_CENTER; + } + } break; + + case 4: + { + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + } } break; case 5: /* Not defined, but best guess. */ { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[5] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 7: /* Not defined, but best guess. */ - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_CENTER; - pChannelMap[5] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[6] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; } - } - } -} - -static void ma_get_standard_channel_map_alsa(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_BACK_CENTER; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_rfc3551(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_CENTER; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 6; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-6)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_flac(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_CENTER; - pChannelMap[5] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[6] = MA_CHANNEL_SIDE_RIGHT; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[3] = MA_CHANNEL_LFE; - pChannelMap[4] = MA_CHANNEL_BACK_LEFT; - pChannelMap[5] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_vorbis(ma_uint32 channels, ma_channel* pChannelMap) -{ - /* In Vorbis' type 0 channel mapping, the first two channels are not always the standard left/right - it will have the center speaker where the right usually goes. Why?! */ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_BACK_LEFT; - pChannelMap[4] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - pChannelMap[6] = MA_CHANNEL_LFE; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[2] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[3] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[4] = MA_CHANNEL_SIDE_RIGHT; - pChannelMap[5] = MA_CHANNEL_BACK_LEFT; - pChannelMap[6] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[7] = MA_CHANNEL_LFE; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < channels; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_sound4(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 6: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - } break; - - case 7: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_BACK_CENTER; - pChannelMap[6] = MA_CHANNEL_LFE; - } break; - - case 8: - default: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; - pChannelMap[6] = MA_CHANNEL_SIDE_LEFT; - pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; - } break; - } - - /* Remainder. */ - if (channels > 8) { - ma_uint32 iChannel; - for (iChannel = 8; iChannel < MA_MAX_CHANNELS; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-8)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } - } - } -} - -static void ma_get_standard_channel_map_sndio(ma_uint32 channels, ma_channel* pChannelMap) -{ - switch (channels) - { - case 1: - { - pChannelMap[0] = MA_CHANNEL_MONO; - } break; - - case 2: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - } break; - - case 3: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_FRONT_CENTER; - } break; - - case 4: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - } break; - - case 5: - { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; } break; case 6: default: { - pChannelMap[0] = MA_CHANNEL_FRONT_LEFT; - pChannelMap[1] = MA_CHANNEL_FRONT_RIGHT; - pChannelMap[2] = MA_CHANNEL_BACK_LEFT; - pChannelMap[3] = MA_CHANNEL_BACK_RIGHT; - pChannelMap[4] = MA_CHANNEL_FRONT_CENTER; - pChannelMap[5] = MA_CHANNEL_LFE; + switch (channelIndex) { + case 0: return MA_CHANNEL_FRONT_LEFT; + case 1: return MA_CHANNEL_FRONT_RIGHT; + case 2: return MA_CHANNEL_BACK_LEFT; + case 3: return MA_CHANNEL_BACK_RIGHT; + case 4: return MA_CHANNEL_FRONT_CENTER; + case 5: return MA_CHANNEL_LFE; + } } break; } - /* Remainder. */ - if (channels > 6) { - ma_uint32 iChannel; - for (iChannel = 6; iChannel < channels && iChannel < MA_MAX_CHANNELS; ++iChannel) { - if (iChannel < MA_MAX_CHANNELS) { - pChannelMap[iChannel] = (ma_channel)(MA_CHANNEL_AUX_0 + (iChannel-6)); - } else { - pChannelMap[iChannel] = MA_CHANNEL_NONE; - } + if (channelCount > 6) { + if (channelIndex < 32) { /* We have 32 AUX channels. */ + return (ma_channel)(MA_CHANNEL_AUX_0 + (channelIndex - 6)); } } + + /* Getting here means we don't know how to map the channel position so just return MA_CHANNEL_NONE. */ + return MA_CHANNEL_NONE; } -MA_API void ma_get_standard_channel_map(ma_standard_channel_map standardChannelMap, ma_uint32 channels, ma_channel* pChannelMap) + +static ma_channel ma_channel_map_init_standard_channel(ma_standard_channel_map standardChannelMap, ma_uint32 channelCount, ma_uint32 channelIndex) { + if (channelCount == 0 || channelIndex >= channelCount) { + return MA_CHANNEL_NONE; + } + switch (standardChannelMap) { case ma_standard_channel_map_alsa: { - ma_get_standard_channel_map_alsa(channels, pChannelMap); + return ma_channel_map_init_standard_channel_alsa(channelCount, channelIndex); } break; case ma_standard_channel_map_rfc3551: { - ma_get_standard_channel_map_rfc3551(channels, pChannelMap); + return ma_channel_map_init_standard_channel_rfc3551(channelCount, channelIndex); } break; case ma_standard_channel_map_flac: { - ma_get_standard_channel_map_flac(channels, pChannelMap); + return ma_channel_map_init_standard_channel_flac(channelCount, channelIndex); } break; case ma_standard_channel_map_vorbis: { - ma_get_standard_channel_map_vorbis(channels, pChannelMap); + return ma_channel_map_init_standard_channel_vorbis(channelCount, channelIndex); } break; case ma_standard_channel_map_sound4: { - ma_get_standard_channel_map_sound4(channels, pChannelMap); + return ma_channel_map_init_standard_channel_sound4(channelCount, channelIndex); } break; case ma_standard_channel_map_sndio: { - ma_get_standard_channel_map_sndio(channels, pChannelMap); + return ma_channel_map_init_standard_channel_sndio(channelCount, channelIndex); } break; case ma_standard_channel_map_microsoft: /* Also default. */ /*case ma_standard_channel_map_default;*/ default: { - ma_get_standard_channel_map_microsoft(channels, pChannelMap); + return ma_channel_map_init_standard_channel_microsoft(channelCount, channelIndex); } break; } } +MA_API void ma_channel_map_init_standard(ma_standard_channel_map standardChannelMap, ma_channel* pChannelMap, size_t channelMapCap, ma_uint32 channels) +{ + ma_uint32 iChannel; + + if (pChannelMap == NULL || channelMapCap == 0 || channels == 0) { + return; + } + + for (iChannel = 0; iChannel < channels; iChannel += 1) { + if (channelMapCap == 0) { + break; /* Ran out of room. */ + } + + pChannelMap[0] = ma_channel_map_init_standard_channel(standardChannelMap, channels, iChannel); + pChannelMap += 1; + channelMapCap -= 1; + } +} + MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels) { if (pOut != NULL && pIn != NULL && channels > 0) { @@ -42742,7 +53574,7 @@ MA_API void ma_channel_map_copy(ma_channel* pOut, const ma_channel* pIn, ma_uint } } -MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* pIn, ma_uint32 channels) +MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, size_t channelMapCapOut, const ma_channel* pIn, ma_uint32 channels) { if (pOut == NULL || channels == 0) { return; @@ -42751,16 +53583,12 @@ MA_API void ma_channel_map_copy_or_default(ma_channel* pOut, const ma_channel* p if (pIn != NULL) { ma_channel_map_copy(pOut, pIn, channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, channels, pOut); + ma_channel_map_init_standard(ma_standard_channel_map_default, pOut, channelMapCapOut, channels); } } -MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pChannelMap) +MA_API ma_bool32 ma_channel_map_is_valid(const ma_channel* pChannelMap, ma_uint32 channels) { - if (pChannelMap == NULL) { - return MA_FALSE; - } - /* A channel count of 0 is invalid. */ if (channels == 0) { return MA_FALSE; @@ -42770,7 +53598,7 @@ MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pCha if (channels > 1) { ma_uint32 iChannel; for (iChannel = 0; iChannel < channels; ++iChannel) { - if (pChannelMap[iChannel] == MA_CHANNEL_MONO) { + if (ma_channel_map_get_channel(pChannelMap, channels, iChannel) == MA_CHANNEL_MONO) { return MA_FALSE; } } @@ -42779,7 +53607,7 @@ MA_API ma_bool32 ma_channel_map_valid(ma_uint32 channels, const ma_channel* pCha return MA_TRUE; } -MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pChannelMapA, const ma_channel* pChannelMapB) +MA_API ma_bool32 ma_channel_map_is_equal(const ma_channel* pChannelMapA, const ma_channel* pChannelMapB, ma_uint32 channels) { ma_uint32 iChannel; @@ -42796,7 +53624,7 @@ MA_API ma_bool32 ma_channel_map_equal(ma_uint32 channels, const ma_channel* pCha return MA_TRUE; } -MA_API ma_bool32 ma_channel_map_blank(ma_uint32 channels, const ma_channel* pChannelMap) +MA_API ma_bool32 ma_channel_map_is_blank(const ma_channel* pChannelMap, ma_uint32 channels) { ma_uint32 iChannel; @@ -42839,8 +53667,6 @@ MA_API ma_uint64 ma_convert_frames(void* pOut, ma_uint64 frameCountOut, ma_forma ma_data_converter_config config; config = ma_data_converter_config_init(formatIn, formatOut, channelsIn, channelsOut, sampleRateIn, sampleRateOut); - ma_get_standard_channel_map(ma_standard_channel_map_default, channelsOut, config.channelMapOut); - ma_get_standard_channel_map(ma_standard_channel_map_default, channelsIn, config.channelMapIn); config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); return ma_convert_frames_ex(pOut, frameCountOut, pIn, frameCountIn, &config); @@ -42855,13 +53681,31 @@ MA_API ma_uint64 ma_convert_frames_ex(void* pOut, ma_uint64 frameCountOut, const return 0; } - result = ma_data_converter_init(pConfig, &converter); + result = ma_data_converter_init(pConfig, NULL, &converter); if (result != MA_SUCCESS) { return 0; /* Failed to initialize the data converter. */ } if (pOut == NULL) { - frameCountOut = ma_data_converter_get_expected_output_frame_count(&converter, frameCountIn); + result = ma_data_converter_get_expected_output_frame_count(&converter, frameCountIn, &frameCountOut); + if (result != MA_SUCCESS) { + if (result == MA_NOT_IMPLEMENTED) { + /* No way to calculate the number of frames, so we'll need to brute force it and loop. */ + frameCountOut = 0; + + while (frameCountIn > 0) { + ma_uint64 framesProcessedIn = frameCountIn; + ma_uint64 framesProcessedOut = 0xFFFFFFFF; + + result = ma_data_converter_process_pcm_frames(&converter, pIn, &framesProcessedIn, NULL, &framesProcessedOut); + if (result != MA_SUCCESS) { + break; + } + + frameCountIn -= framesProcessedIn; + } + } + } } else { result = ma_data_converter_process_pcm_frames(&converter, pIn, &frameCountIn, pOut, &frameCountOut); if (result != MA_SUCCESS) { @@ -42869,7 +53713,7 @@ MA_API ma_uint64 ma_convert_frames_ex(void* pOut, ma_uint64 frameCountOut, const } } - ma_data_converter_uninit(&converter); + ma_data_converter_uninit(&converter, NULL); return frameCountOut; } @@ -43038,7 +53882,7 @@ MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppB return MA_SUCCESS; } -MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut) +MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes) { ma_uint32 readOffset; ma_uint32 readOffsetInBytes; @@ -43050,11 +53894,6 @@ MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBuffer return MA_INVALID_ARGS; } - /* Validate the buffer. */ - if (pBufferOut != ma_rb__get_read_ptr(pRB)) { - return MA_INVALID_ARGS; - } - readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); @@ -43129,7 +53968,7 @@ MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** pp return MA_SUCCESS; } -MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBufferOut) +MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes) { ma_uint32 writeOffset; ma_uint32 writeOffsetInBytes; @@ -43141,11 +53980,6 @@ MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBuffe return MA_INVALID_ARGS; } - /* Validate the buffer. */ - if (pBufferOut != ma_rb__get_write_ptr(pRB)) { - return MA_INVALID_ARGS; - } - writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); @@ -43429,13 +54263,13 @@ MA_API ma_result ma_pcm_rb_acquire_read(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames return MA_SUCCESS; } -MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut) +MA_API ma_result ma_pcm_rb_commit_read(ma_pcm_rb* pRB, ma_uint32 sizeInFrames) { if (pRB == NULL) { return MA_INVALID_ARGS; } - return ma_rb_commit_read(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB), pBufferOut); + return ma_rb_commit_read(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB)); } MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrames, void** ppBufferOut) @@ -43458,13 +54292,13 @@ MA_API ma_result ma_pcm_rb_acquire_write(ma_pcm_rb* pRB, ma_uint32* pSizeInFrame return MA_SUCCESS; } -MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames, void* pBufferOut) +MA_API ma_result ma_pcm_rb_commit_write(ma_pcm_rb* pRB, ma_uint32 sizeInFrames) { if (pRB == NULL) { return MA_INVALID_ARGS; } - return ma_rb_commit_write(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB), pBufferOut); + return ma_rb_commit_write(&pRB->rb, sizeInFrames * ma_pcm_rb_get_bpf(pRB)); } MA_API ma_result ma_pcm_rb_seek_read(ma_pcm_rb* pRB, ma_uint32 offsetInFrames) @@ -43665,19 +54499,33 @@ MA_API const char* ma_result_description(ma_result result) MA_API void* ma_malloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { - return ma__malloc_from_callbacks(sz, pAllocationCallbacks); + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } else { + return NULL; /* Do not fall back to the default implementation. */ + } } else { return ma__malloc_default(sz, NULL); } } +MA_API void* ma_calloc(size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) +{ + void* p = ma_malloc(sz, pAllocationCallbacks); + if (p != NULL) { + MA_ZERO_MEMORY(p, sz); + } + + return p; +} + MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { if (pAllocationCallbacks->onRealloc != NULL) { return pAllocationCallbacks->onRealloc(p, sz, pAllocationCallbacks->pUserData); } else { - return NULL; /* This requires a native implementation of realloc(). */ + return NULL; /* Do not fall back to the default implementation. */ } } else { return ma__realloc_default(p, sz, NULL); @@ -43686,8 +54534,16 @@ MA_API void* ma_realloc(void* p, size_t sz, const ma_allocation_callbacks* pAllo MA_API void ma_free(void* p, const ma_allocation_callbacks* pAllocationCallbacks) { + if (p == NULL) { + return; + } + if (pAllocationCallbacks != NULL) { - ma__free_from_callbacks(p, pAllocationCallbacks); + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } else { + return; /* Do no fall back to the default implementation. */ + } } else { ma__free_default(p, NULL); } @@ -43792,11 +54648,6 @@ MA_API ma_result ma_data_source_init(const ma_data_source_config* pConfig, ma_da pDataSourceBase->pNext = NULL; pDataSourceBase->onGetNext = NULL; - /* Compatibility: Need to make a copy of the callbacks. This will be removed in version 0.11. */ - if (pConfig->vtable != NULL) { - pDataSourceBase->cb = *pConfig->vtable; - } - return MA_SUCCESS; } @@ -43812,7 +54663,6 @@ MA_API void ma_data_source_uninit(ma_data_source* pDataSource) */ } -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_data_source** ppCurrentDataSource) { ma_data_source_base* pCurrentDataSource = (ma_data_source_base*)pDataSource; @@ -43839,63 +54689,76 @@ static ma_result ma_data_source_resolve_current(ma_data_source* pDataSource, ma_ return MA_SUCCESS; } -static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) +static ma_result ma_data_source_read_pcm_frames_within_range(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; - + ma_result result; + ma_uint64 framesRead = 0; + ma_bool32 loop = ma_data_source_is_looping(pDataSource); + if (pDataSourceBase == NULL) { return MA_AT_END; } - if (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE)) { - /* No range is set - just read like normal. The data source itself will tell us when the end is reached. */ - return pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + if ((pDataSourceBase->vtable->flags & MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT) != 0 || (pDataSourceBase->rangeEndInFrames == ~((ma_uint64)0) && (pDataSourceBase->loopEndInFrames == ~((ma_uint64)0) || loop == MA_FALSE))) { + /* Either the data source is self-managing the range, or no range is set - just read like normal. The data source itself will tell us when the end is reached. */ + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); } else { /* Need to clamp to within the range. */ - ma_result result; ma_uint64 cursor; - ma_uint64 framesRead = 0; - ma_uint64 rangeEnd; result = ma_data_source_get_cursor_in_pcm_frames(pDataSourceBase, &cursor); if (result != MA_SUCCESS) { /* Failed to retrieve the cursor. Cannot read within a range or loop points. Just read like normal - this may happen for things like noise data sources where it doesn't really matter. */ - return pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, pFramesRead); - } + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + } else { + ma_uint64 rangeEnd; - /* We have the cursor. We need to make sure we don't read beyond our range. */ - rangeEnd = pDataSourceBase->rangeEndInFrames; + /* We have the cursor. We need to make sure we don't read beyond our range. */ + rangeEnd = pDataSourceBase->rangeEndInFrames; - /* If looping, make sure we're within range. */ - if (loop) { - if (pDataSourceBase->loopEndInFrames != ~((ma_uint64)0)) { - rangeEnd = ma_min(rangeEnd, pDataSourceBase->rangeBegInFrames + pDataSourceBase->loopEndInFrames); + /* If looping, make sure we're within range. */ + if (loop) { + if (pDataSourceBase->loopEndInFrames != ~((ma_uint64)0)) { + rangeEnd = ma_min(rangeEnd, pDataSourceBase->rangeBegInFrames + pDataSourceBase->loopEndInFrames); + } + } + + if (frameCount > (rangeEnd - cursor) && rangeEnd != ~((ma_uint64)0)) { + frameCount = (rangeEnd - cursor); + } + + /* + If the cursor is sitting on the end of the range the frame count will be set to 0 which can + result in MA_INVALID_ARGS. In this case, we don't want to try reading, but instead return + MA_AT_END so the higher level function can know about it. + */ + if (frameCount > 0) { + result = pDataSourceBase->vtable->onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); + } else { + result = MA_AT_END; /* The cursor is sitting on the end of the range which means we're at the end. */ } } - - if (frameCount > (rangeEnd - cursor) && rangeEnd != ~((ma_uint64)0)) { - frameCount = (rangeEnd - cursor); - } - - result = pDataSourceBase->cb.onRead(pDataSourceBase, pFramesOut, frameCount, &framesRead); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - /* We need to make sure MA_AT_END is returned if we hit the end of the range. */ - if (result != MA_AT_END && framesRead == 0) { - result = MA_AT_END; - } - - return result; } -} -#endif -MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) + if (pFramesRead != NULL) { + *pFramesRead = framesRead; + } + + /* We need to make sure MA_AT_END is returned if we hit the end of the range. */ + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_result result = MA_SUCCESS; ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_data_source_base* pCurrentDataSource; @@ -43904,26 +54767,33 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi ma_format format; ma_uint32 channels; ma_uint32 emptyLoopCounter = 0; /* Keeps track of how many times 0 frames have been read. For infinite loop detection of sounds with no audio data. */ + ma_bool32 loop; if (pFramesRead != NULL) { *pFramesRead = 0; } + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pDataSourceBase == NULL) { return MA_INVALID_ARGS; } + loop = ma_data_source_is_looping(pDataSource); + /* We need to know the data format so we can advance the output buffer as we read frames. If this fails, chaining will not work and we'll just read as much as we can from the current source. */ - if (ma_data_source_get_data_format(pDataSource, &format, &channels, NULL) != MA_SUCCESS) { + if (ma_data_source_get_data_format(pDataSource, &format, &channels, NULL, NULL, 0) != MA_SUCCESS) { result = ma_data_source_resolve_current(pDataSource, (ma_data_source**)&pCurrentDataSource); if (result != MA_SUCCESS) { return result; } - return ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pFramesOut, frameCount, pFramesRead, loop); + return ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pFramesOut, frameCount, pFramesRead); } /* @@ -43946,7 +54816,7 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi break; } - result = ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pRunningFramesOut, framesRemaining, &framesProcessed, loop); + result = ma_data_source_read_pcm_frames_within_range(pCurrentDataSource, pRunningFramesOut, framesRemaining, &framesProcessed); totalFramesProcessed += framesProcessed; /* @@ -43958,10 +54828,18 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi } /* - We can determine if we've reached the end by checking the return value of the onRead() - callback. To loop back to the start, all we need to do is seek back to the first frame. + We can determine if we've reached the end by checking if ma_data_source_read_pcm_frames_within_range() returned + MA_AT_END. To loop back to the start, all we need to do is seek back to the first frame. */ if (result == MA_AT_END) { + /* + The result needs to be reset back to MA_SUCCESS (from MA_AT_END) so that we don't + accidentally return MA_AT_END when data has been read in prior loop iterations. at the + end of this function, the result will be checked for MA_SUCCESS, and if the total + number of frames processed is 0, will be explicitly set to MA_AT_END. + */ + result = MA_SUCCESS; + /* We reached the end. If we're looping, we just loop back to the start of the current data source. If we're not looping we need to check if we have another in the chain, and @@ -43977,7 +54855,8 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi emptyLoopCounter = 0; } - if (ma_data_source_seek_to_pcm_frame(pCurrentDataSource, pCurrentDataSource->loopBegInFrames) != MA_SUCCESS) { + result = ma_data_source_seek_to_pcm_frame(pCurrentDataSource, pCurrentDataSource->loopBegInFrames); + if (result != MA_SUCCESS) { break; /* Failed to loop. Abort. */ } @@ -43997,14 +54876,10 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi } /* The next data source needs to be rewound to ensure data is read in looping scenarios. */ - ma_data_source_seek_to_pcm_frame(pDataSourceBase->pCurrent, 0); - - /* - We need to make sure we clear the MA_AT_END result so we don't accidentally return - it in the event that we coincidentally ended reading at the exact transition point - of two data sources in a chain. - */ - result = MA_SUCCESS; + result = ma_data_source_seek_to_pcm_frame(pDataSourceBase->pCurrent, 0); + if (result != MA_SUCCESS) { + break; + } } } @@ -44017,93 +54892,29 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi *pFramesRead = totalFramesProcessed; } + MA_ASSERT(!(result == MA_AT_END && totalFramesProcessed > 0)); /* We should never be returning MA_AT_END if we read some data. */ + + if (result == MA_SUCCESS && totalFramesProcessed == 0) { + result = MA_AT_END; + } + return result; -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - - /* Safety. */ - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onRead == NULL) { - return MA_NOT_IMPLEMENTED; - } - - /* A very small optimization for the non looping case. */ - if (loop == MA_FALSE) { - return pCallbacks->onRead(pDataSource, pFramesOut, frameCount, pFramesRead); - } else { - ma_format format; - ma_uint32 channels; - ma_uint32 sampleRate; - if (ma_data_source_get_data_format(pDataSource, &format, &channels, &sampleRate) != MA_SUCCESS) { - return pCallbacks->onRead(pDataSource, pFramesOut, frameCount, pFramesRead); /* We don't have a way to retrieve the data format which means we don't know how to offset the output buffer. Just read as much as we can. */ - } else { - ma_result result = MA_SUCCESS; - ma_uint64 totalFramesProcessed; - void* pRunningFramesOut = pFramesOut; - - totalFramesProcessed = 0; - while (totalFramesProcessed < frameCount) { - ma_uint64 framesProcessed; - ma_uint64 framesRemaining = frameCount - totalFramesProcessed; - - result = pCallbacks->onRead(pDataSource, pRunningFramesOut, framesRemaining, &framesProcessed); - totalFramesProcessed += framesProcessed; - - /* - If we encounted an error from the read callback, make sure it's propagated to the caller. The caller may need to know whether or not MA_BUSY is returned which is - not necessarily considered an error. - */ - if (result != MA_SUCCESS && result != MA_AT_END) { - break; - } - - /* - We can determine if we've reached the end by checking the return value of the onRead() callback. If it's less than what we requested it means - we've reached the end. To loop back to the start, all we need to do is seek back to the first frame. - */ - if (framesProcessed < framesRemaining || result == MA_AT_END) { - if (ma_data_source_seek_to_pcm_frame(pDataSource, 0) != MA_SUCCESS) { - break; - } - } - - if (pRunningFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesProcessed * ma_get_bytes_per_frame(format, channels)); - } - } - - if (pFramesRead != NULL) { - *pFramesRead = totalFramesProcessed; - } - - return result; - } - } -#endif } -MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked, ma_bool32 loop) +MA_API ma_result ma_data_source_seek_pcm_frames(ma_data_source* pDataSource, ma_uint64 frameCount, ma_uint64* pFramesSeeked) { - return ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, pFramesSeeked, loop); + return ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount, pFramesSeeked); } MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; if (pDataSourceBase == NULL) { return MA_SUCCESS; } - if (pDataSourceBase->cb.onSeek == NULL) { + if (pDataSourceBase->vtable->onSeek == NULL) { return MA_NOT_IMPLEMENTED; } @@ -44111,78 +54922,40 @@ MA_API ma_result ma_data_source_seek_to_pcm_frame(ma_data_source* pDataSource, m return MA_INVALID_OPERATION; /* Trying to seek to far forward. */ } - return pDataSourceBase->cb.onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onSeek == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onSeek(pDataSource, frameIndex); -#endif + return pDataSourceBase->vtable->onSeek(pDataSource, pDataSourceBase->rangeBegInFrames + frameIndex); } -MA_API ma_result ma_data_source_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) -{ - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onMap == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onMap(pDataSource, ppFramesOut, pFrameCount); -} - -MA_API ma_result ma_data_source_unmap(ma_data_source* pDataSource, ma_uint64 frameCount) -{ - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onUnmap == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onUnmap(pDataSource, frameCount); -} - -MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_result result; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; + /* Initialize to defaults for safety just in case the data source does not implement this callback. */ if (pFormat != NULL) { *pFormat = ma_format_unknown; } - if (pChannels != NULL) { *pChannels = 0; } - if (pSampleRate != NULL) { *pSampleRate = 0; } + if (pChannelMap != NULL) { + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + } - if (pCallbacks == NULL) { + if (pDataSourceBase == NULL) { return MA_INVALID_ARGS; } - if (pCallbacks->onGetDataFormat == NULL) { + if (pDataSourceBase->vtable->onGetDataFormat == NULL) { return MA_NOT_IMPLEMENTED; } - result = pCallbacks->onGetDataFormat(pDataSource, &format, &channels, &sampleRate); + result = pDataSourceBase->vtable->onGetDataFormat(pDataSource, &format, &channels, &sampleRate, pChannelMap, channelMapCap); if (result != MA_SUCCESS) { return result; } @@ -44197,12 +54970,13 @@ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_ *pSampleRate = sampleRate; } + /* Channel map was passed in directly to the callback. This is safe due to the channelMapCap parameter. */ + return MA_SUCCESS; } MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; ma_result result; ma_uint64 cursor; @@ -44217,11 +54991,11 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo return MA_SUCCESS; } - if (pDataSourceBase->cb.onGetCursor == NULL) { + if (pDataSourceBase->vtable->onGetCursor == NULL) { return MA_NOT_IMPLEMENTED; } - result = pDataSourceBase->cb.onGetCursor(pDataSourceBase, &cursor); + result = pDataSourceBase->vtable->onGetCursor(pDataSourceBase, &cursor); if (result != MA_SUCCESS) { return result; } @@ -44232,32 +55006,12 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo } else { *pCursor = cursor - pDataSourceBase->rangeBegInFrames; } - + return MA_SUCCESS; -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; - - if (pCursor == NULL) { - return MA_INVALID_ARGS; - } - - *pCursor = 0; - - if (pCallbacks == NULL) { - return MA_INVALID_ARGS; - } - - if (pCallbacks->onGetCursor == NULL) { - return MA_NOT_IMPLEMENTED; - } - - return pCallbacks->onGetCursor(pDataSource, pCursor); -#endif } MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) { -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; if (pLength == NULL) { @@ -44284,13 +55038,45 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo Getting here means a range is not defined so we'll need to get the data source itself to tell us the length. */ - if (pDataSourceBase->cb.onGetLength == NULL) { + if (pDataSourceBase->vtable->onGetLength == NULL) { return MA_NOT_IMPLEMENTED; } - return pDataSourceBase->cb.onGetLength(pDataSource, pLength); -#else - ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; + return pDataSourceBase->vtable->onGetLength(pDataSource, pLength); +} + +MA_API ma_result ma_data_source_get_cursor_in_seconds(ma_data_source* pDataSource, float* pCursor) +{ + ma_result result; + ma_uint64 cursorInPCMFrames; + ma_uint32 sampleRate; + + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursorInPCMFrames); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + *pCursor = cursorInPCMFrames / (float)sampleRate; + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_source_get_length_in_seconds(ma_data_source* pDataSource, float* pLength) +{ + ma_result result; + ma_uint64 lengthInPCMFrames; + ma_uint32 sampleRate; if (pLength == NULL) { return MA_INVALID_ARGS; @@ -44298,20 +55084,50 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo *pLength = 0; - if (pCallbacks == NULL) { + result = ma_data_source_get_length_in_pcm_frames(pDataSource, &lengthInPCMFrames); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_data_source_get_data_format(pDataSource, NULL, NULL, &sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; + } + + *pLength = lengthInPCMFrames / (float)sampleRate; + + return MA_SUCCESS; +} + +MA_API ma_result ma_data_source_set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + + if (pDataSource == NULL) { return MA_INVALID_ARGS; } - if (pCallbacks->onGetLength == NULL) { - return MA_NOT_IMPLEMENTED; + c89atomic_exchange_32(&pDataSourceBase->isLooping, isLooping); + + /* If there's no callback for this just treat it as a successful no-op. */ + if (pDataSourceBase->vtable->onSetLooping == NULL) { + return MA_SUCCESS; } - return pCallbacks->onGetLength(pDataSource, pLength); -#endif + return pDataSourceBase->vtable->onSetLooping(pDataSource, isLooping); } +MA_API ma_bool32 ma_data_source_is_looping(const ma_data_source* pDataSource) +{ + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; + + if (pDataSource == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32(&pDataSourceBase->isLooping); +} -#if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 rangeBegInFrames, ma_uint64 rangeEndInFrames) { ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; @@ -44356,12 +55172,12 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou } else { pDataSourceBase->loopEndInFrames = 0; } - + if (pDataSourceBase->loopEndInFrames > pDataSourceBase->rangeEndInFrames && pDataSourceBase->loopEndInFrames) { pDataSourceBase->loopEndInFrames = pDataSourceBase->rangeEndInFrames; } } - + /* If the new range is past the current cursor position we need to seek to it. */ result = ma_data_source_get_cursor_in_pcm_frames(pDataSource, &cursor); @@ -44379,9 +55195,9 @@ MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSou return MA_SUCCESS; } -MA_API void ma_data_source_get_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames) +MA_API void ma_data_source_get_range_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return; @@ -44423,9 +55239,9 @@ MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDa return MA_SUCCESS; } -MA_API void ma_data_source_get_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames) +MA_API void ma_data_source_get_loop_point_in_pcm_frames(const ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return; @@ -44453,9 +55269,9 @@ MA_API ma_result ma_data_source_set_current(ma_data_source* pDataSource, ma_data return MA_SUCCESS; } -MA_API ma_data_source* ma_data_source_get_current(ma_data_source* pDataSource) +MA_API ma_data_source* ma_data_source_get_current(const ma_data_source* pDataSource) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return NULL; @@ -44477,9 +55293,9 @@ MA_API ma_result ma_data_source_set_next(ma_data_source* pDataSource, ma_data_so return MA_SUCCESS; } -MA_API ma_data_source* ma_data_source_get_next(ma_data_source* pDataSource) +MA_API ma_data_source* ma_data_source_get_next(const ma_data_source* pDataSource) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return NULL; @@ -44501,9 +55317,9 @@ MA_API ma_result ma_data_source_set_next_callback(ma_data_source* pDataSource, m return MA_SUCCESS; } -MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_source* pDataSource) +MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(const ma_data_source* pDataSource) { - ma_data_source_base* pDataSourceBase = (ma_data_source_base*)pDataSource; + const ma_data_source_base* pDataSourceBase = (const ma_data_source_base*)pDataSource; if (pDataSource == NULL) { return NULL; @@ -44511,7 +55327,6 @@ MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_sou return pDataSourceBase->onGetNext; } -#endif static ma_result ma_audio_buffer_ref__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) @@ -44535,23 +55350,14 @@ static ma_result ma_audio_buffer_ref__data_source_on_seek(ma_data_source* pDataS return ma_audio_buffer_ref_seek_to_pcm_frame((ma_audio_buffer_ref*)pDataSource, frameIndex); } -static ma_result ma_audio_buffer_ref__data_source_on_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) -{ - return ma_audio_buffer_ref_map((ma_audio_buffer_ref*)pDataSource, ppFramesOut, pFrameCount); -} - -static ma_result ma_audio_buffer_ref__data_source_on_unmap(ma_data_source* pDataSource, ma_uint64 frameCount) -{ - return ma_audio_buffer_ref_unmap((ma_audio_buffer_ref*)pDataSource, frameCount); -} - -static ma_result ma_audio_buffer_ref__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_audio_buffer_ref__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; *pFormat = pAudioBufferRef->format; *pChannels = pAudioBufferRef->channels; - *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ + *pSampleRate = pAudioBufferRef->sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pAudioBufferRef->channels); return MA_SUCCESS; } @@ -44578,11 +55384,11 @@ static ma_data_source_vtable g_ma_audio_buffer_ref_data_source_vtable = { ma_audio_buffer_ref__data_source_on_read, ma_audio_buffer_ref__data_source_on_seek, - ma_audio_buffer_ref__data_source_on_map, - ma_audio_buffer_ref__data_source_on_unmap, ma_audio_buffer_ref__data_source_on_get_data_format, ma_audio_buffer_ref__data_source_on_get_cursor, - ma_audio_buffer_ref__data_source_on_get_length + ma_audio_buffer_ref__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 }; MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, const void* pData, ma_uint64 sizeInFrames, ma_audio_buffer_ref* pAudioBufferRef) @@ -44606,6 +55412,7 @@ MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, pAudioBufferRef->format = format; pAudioBufferRef->channels = channels; + pAudioBufferRef->sampleRate = 0; /* TODO: Version 0.12. Set this to sampleRate. */ pAudioBufferRef->cursor = 0; pAudioBufferRef->sizeInFrames = sizeInFrames; pAudioBufferRef->pData = pData; @@ -44658,7 +55465,7 @@ MA_API ma_uint64 ma_audio_buffer_ref_read_pcm_frames(ma_audio_buffer_ref* pAudio } if (pFramesOut != NULL) { - ma_copy_pcm_frames(pFramesOut, ma_offset_ptr(pAudioBufferRef->pData, pAudioBufferRef->cursor * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), framesToRead, pAudioBufferRef->format, pAudioBufferRef->channels); + ma_copy_pcm_frames(ma_offset_ptr(pFramesOut, totalFramesRead * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), ma_offset_ptr(pAudioBufferRef->pData, pAudioBufferRef->cursor * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), framesToRead, pAudioBufferRef->format, pAudioBufferRef->channels); } totalFramesRead += framesToRead; @@ -44816,10 +55623,11 @@ MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_u ma_audio_buffer_config config; MA_ZERO_OBJECT(&config); - config.format = format; - config.channels = channels; + config.format = format; + config.channels = channels; + config.sampleRate = 0; /* TODO: Version 0.12. Set this to sampleRate. */ config.sizeInFrames = sizeInFrames; - config.pData = pData; + config.pData = pData; ma_allocation_callbacks_init_copy(&config.allocationCallbacks, pAllocationCallbacks); return config; @@ -44848,6 +55656,9 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, return result; } + /* TODO: Version 0.12. Set this in ma_audio_buffer_ref_init() instead of here. */ + pAudioBuffer->ref.sampleRate = pConfig->sampleRate; + ma_allocation_callbacks_init_copy(&pAudioBuffer->allocationCallbacks, &pConfig->allocationCallbacks); if (doCopy) { @@ -44859,7 +55670,7 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, return MA_OUT_OF_MEMORY; /* Too big. */ } - pData = ma__malloc_from_callbacks((size_t)allocationSizeInBytes, &pAudioBuffer->allocationCallbacks); /* Safe cast to size_t. */ + pData = ma_malloc((size_t)allocationSizeInBytes, &pAudioBuffer->allocationCallbacks); /* Safe cast to size_t. */ if (pData == NULL) { return MA_OUT_OF_MEMORY; } @@ -44887,11 +55698,11 @@ static void ma_audio_buffer_uninit_ex(ma_audio_buffer* pAudioBuffer, ma_bool32 d } if (pAudioBuffer->ownsData && pAudioBuffer->ref.pData != &pAudioBuffer->_pExtraData[0]) { - ma__free_from_callbacks((void*)pAudioBuffer->ref.pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ + ma_free((void*)pAudioBuffer->ref.pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ } if (doFree) { - ma__free_from_callbacks(pAudioBuffer, &pAudioBuffer->allocationCallbacks); + ma_free(pAudioBuffer, &pAudioBuffer->allocationCallbacks); } ma_audio_buffer_ref_uninit(&pAudioBuffer->ref); @@ -44932,7 +55743,7 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC return MA_OUT_OF_MEMORY; /* Too big. */ } - pAudioBuffer = (ma_audio_buffer*)ma__malloc_from_callbacks((size_t)allocationSizeInBytes, &innerConfig.allocationCallbacks); /* Safe cast to size_t. */ + pAudioBuffer = (ma_audio_buffer*)ma_malloc((size_t)allocationSizeInBytes, &innerConfig.allocationCallbacks); /* Safe cast to size_t. */ if (pAudioBuffer == NULL) { return MA_OUT_OF_MEMORY; } @@ -44947,7 +55758,7 @@ MA_API ma_result ma_audio_buffer_alloc_and_init(const ma_audio_buffer_config* pC result = ma_audio_buffer_init_ex(&innerConfig, MA_FALSE, pAudioBuffer); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pAudioBuffer, &innerConfig.allocationCallbacks); + ma_free(pAudioBuffer, &innerConfig.allocationCallbacks); return result; } @@ -45054,6 +55865,392 @@ MA_API ma_result ma_audio_buffer_get_available_frames(const ma_audio_buffer* pAu + + +MA_API ma_result ma_paged_audio_buffer_data_init(ma_format format, ma_uint32 channels, ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pData); + + pData->format = format; + pData->channels = channels; + pData->pTail = &pData->head; + + return MA_SUCCESS; +} + +MA_API void ma_paged_audio_buffer_data_uninit(ma_paged_audio_buffer_data* pData, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_paged_audio_buffer_page* pPage; + + if (pData == NULL) { + return; + } + + /* All pages need to be freed. */ + pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->head.pNext); + while (pPage != NULL) { + ma_paged_audio_buffer_page* pNext = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext); + + ma_free(pPage, pAllocationCallbacks); + pPage = pNext; + } +} + +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_head(ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return NULL; + } + + return &pData->head; +} + +MA_API ma_paged_audio_buffer_page* ma_paged_audio_buffer_data_get_tail(ma_paged_audio_buffer_data* pData) +{ + if (pData == NULL) { + return NULL; + } + + return pData->pTail; +} + +MA_API ma_result ma_paged_audio_buffer_data_get_length_in_pcm_frames(ma_paged_audio_buffer_data* pData, ma_uint64* pLength) +{ + ma_paged_audio_buffer_page* pPage; + + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; + + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + /* Calculate the length from the linked list. */ + for (pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->head.pNext); pPage != NULL; pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext)) { + *pLength += pPage->sizeInFrames; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_allocate_page(ma_paged_audio_buffer_data* pData, ma_uint64 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks, ma_paged_audio_buffer_page** ppPage) +{ + ma_paged_audio_buffer_page* pPage; + ma_uint64 allocationSize; + + if (ppPage == NULL) { + return MA_INVALID_ARGS; + } + + *ppPage = NULL; + + if (pData == NULL) { + return MA_INVALID_ARGS; + } + + allocationSize = sizeof(*pPage) + (pageSizeInFrames * ma_get_bytes_per_frame(pData->format, pData->channels)); + if (allocationSize > MA_SIZE_MAX) { + return MA_OUT_OF_MEMORY; /* Too big. */ + } + + pPage = (ma_paged_audio_buffer_page*)ma_malloc((size_t)allocationSize, pAllocationCallbacks); /* Safe cast to size_t. */ + if (pPage == NULL) { + return MA_OUT_OF_MEMORY; + } + + pPage->pNext = NULL; + pPage->sizeInFrames = pageSizeInFrames; + + if (pInitialData != NULL) { + ma_copy_pcm_frames(pPage->pAudioData, pInitialData, pageSizeInFrames, pData->format, pData->channels); + } + + *ppPage = pPage; + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_free_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pData == NULL || pPage == NULL) { + return MA_INVALID_ARGS; + } + + /* It's assumed the page is not attached to the list. */ + ma_free(pPage, pAllocationCallbacks); + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_append_page(ma_paged_audio_buffer_data* pData, ma_paged_audio_buffer_page* pPage) +{ + if (pData == NULL || pPage == NULL) { + return MA_INVALID_ARGS; + } + + /* This function assumes the page has been filled with audio data by this point. As soon as we append, the page will be available for reading. */ + + /* First thing to do is update the tail. */ + for (;;) { + ma_paged_audio_buffer_page* pOldTail = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pData->pTail); + ma_paged_audio_buffer_page* pNewTail = pPage; + + if (c89atomic_compare_exchange_weak_ptr((volatile void**)&pData->pTail, (void**)&pOldTail, pNewTail)) { + /* Here is where we append the page to the list. After this, the page is attached to the list and ready to be read from. */ + c89atomic_exchange_ptr(&pOldTail->pNext, pPage); + break; /* Done. */ + } + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_data_allocate_and_append_page(ma_paged_audio_buffer_data* pData, ma_uint32 pageSizeInFrames, const void* pInitialData, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_result result; + ma_paged_audio_buffer_page* pPage; + + result = ma_paged_audio_buffer_data_allocate_page(pData, pageSizeInFrames, pInitialData, pAllocationCallbacks, &pPage); + if (result != MA_SUCCESS) { + return result; + } + + return ma_paged_audio_buffer_data_append_page(pData, pPage); /* <-- Should never fail. */ +} + + +MA_API ma_paged_audio_buffer_config ma_paged_audio_buffer_config_init(ma_paged_audio_buffer_data* pData) +{ + ma_paged_audio_buffer_config config; + + MA_ZERO_OBJECT(&config); + config.pData = pData; + + return config; +} + + +static ma_result ma_paged_audio_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_paged_audio_buffer_read_pcm_frames((ma_paged_audio_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_paged_audio_buffer__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_paged_audio_buffer_seek_to_pcm_frame((ma_paged_audio_buffer*)pDataSource, frameIndex); +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + ma_paged_audio_buffer* pPagedAudioBuffer = (ma_paged_audio_buffer*)pDataSource; + + *pFormat = pPagedAudioBuffer->pData->format; + *pChannels = pPagedAudioBuffer->pData->channels; + *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pPagedAudioBuffer->pData->channels); + + return MA_SUCCESS; +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_paged_audio_buffer_get_cursor_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pCursor); +} + +static ma_result ma_paged_audio_buffer__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_paged_audio_buffer_get_length_in_pcm_frames((ma_paged_audio_buffer*)pDataSource, pLength); +} + +static ma_data_source_vtable g_ma_paged_audio_buffer_data_source_vtable = +{ + ma_paged_audio_buffer__data_source_on_read, + ma_paged_audio_buffer__data_source_on_seek, + ma_paged_audio_buffer__data_source_on_get_data_format, + ma_paged_audio_buffer__data_source_on_get_cursor, + ma_paged_audio_buffer__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 +}; + +MA_API ma_result ma_paged_audio_buffer_init(const ma_paged_audio_buffer_config* pConfig, ma_paged_audio_buffer* pPagedAudioBuffer) +{ + ma_result result; + ma_data_source_config dataSourceConfig; + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pPagedAudioBuffer); + + /* A config is required for the format and channel count. */ + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pData == NULL) { + return MA_INVALID_ARGS; /* No underlying data specified. */ + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_paged_audio_buffer_data_source_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pPagedAudioBuffer->ds); + if (result != MA_SUCCESS) { + return result; + } + + pPagedAudioBuffer->pData = pConfig->pData; + pPagedAudioBuffer->pCurrent = ma_paged_audio_buffer_data_get_head(pConfig->pData); + pPagedAudioBuffer->relativeCursor = 0; + pPagedAudioBuffer->absoluteCursor = 0; + + return MA_SUCCESS; +} + +MA_API void ma_paged_audio_buffer_uninit(ma_paged_audio_buffer* pPagedAudioBuffer) +{ + if (pPagedAudioBuffer == NULL) { + return; + } + + /* Nothing to do. The data needs to be deleted separately. */ +} + +MA_API ma_result ma_paged_audio_buffer_read_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesRead = 0; + ma_format format; + ma_uint32 channels; + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + format = pPagedAudioBuffer->pData->format; + channels = pPagedAudioBuffer->pData->channels; + + while (totalFramesRead < frameCount) { + /* Read from the current page. The buffer should never be in a state where this is NULL. */ + ma_uint64 framesRemainingInCurrentPage; + ma_uint64 framesRemainingToRead = frameCount - totalFramesRead; + ma_uint64 framesToReadThisIteration; + + MA_ASSERT(pPagedAudioBuffer->pCurrent != NULL); + + framesRemainingInCurrentPage = pPagedAudioBuffer->pCurrent->sizeInFrames - pPagedAudioBuffer->relativeCursor; + + framesToReadThisIteration = ma_min(framesRemainingInCurrentPage, framesRemainingToRead); + ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), ma_offset_pcm_frames_ptr(pPagedAudioBuffer->pCurrent->pAudioData, pPagedAudioBuffer->relativeCursor, format, channels), framesToReadThisIteration, format, channels); + totalFramesRead += framesToReadThisIteration; + + pPagedAudioBuffer->absoluteCursor += framesToReadThisIteration; + pPagedAudioBuffer->relativeCursor += framesToReadThisIteration; + + /* Move to the next page if necessary. If there's no more pages, we need to return MA_AT_END. */ + MA_ASSERT(pPagedAudioBuffer->relativeCursor <= pPagedAudioBuffer->pCurrent->sizeInFrames); + + if (pPagedAudioBuffer->relativeCursor == pPagedAudioBuffer->pCurrent->sizeInFrames) { + /* We reached the end of the page. Need to move to the next. If there's no more pages, we're done. */ + ma_paged_audio_buffer_page* pNext = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPagedAudioBuffer->pCurrent->pNext); + if (pNext == NULL) { + result = MA_AT_END; + break; /* We've reached the end. */ + } else { + pPagedAudioBuffer->pCurrent = pNext; + pPagedAudioBuffer->relativeCursor = 0; + } + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return result; +} + +MA_API ma_result ma_paged_audio_buffer_seek_to_pcm_frame(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64 frameIndex) +{ + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (frameIndex == pPagedAudioBuffer->absoluteCursor) { + return MA_SUCCESS; /* Nothing to do. */ + } + + if (frameIndex < pPagedAudioBuffer->absoluteCursor) { + /* Moving backwards. Need to move the cursor back to the start, and then move forward. */ + pPagedAudioBuffer->pCurrent = ma_paged_audio_buffer_data_get_head(pPagedAudioBuffer->pData); + pPagedAudioBuffer->absoluteCursor = 0; + pPagedAudioBuffer->relativeCursor = 0; + + /* Fall through to the forward seeking section below. */ + } + + if (frameIndex > pPagedAudioBuffer->absoluteCursor) { + /* Moving forward. */ + ma_paged_audio_buffer_page* pPage; + ma_uint64 runningCursor = 0; + + for (pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&ma_paged_audio_buffer_data_get_head(pPagedAudioBuffer->pData)->pNext); pPage != NULL; pPage = (ma_paged_audio_buffer_page*)c89atomic_load_ptr(&pPage->pNext)) { + ma_uint64 pageRangeBeg = runningCursor; + ma_uint64 pageRangeEnd = pageRangeBeg + pPage->sizeInFrames; + + if (frameIndex >= pageRangeBeg) { + if (frameIndex < pageRangeEnd || (frameIndex == pageRangeEnd && pPage == (ma_paged_audio_buffer_page*)c89atomic_load_ptr(ma_paged_audio_buffer_data_get_tail(pPagedAudioBuffer->pData)))) { /* A small edge case - allow seeking to the very end of the buffer. */ + /* We found the page. */ + pPagedAudioBuffer->pCurrent = pPage; + pPagedAudioBuffer->absoluteCursor = frameIndex; + pPagedAudioBuffer->relativeCursor = frameIndex - pageRangeBeg; + return MA_SUCCESS; + } + } + + runningCursor = pageRangeEnd; + } + + /* Getting here means we tried seeking too far forward. Don't change any state. */ + return MA_BAD_SEEK; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_get_cursor_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pCursor) +{ + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; /* Safety. */ + + if (pPagedAudioBuffer == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = pPagedAudioBuffer->absoluteCursor; + + return MA_SUCCESS; +} + +MA_API ma_result ma_paged_audio_buffer_get_length_in_pcm_frames(ma_paged_audio_buffer* pPagedAudioBuffer, ma_uint64* pLength) +{ + return ma_paged_audio_buffer_data_get_length_in_pcm_frames(pPagedAudioBuffer->pData, pLength); +} + + + /************************************************************************************************************************************************************** VFS @@ -45119,6 +56316,8 @@ MA_API ma_result ma_vfs_close(ma_vfs* pVFS, ma_vfs_file file) MA_API ma_result ma_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead) { ma_vfs_callbacks* pCallbacks = (ma_vfs_callbacks*)pVFS; + ma_result result; + size_t bytesRead; if (pBytesRead != NULL) { *pBytesRead = 0; @@ -45132,7 +56331,17 @@ MA_API ma_result ma_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t return MA_NOT_IMPLEMENTED; } - return pCallbacks->onRead(pVFS, file, pDst, sizeInBytes, pBytesRead); + result = pCallbacks->onRead(pVFS, file, pDst, sizeInBytes, &bytesRead); + + if (pBytesRead != NULL) { + *pBytesRead = bytesRead; + } + + if (result == MA_SUCCESS && bytesRead == 0 && sizeInBytes > 0) { + result = MA_AT_END; + } + + return result; } MA_API ma_result ma_vfs_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, size_t sizeInBytes, size_t* pBytesWritten) @@ -45212,7 +56421,7 @@ MA_API ma_result ma_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo } -static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePath, const wchar_t* pFilePathW, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks, ma_uint32 allocationType) +static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePath, const wchar_t* pFilePathW, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { ma_result result; ma_vfs_file file; @@ -45220,8 +56429,6 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat void* pData; size_t bytesRead; - (void)allocationType; - if (ppData != NULL) { *ppData = NULL; } @@ -45253,7 +56460,7 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat return MA_TOO_BIG; } - pData = ma__malloc_from_callbacks((size_t)info.sizeInBytes, pAllocationCallbacks); /* Safe cast. */ + pData = ma_malloc((size_t)info.sizeInBytes, pAllocationCallbacks); /* Safe cast. */ if (pData == NULL) { ma_vfs_close(pVFS, file); return result; @@ -45263,7 +56470,7 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat ma_vfs_close(pVFS, file); if (result != MA_SUCCESS) { - ma__free_from_callbacks(pData, pAllocationCallbacks); + ma_free(pData, pAllocationCallbacks); return result; } @@ -45279,12 +56486,12 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat MA_API ma_result ma_vfs_open_and_read_file(ma_vfs* pVFS, const char* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_vfs_open_and_read_file_ex(pVFS, pFilePath, NULL, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); + return ma_vfs_open_and_read_file_ex(pVFS, pFilePath, NULL, ppData, pSize, pAllocationCallbacks); } MA_API ma_result ma_vfs_open_and_read_file_w(ma_vfs* pVFS, const wchar_t* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_vfs_open_and_read_file_ex(pVFS, NULL, pFilePath, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); + return ma_vfs_open_and_read_file_ex(pVFS, NULL, pFilePath, ppData, pSize, pAllocationCallbacks); } @@ -45992,7 +57199,7 @@ extern "C" { #define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 13 -#define DRWAV_VERSION_REVISION 1 +#define DRWAV_VERSION_REVISION 6 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include typedef signed char drwav_int8; @@ -46527,7 +57734,7 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 31 +#define DRFLAC_VERSION_REVISION 38 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include typedef signed char drflac_int8; @@ -46536,7 +57743,7 @@ typedef signed short drflac_int16; typedef unsigned short drflac_uint16; typedef signed int drflac_int32; typedef unsigned int drflac_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 drflac_int64; typedef unsigned __int64 drflac_uint64; #else @@ -46553,7 +57760,7 @@ typedef unsigned int drflac_uint32; #pragma GCC diagnostic pop #endif #endif -#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined(_M_IA64) || defined(__aarch64__) || defined(_M_ARM64) || defined(__powerpc64__) typedef drflac_uint64 drflac_uintptr; #else typedef drflac_uint32 drflac_uintptr; @@ -46888,7 +58095,7 @@ extern "C" { #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 31 +#define DRMP3_VERSION_REVISION 33 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include typedef signed char drmp3_int8; @@ -46897,7 +58104,7 @@ typedef signed short drmp3_int16; typedef unsigned short drmp3_uint16; typedef signed int drmp3_int32; typedef unsigned int drmp3_uint32; -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) typedef signed __int64 drmp3_int64; typedef unsigned __int64 drmp3_uint64; #else @@ -47012,9 +58219,14 @@ typedef drmp3_int32 drmp3_result; #define DRMP3_INLINE __forceinline #elif defined(__GNUC__) #if defined(__STRICT_ANSI__) - #define DRMP3_INLINE __inline__ __attribute__((always_inline)) + #define DRMP3_GNUC_INLINE_HINT __inline__ #else - #define DRMP3_INLINE inline __attribute__((always_inline)) + #define DRMP3_GNUC_INLINE_HINT inline + #endif + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRMP3_INLINE DRMP3_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRMP3_INLINE __inline @@ -47135,43 +58347,22 @@ Decoding static ma_result ma_decoder_read_bytes(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { - size_t bytesRead; + MA_ASSERT(pDecoder != NULL); - MA_ASSERT(pDecoder != NULL); - MA_ASSERT(pBufferOut != NULL); - MA_ASSERT(bytesToRead > 0); /* It's an error to call this with a byte count of zero. */ - - bytesRead = pDecoder->onRead(pDecoder, pBufferOut, bytesToRead); - - if (pBytesRead != NULL) { - *pBytesRead = bytesRead; - } - - if (bytesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return pDecoder->onRead(pDecoder, pBufferOut, bytesToRead, pBytesRead); } static ma_result ma_decoder_seek_bytes(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) { - ma_bool32 wasSuccessful; - MA_ASSERT(pDecoder != NULL); - wasSuccessful = pDecoder->onSeek(pDecoder, byteOffset, origin); - if (wasSuccessful) { - return MA_SUCCESS; - } else { - return MA_ERROR; - } + return pDecoder->onSeek(pDecoder, byteOffset, origin); } static ma_result ma_decoder_tell_bytes(ma_decoder* pDecoder, ma_int64* pCursor) { MA_ASSERT(pDecoder != NULL); - + if (pDecoder->onTell == NULL) { return MA_NOT_IMPLEMENTED; } @@ -47180,12 +58371,13 @@ static ma_result ma_decoder_tell_bytes(ma_decoder* pDecoder, ma_int64* pCursor) } -MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat) +MA_API ma_decoding_backend_config ma_decoding_backend_config_init(ma_format preferredFormat, ma_uint32 seekPointCount) { ma_decoding_backend_config config; MA_ZERO_OBJECT(&config); config.preferredFormat = preferredFormat; + config.seekPointCount = seekPointCount; return config; } @@ -47195,12 +58387,10 @@ MA_API ma_decoder_config ma_decoder_config_init(ma_format outputFormat, ma_uint3 { ma_decoder_config config; MA_ZERO_OBJECT(&config); - config.format = outputFormat; - config.channels = ma_min(outputChannels, ma_countof(config.channelMap)); - config.sampleRate = outputSampleRate; - config.resampling.algorithm = ma_resample_algorithm_linear; - config.resampling.linear.lpfOrder = ma_min(MA_DEFAULT_RESAMPLER_LPF_ORDER, MA_MAX_FILTER_ORDER); - config.resampling.speex.quality = 3; + config.format = outputFormat; + config.channels = outputChannels; + config.sampleRate = outputSampleRate; + config.resampling = ma_resampler_config_init(ma_format_unknown, 0, 0, 0, ma_resample_algorithm_linear); /* Format/channels/rate doesn't matter here. */ config.encodingFormat = ma_encoding_format_unknown; /* Note that we are intentionally leaving the channel map empty here which will cause the default channel map to be used. */ @@ -47237,19 +58427,11 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ MA_ASSERT(pDecoder != NULL); MA_ASSERT(pConfig != NULL); - result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, &internalSampleRate); + result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, &internalSampleRate, internalChannelMap, ma_countof(internalChannelMap)); if (result != MA_SUCCESS) { return result; /* Failed to retrieve the internal data format. */ } - /* Channel map needs to be retrieved separately. */ - if (pDecoder->pBackendVTable != NULL && pDecoder->pBackendVTable->onGetChannelMap != NULL) { - pDecoder->pBackendVTable->onGetChannelMap(pDecoder->pBackendUserData, pDecoder->pBackend, internalChannelMap, ma_countof(internalChannelMap)); - } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, ma_min(internalChannels, ma_countof(internalChannelMap)), internalChannelMap); - } - - /* Make sure we're not asking for too many channels. */ if (pConfig->channels > MA_MAX_CHANNELS) { @@ -47281,28 +58463,57 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ pDecoder->outputSampleRate = pConfig->sampleRate; } - if (ma_channel_map_blank(pDecoder->outputChannels, pConfig->channelMap)) { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDecoder->outputChannels, pDecoder->outputChannelMap); - } else { - MA_COPY_MEMORY(pDecoder->outputChannelMap, pConfig->channelMap, sizeof(pConfig->channelMap)); - } - - converterConfig = ma_data_converter_config_init( internalFormat, pDecoder->outputFormat, internalChannels, pDecoder->outputChannels, internalSampleRate, pDecoder->outputSampleRate ); - ma_channel_map_copy(converterConfig.channelMapIn, internalChannelMap, internalChannels); - ma_channel_map_copy(converterConfig.channelMapOut, pDecoder->outputChannelMap, pDecoder->outputChannels); - converterConfig.channelMixMode = pConfig->channelMixMode; - converterConfig.ditherMode = pConfig->ditherMode; - converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; /* Never allow dynamic sample rate conversion. Setting this to true will disable passthrough optimizations. */ - converterConfig.resampling.algorithm = pConfig->resampling.algorithm; - converterConfig.resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; - converterConfig.resampling.speex.quality = pConfig->resampling.speex.quality; + converterConfig.pChannelMapIn = internalChannelMap; + converterConfig.pChannelMapOut = pConfig->pChannelMap; + converterConfig.channelMixMode = pConfig->channelMixMode; + converterConfig.ditherMode = pConfig->ditherMode; + converterConfig.allowDynamicSampleRate = MA_FALSE; /* Never allow dynamic sample rate conversion. Setting this to true will disable passthrough optimizations. */ + converterConfig.resampling = pConfig->resampling; - return ma_data_converter_init(&converterConfig, &pDecoder->converter); + result = ma_data_converter_init(&converterConfig, &pDecoder->allocationCallbacks, &pDecoder->converter); + if (result != MA_SUCCESS) { + return result; + } + + /* + Now that we have the decoder we need to determine whether or not we need a heap-allocated cache. We'll + need this if the data converter does not support calculation of the required input frame count. To + determine support for this we'll just run a test. + */ + { + ma_uint64 unused; + + result = ma_data_converter_get_required_input_frame_count(&pDecoder->converter, 1, &unused); + if (result != MA_SUCCESS) { + /* + We were unable to calculate the required input frame count which means we'll need to use + a heap-allocated cache. + */ + ma_uint64 inputCacheCapSizeInBytes; + + pDecoder->inputCacheCap = MA_DATA_CONVERTER_STACK_BUFFER_SIZE / ma_get_bytes_per_frame(internalFormat, internalChannels); + + /* Not strictly necessary, but keeping here for safety in case we change the default value of pDecoder->inputCacheCap. */ + inputCacheCapSizeInBytes = pDecoder->inputCacheCap * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (inputCacheCapSizeInBytes > MA_SIZE_MAX) { + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + pDecoder->pInputCache = ma_malloc((size_t)inputCacheCapSizeInBytes, &pDecoder->allocationCallbacks); /* Safe cast to size_t. */ + if (pDecoder->pInputCache == NULL) { + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + } + } + + return MA_SUCCESS; } @@ -47346,7 +58557,7 @@ static ma_result ma_decoder_init_from_vtable(const ma_decoding_backend_vtable* p return MA_NOT_IMPLEMENTED; } - backendConfig = ma_decoding_backend_config_init(pConfig->format); + backendConfig = ma_decoding_backend_config_init(pConfig->format, pConfig->seekPointCount); result = pVTable->onInit(pVTableUserData, ma_decoder_internal_on_read__custom, ma_decoder_internal_on_seek__custom, ma_decoder_internal_on_tell__custom, pDecoder, &backendConfig, &pDecoder->allocationCallbacks, &pBackend); if (result != MA_SUCCESS) { @@ -47438,9 +58649,9 @@ static ma_result ma_wav_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInde return ma_wav_seek_to_pcm_frame((ma_wav*)pDataSource, frameIndex); } -static ma_result ma_wav_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_wav_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_wav_get_data_format((ma_wav*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_wav_get_data_format((ma_wav*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_wav_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -47457,11 +58668,11 @@ static ma_data_source_vtable g_ma_wav_ds_vtable = { ma_wav_ds_read, ma_wav_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_wav_ds_get_data_format, ma_wav_ds_get_cursor, - ma_wav_ds_get_length + ma_wav_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -47531,7 +58742,7 @@ static ma_result ma_wav_init_internal(const ma_decoding_backend_config* pConfig, } MA_ZERO_OBJECT(pWav); - pWav->format = ma_format_f32; /* f32 by default. */ + pWav->format = ma_format_unknown; /* Use closest match to source file by default. */ if (pConfig != NULL && (pConfig->preferredFormat == ma_format_f32 || pConfig->preferredFormat == ma_format_s16 || pConfig->preferredFormat == ma_format_s32)) { pWav->format = pConfig->preferredFormat; @@ -47578,6 +58789,42 @@ MA_API ma_result ma_wav_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p return MA_INVALID_FILE; } + /* + If an explicit format was not specified, try picking the closest match based on the internal + format. The format needs to be supported by miniaudio. + */ + if (pWav->format == ma_format_unknown) { + switch (pWav->dr.translatedFormatTag) + { + case DR_WAVE_FORMAT_PCM: + { + if (pWav->dr.bitsPerSample == 8) { + pWav->format = ma_format_u8; + } else if (pWav->dr.bitsPerSample == 16) { + pWav->format = ma_format_s16; + } else if (pWav->dr.bitsPerSample == 24) { + pWav->format = ma_format_s24; + } else if (pWav->dr.bitsPerSample == 32) { + pWav->format = ma_format_s32; + } + } break; + + case DR_WAVE_FORMAT_IEEE_FLOAT: + { + if (pWav->dr.bitsPerSample == 32) { + pWav->format = ma_format_f32; + } + } break; + + default: break; + } + + /* Fall back to f32 if we couldn't find anything. */ + if (pWav->format == ma_format_unknown) { + pWav->format = ma_format_f32; + } + } + return MA_SUCCESS; } #else @@ -47707,6 +58954,14 @@ MA_API void ma_wav_uninit(ma_wav* pWav, const ma_allocation_callbacks* pAllocati MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pWav == NULL) { return MA_INVALID_ARGS; } @@ -47738,7 +58993,7 @@ MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint6 } break; /* Fallback to a raw read. */ - case ma_format_unknown: return MA_INVALID_OPERATION; /* <-- this should never be hit because initialization would just fall back to supported format. */ + case ma_format_unknown: return MA_INVALID_OPERATION; /* <-- this should never be hit because initialization would just fall back to a supported format. */ default: { totalFramesRead = drwav_read_pcm_frames(&pWav->dr, frameCount, pFramesOut); @@ -47754,6 +59009,10 @@ MA_API ma_result ma_wav_read_pcm_frames(ma_wav* pWav, void* pFramesOut, ma_uint6 *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -47779,7 +59038,7 @@ MA_API ma_result ma_wav_seek_to_pcm_frame(ma_wav* pWav, ma_uint64 frameIndex) #if !defined(MA_NO_WAV) { drwav_bool32 wavResult; - + wavResult = drwav_seek_to_pcm_frame(&pWav->dr, frameIndex); if (wavResult != DRWAV_TRUE) { return MA_ERROR; @@ -47834,7 +59093,7 @@ MA_API ma_result ma_wav_get_data_format(ma_wav* pWav, ma_format* pFormat, ma_uin } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, (ma_uint32)ma_min(pWav->dr.channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pChannelMap, channelMapCap, pWav->dr.channels); } return MA_SUCCESS; @@ -48015,23 +59274,13 @@ static void ma_decoding_backend_uninit__wav(void* pUserData, ma_data_source* pBa ma_free(pWav, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__wav(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_wav* pWav = (ma_wav*)pBackend; - - (void)pUserData; - - return ma_wav_get_data_format(pWav, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_wav = { ma_decoding_backend_init__wav, ma_decoding_backend_init_file__wav, ma_decoding_backend_init_file_w__wav, ma_decoding_backend_init_memory__wav, - ma_decoding_backend_uninit__wav, - ma_decoding_backend_get_channel_map__wav + ma_decoding_backend_uninit__wav }; static ma_result ma_decoder_init_wav__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -48079,9 +59328,9 @@ static ma_result ma_flac_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInd return ma_flac_seek_to_pcm_frame((ma_flac*)pDataSource, frameIndex); } -static ma_result ma_flac_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_flac_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_flac_get_data_format((ma_flac*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_flac_get_data_format((ma_flac*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_flac_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -48098,11 +59347,11 @@ static ma_data_source_vtable g_ma_flac_ds_vtable = { ma_flac_ds_read, ma_flac_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_flac_ds_get_data_format, ma_flac_ds_get_cursor, - ma_flac_ds_get_length + ma_flac_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -48344,6 +59593,14 @@ MA_API void ma_flac_uninit(ma_flac* pFlac, const ma_allocation_callbacks* pAlloc MA_API ma_result ma_flac_read_pcm_frames(ma_flac* pFlac, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pFlac == NULL) { return MA_INVALID_ARGS; } @@ -48392,6 +59649,10 @@ MA_API ma_result ma_flac_read_pcm_frames(ma_flac* pFlac, void* pFramesOut, ma_ui *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -48417,7 +59678,7 @@ MA_API ma_result ma_flac_seek_to_pcm_frame(ma_flac* pFlac, ma_uint64 frameIndex) #if !defined(MA_NO_FLAC) { drflac_bool32 flacResult; - + flacResult = drflac_seek_to_pcm_frame(pFlac->dr, frameIndex); if (flacResult != DRFLAC_TRUE) { return MA_ERROR; @@ -48472,7 +59733,7 @@ MA_API ma_result ma_flac_get_data_format(ma_flac* pFlac, ma_format* pFormat, ma_ } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, (ma_uint32)ma_min(pFlac->dr->channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_microsoft, pChannelMap, channelMapCap, pFlac->dr->channels); } return MA_SUCCESS; @@ -48647,23 +59908,13 @@ static void ma_decoding_backend_uninit__flac(void* pUserData, ma_data_source* pB ma_free(pFlac, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__flac(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_flac* pFlac = (ma_flac*)pBackend; - - (void)pUserData; - - return ma_flac_get_data_format(pFlac, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_flac = { ma_decoding_backend_init__flac, ma_decoding_backend_init_file__flac, ma_decoding_backend_init_file_w__flac, ma_decoding_backend_init_memory__flac, - ma_decoding_backend_uninit__flac, - ma_decoding_backend_get_channel_map__flac + ma_decoding_backend_uninit__flac }; static ma_result ma_decoder_init_flac__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -48686,6 +59937,8 @@ typedef struct ma_format format; /* Can be f32 or s16. */ #if !defined(MA_NO_MP3) drmp3 dr; + drmp3_uint32 seekPointCount; + drmp3_seek_point* pSeekPoints; /* Only used if seek table generation is used. */ #endif } ma_mp3; @@ -48711,9 +59964,9 @@ static ma_result ma_mp3_ds_seek(ma_data_source* pDataSource, ma_uint64 frameInde return ma_mp3_seek_to_pcm_frame((ma_mp3*)pDataSource, frameIndex); } -static ma_result ma_mp3_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_mp3_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_mp3_get_data_format((ma_mp3*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_mp3_get_data_format((ma_mp3*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_mp3_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -48730,11 +59983,11 @@ static ma_data_source_vtable g_ma_mp3_ds_vtable = { ma_mp3_ds_read, ma_mp3_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_mp3_ds_get_data_format, ma_mp3_ds_get_cursor, - ma_mp3_ds_get_length + ma_mp3_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -48823,6 +60076,40 @@ static ma_result ma_mp3_init_internal(const ma_decoding_backend_config* pConfig, return MA_SUCCESS; } +static ma_result ma_mp3_generate_seek_table(ma_mp3* pMP3, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks) +{ + drmp3_bool32 mp3Result; + drmp3_uint32 seekPointCount = 0; + drmp3_seek_point* pSeekPoints = NULL; + + MA_ASSERT(pMP3 != NULL); + MA_ASSERT(pConfig != NULL); + + seekPointCount = pConfig->seekPointCount; + if (seekPointCount > 0) { + pSeekPoints = (drmp3_seek_point*)ma_malloc(sizeof(*pMP3->pSeekPoints) * seekPointCount, pAllocationCallbacks); + if (pSeekPoints == NULL) { + return MA_OUT_OF_MEMORY; + } + } + + mp3Result = drmp3_calculate_seek_points(&pMP3->dr, &seekPointCount, pSeekPoints); + if (mp3Result != MA_TRUE) { + return MA_ERROR; + } + + mp3Result = drmp3_bind_seek_table(&pMP3->dr, seekPointCount, pSeekPoints); + if (mp3Result != MA_TRUE) { + ma_free(pSeekPoints, pAllocationCallbacks); + return MA_ERROR; + } + + pMP3->seekPointCount = seekPointCount; + pMP3->pSeekPoints = pSeekPoints; + + return MA_SUCCESS; +} + MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_mp3* pMP3) { ma_result result; @@ -48851,6 +60138,8 @@ MA_API ma_result ma_mp3_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_p return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -48881,6 +60170,8 @@ MA_API ma_result ma_mp3_init_file(const char* pFilePath, const ma_decoding_backe return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -48912,6 +60203,8 @@ MA_API ma_result ma_mp3_init_file_w(const wchar_t* pFilePath, const ma_decoding_ return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -48943,6 +60236,8 @@ MA_API ma_result ma_mp3_init_memory(const void* pData, size_t dataSize, const ma return MA_INVALID_FILE; } + ma_mp3_generate_seek_table(pMP3, pConfig, pAllocationCallbacks); + return MA_SUCCESS; } #else @@ -48962,8 +60257,6 @@ MA_API void ma_mp3_uninit(ma_mp3* pMP3, const ma_allocation_callbacks* pAllocati return; } - (void)pAllocationCallbacks; - #if !defined(MA_NO_MP3) { drmp3_uninit(&pMP3->dr); @@ -48975,11 +60268,22 @@ MA_API void ma_mp3_uninit(ma_mp3* pMP3, const ma_allocation_callbacks* pAllocati } #endif + /* Seek points need to be freed after the MP3 decoder has been uninitialized to ensure they're no longer being referenced. */ + ma_free(pMP3->pSeekPoints, pAllocationCallbacks); + ma_data_source_uninit(&pMP3->ds); } MA_API ma_result ma_mp3_read_pcm_frames(ma_mp3* pMP3, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pMP3 == NULL) { return MA_INVALID_ARGS; } @@ -49049,7 +60353,7 @@ MA_API ma_result ma_mp3_seek_to_pcm_frame(ma_mp3* pMP3, ma_uint64 frameIndex) #if !defined(MA_NO_MP3) { drmp3_bool32 mp3Result; - + mp3Result = drmp3_seek_to_pcm_frame(&pMP3->dr, frameIndex); if (mp3Result != DRMP3_TRUE) { return MA_ERROR; @@ -49104,7 +60408,7 @@ MA_API ma_result ma_mp3_get_data_format(ma_mp3* pMP3, ma_format* pFormat, ma_uin } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_default, (ma_uint32)ma_min(pMP3->dr.channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pMP3->dr.channels); } return MA_SUCCESS; @@ -49279,23 +60583,13 @@ static void ma_decoding_backend_uninit__mp3(void* pUserData, ma_data_source* pBa ma_free(pMP3, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__mp3(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_mp3* pMP3 = (ma_mp3*)pBackend; - - (void)pUserData; - - return ma_mp3_get_data_format(pMP3, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_mp3 = { ma_decoding_backend_init__mp3, ma_decoding_backend_init_file__mp3, ma_decoding_backend_init_file_w__mp3, ma_decoding_backend_init_memory__mp3, - ma_decoding_backend_uninit__mp3, - ma_decoding_backend_get_channel_map__mp3 + ma_decoding_backend_uninit__mp3 }; static ma_result ma_decoder_init_mp3__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -49359,9 +60653,9 @@ static ma_result ma_stbvorbis_ds_seek(ma_data_source* pDataSource, ma_uint64 fra return ma_stbvorbis_seek_to_pcm_frame((ma_stbvorbis*)pDataSource, frameIndex); } -static ma_result ma_stbvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_stbvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - return ma_stbvorbis_get_data_format((ma_stbvorbis*)pDataSource, pFormat, pChannels, pSampleRate, NULL, 0); + return ma_stbvorbis_get_data_format((ma_stbvorbis*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_stbvorbis_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) @@ -49378,11 +60672,11 @@ static ma_data_source_vtable g_ma_stbvorbis_ds_vtable = { ma_stbvorbis_ds_read, ma_stbvorbis_ds_seek, - NULL, /* onMap() */ - NULL, /* onUnmap() */ ma_stbvorbis_ds_get_data_format, ma_stbvorbis_ds_get_cursor, - ma_stbvorbis_ds_get_length + ma_stbvorbis_ds_get_length, + NULL, /* onSetLooping */ + 0 }; @@ -49645,6 +60939,14 @@ MA_API void ma_stbvorbis_uninit(ma_stbvorbis* pVorbis, const ma_allocation_callb MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pVorbis == NULL) { return MA_INVALID_ARGS; } @@ -49753,7 +61055,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram while (totalFramesRead < frameCount) { ma_uint64 framesRemaining = (frameCount - totalFramesRead); int framesRead; - + if (framesRemaining > INT_MAX) { framesRemaining = INT_MAX; } @@ -49761,7 +61063,7 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram framesRead = stb_vorbis_get_samples_float_interleaved(pVorbis->stb, channels, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), (int)framesRemaining * channels); /* Safe cast. */ totalFramesRead += framesRead; - if (framesRead < framesRemaining) { + if (framesRead < (int)framesRemaining) { break; /* Nothing left to read. Get out. */ } } @@ -49780,6 +61082,10 @@ MA_API ma_result ma_stbvorbis_read_pcm_frames(ma_stbvorbis* pVorbis, void* pFram *pFramesRead = totalFramesRead; } + if (result == MA_SUCCESS && totalFramesRead == 0) { + result = MA_AT_END; + } + return result; } #else @@ -49910,7 +61216,7 @@ MA_API ma_result ma_stbvorbis_get_data_format(ma_stbvorbis* pVorbis, ma_format* } if (pChannelMap != NULL) { - ma_get_standard_channel_map(ma_standard_channel_map_vorbis, (ma_uint32)ma_min(pVorbis->channels, channelMapCap), pChannelMap); + ma_channel_map_init_standard(ma_standard_channel_map_vorbis, pChannelMap, channelMapCap, pVorbis->channels); } return MA_SUCCESS; @@ -49970,7 +61276,7 @@ MA_API ma_result ma_stbvorbis_get_length_in_pcm_frames(ma_stbvorbis* pVorbis, ma } else { *pLength = stb_vorbis_stream_length_in_samples(pVorbis->stb); } - + return MA_SUCCESS; } #else @@ -50065,23 +61371,13 @@ static void ma_decoding_backend_uninit__stbvorbis(void* pUserData, ma_data_sourc ma_free(pVorbis, pAllocationCallbacks); } -static ma_result ma_decoding_backend_get_channel_map__stbvorbis(void* pUserData, ma_data_source* pBackend, ma_channel* pChannelMap, size_t channelMapCap) -{ - ma_stbvorbis* pVorbis = (ma_stbvorbis*)pBackend; - - (void)pUserData; - - return ma_stbvorbis_get_data_format(pVorbis, NULL, NULL, NULL, pChannelMap, channelMapCap); -} - static ma_decoding_backend_vtable g_ma_decoding_backend_vtable_stbvorbis = { ma_decoding_backend_init__stbvorbis, ma_decoding_backend_init_file__stbvorbis, NULL, /* onInitFileW() */ ma_decoding_backend_init_memory__stbvorbis, - ma_decoding_backend_uninit__stbvorbis, - ma_decoding_backend_get_channel_map__stbvorbis + ma_decoding_backend_uninit__stbvorbis }; static ma_result ma_decoder_init_vorbis__internal(const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -50106,17 +61402,7 @@ static ma_result ma_decoder__init_allocation_callbacks(const ma_decoder_config* static ma_result ma_decoder__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_decoder_read_pcm_frames((ma_decoder*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_decoder_read_pcm_frames((ma_decoder*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -50124,45 +61410,30 @@ static ma_result ma_decoder__data_source_on_seek(ma_data_source* pDataSource, ma return ma_decoder_seek_to_pcm_frame((ma_decoder*)pDataSource, frameIndex); } -static ma_result ma_decoder__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_decoder__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - *pFormat = pDecoder->outputFormat; - *pChannels = pDecoder->outputChannels; - *pSampleRate = pDecoder->outputSampleRate; - - return MA_SUCCESS; + return ma_decoder_get_data_format((ma_decoder*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); } static ma_result ma_decoder__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - return ma_decoder_get_cursor_in_pcm_frames(pDecoder, pCursor); + return ma_decoder_get_cursor_in_pcm_frames((ma_decoder*)pDataSource, pCursor); } static ma_result ma_decoder__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) { - ma_decoder* pDecoder = (ma_decoder*)pDataSource; - - *pLength = ma_decoder_get_length_in_pcm_frames(pDecoder); - if (*pLength == 0) { - return MA_NOT_IMPLEMENTED; - } - - return MA_SUCCESS; + return ma_decoder_get_length_in_pcm_frames((ma_decoder*)pDataSource, pLength); } static ma_data_source_vtable g_ma_decoder_data_source_vtable = { ma_decoder__data_source_on_read, ma_decoder__data_source_on_seek, - NULL, /* onMap */ - NULL, /* onUnmap */ ma_decoder__data_source_on_get_data_format, ma_decoder__data_source_on_get_cursor, - ma_decoder__data_source_on_get_length + ma_decoder__data_source_on_get_length, + NULL, /* onSetLooping */ + 0 }; static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, ma_decoder_tell_proc onTell, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) @@ -50206,22 +61477,9 @@ static ma_result ma_decoder__preinit(ma_decoder_read_proc onRead, ma_decoder_see static ma_result ma_decoder__postinit(const ma_decoder_config* pConfig, ma_decoder* pDecoder) { - ma_result result = MA_SUCCESS; + ma_result result; - /* Basic validation in case the internal decoder supports different limits to miniaudio. */ - { - /* TODO: Remove this block once we remove MA_MIN_CHANNELS and MA_MAX_CHANNELS. */ - ma_uint32 internalChannels; - ma_data_source_get_data_format(pDecoder->pBackend, NULL, &internalChannels, NULL); - - if (internalChannels < MA_MIN_CHANNELS || internalChannels > MA_MAX_CHANNELS) { - result = MA_INVALID_DATA; - } - } - - if (result == MA_SUCCESS) { - result = ma_decoder__init_data_converter(pDecoder, pConfig); - } + result = ma_decoder__init_data_converter(pDecoder, pConfig); /* If we failed post initialization we need to uninitialize the decoder before returning to prevent a memory leak. */ if (result != MA_SUCCESS) { @@ -50232,83 +61490,6 @@ static ma_result ma_decoder__postinit(const ma_decoder_config* pConfig, ma_decod return result; } -MA_API ma_result ma_decoder_init_wav(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_flac(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_mp3(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vorbis(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init(onRead, onSeek, pUserData, &config, pDecoder); -#else - (void)onRead; - (void)onSeek; - (void)pUserData; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - static ma_result ma_decoder_init__internal(ma_decoder_read_proc onRead, ma_decoder_seek_proc onSeek, void* pUserData, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { @@ -50431,29 +61612,41 @@ MA_API ma_result ma_decoder_init(ma_decoder_read_proc onRead, ma_decoder_seek_pr } -static size_t ma_decoder__on_read_memory(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +static ma_result ma_decoder__on_read_memory(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { size_t bytesRemaining; MA_ASSERT(pDecoder->data.memory.dataSize >= pDecoder->data.memory.currentReadPos); + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + bytesRemaining = pDecoder->data.memory.dataSize - pDecoder->data.memory.currentReadPos; if (bytesToRead > bytesRemaining) { bytesToRead = bytesRemaining; } + if (bytesRemaining == 0) { + return MA_AT_END; + } + if (bytesToRead > 0) { MA_COPY_MEMORY(pBufferOut, pDecoder->data.memory.pData + pDecoder->data.memory.currentReadPos, bytesToRead); pDecoder->data.memory.currentReadPos += bytesToRead; } - return bytesToRead; + if (pBytesRead != NULL) { + *pBytesRead = bytesToRead; + } + + return MA_SUCCESS; } -static ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) +static ma_result ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteOffset, ma_seek_origin origin) { if (byteOffset > 0 && (ma_uint64)byteOffset > MA_SIZE_MAX) { - return MA_FALSE; /* Too far. */ + return MA_BAD_SEEK; } if (origin == ma_seek_origin_current) { @@ -50490,7 +61683,7 @@ static ma_bool32 ma_decoder__on_seek_memory(ma_decoder* pDecoder, ma_int64 byteO } } - return MA_TRUE; + return MA_SUCCESS; } static ma_result ma_decoder__on_tell_memory(ma_decoder* pDecoder, ma_int64* pCursor) @@ -50537,79 +61730,6 @@ MA_API ma_result ma_decoder_init_memory(const void* pData, size_t dataSize, cons return ma_decoder_init__internal(ma_decoder__on_read_memory, ma_decoder__on_seek_memory, NULL, &config, pDecoder); } -MA_API ma_result ma_decoder_init_memory_wav(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_flac(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_mp3(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_memory_vorbis(const void* pData, size_t dataSize, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); /* Make sure the config is not NULL. */ - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_memory(pData, dataSize, &config, pDecoder); -#else - (void)pData; - (void)dataSize; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - #if defined(MA_HAS_WAV) || \ defined(MA_HAS_MP3) || \ @@ -50790,30 +61910,19 @@ static ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* e -static size_t ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead) +static ma_result ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, size_t bytesToRead, size_t* pBytesRead) { - size_t bytesRead; - MA_ASSERT(pDecoder != NULL); MA_ASSERT(pBufferOut != NULL); - ma_vfs_or_default_read(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, pBufferOut, bytesToRead, &bytesRead); - - return bytesRead; + return ma_vfs_or_default_read(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, pBufferOut, bytesToRead, pBytesRead); } -static ma_bool32 ma_decoder__on_seek_vfs(ma_decoder* pDecoder, ma_int64 offset, ma_seek_origin origin) +static ma_result ma_decoder__on_seek_vfs(ma_decoder* pDecoder, ma_int64 offset, ma_seek_origin origin) { - ma_result result; - MA_ASSERT(pDecoder != NULL); - result = ma_vfs_or_default_seek(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, offset, origin); - if (result != MA_SUCCESS) { - return MA_FALSE; - } - - return MA_TRUE; + return ma_vfs_or_default_seek(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file, offset, origin); } static ma_result ma_decoder__on_tell_vfs(ma_decoder* pDecoder, ma_int64* pCursor) @@ -50955,79 +62064,6 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const return MA_SUCCESS; } -MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_vfs(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { @@ -51158,132 +62194,16 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c return MA_SUCCESS; } -MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_WAV - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_wav; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_FLAC - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_flac; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_MP3 - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_mp3; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - -MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ -#ifdef MA_HAS_VORBIS - ma_decoder_config config; - - config = ma_decoder_config_init_copy(pConfig); - config.encodingFormat = ma_encoding_format_vorbis; - - return ma_decoder_init_vfs_w(pVFS, pFilePath, &config, pDecoder); -#else - (void)pVFS; - (void)pFilePath; - (void)pConfig; - (void)pDecoder; - return MA_NO_BACKEND; -#endif -} - - - MA_API ma_result ma_decoder_init_file(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { return ma_decoder_init_vfs(NULL, pFilePath, pConfig, pDecoder); } -MA_API ma_result ma_decoder_init_file_wav(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_wav(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_flac(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_flac(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_mp3(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_mp3(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_vorbis(const char* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_vorbis(NULL, pFilePath, pConfig, pDecoder); -} - - - MA_API ma_result ma_decoder_init_file_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) { return ma_decoder_init_vfs_w(NULL, pFilePath, pConfig, pDecoder); } -MA_API ma_result ma_decoder_init_file_wav_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_wav_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_flac_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_flac_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_mp3_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_mp3_w(NULL, pFilePath, pConfig, pDecoder); -} - -MA_API ma_result ma_decoder_init_file_vorbis_w(const wchar_t* pFilePath, const ma_decoder_config* pConfig, ma_decoder* pDecoder) -{ - return ma_decoder_init_vfs_vorbis_w(NULL, pFilePath, pConfig, pDecoder); -} - MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) { if (pDecoder == NULL) { @@ -51296,15 +62216,244 @@ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) } } - /* Legacy. */ if (pDecoder->onRead == ma_decoder__on_read_vfs) { ma_vfs_or_default_close(pDecoder->data.vfs.pVFS, pDecoder->data.vfs.file); pDecoder->data.vfs.file = NULL; } - ma_data_converter_uninit(&pDecoder->converter); + ma_data_converter_uninit(&pDecoder->converter, &pDecoder->allocationCallbacks); ma_data_source_uninit(&pDecoder->ds); + if (pDecoder->pInputCache != NULL) { + ma_free(pDecoder->pInputCache, &pDecoder->allocationCallbacks); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesReadOut; + void* pRunningFramesOut; + + if (pFramesRead != NULL) { + *pFramesRead = 0; /* Safety. */ + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pDecoder->pBackend == NULL) { + return MA_INVALID_OPERATION; + } + + /* Fast path. */ + if (pDecoder->converter.isPassthrough) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pFramesOut, frameCount, &totalFramesReadOut); + } else { + /* + Getting here means we need to do data conversion. If we're seeking forward and are _not_ doing resampling we can run this in a fast path. If we're doing resampling we + need to run through each sample because we need to ensure it's internal cache is updated. + */ + if (pFramesOut == NULL && pDecoder->converter.hasResampler == MA_FALSE) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut); + } else { + /* Slow path. Need to run everything through the data converter. */ + ma_format internalFormat; + ma_uint32 internalChannels; + + totalFramesReadOut = 0; + pRunningFramesOut = pFramesOut; + + result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, NULL, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal format and channel count. */ + } + + /* + We run a different path depending on whether or not we are using a heap-allocated + intermediary buffer or not. If the data converter does not support the calculation of + the required number of input frames, we'll use the heap-allocated path. Otherwise we'll + use the stack-allocated path. + */ + if (pDecoder->pInputCache != NULL) { + /* We don't have a way of determining the required number of input frames, so need to persistently store input data in a cache. */ + while (totalFramesReadOut < frameCount) { + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; + + /* If there's any data available in the cache, that needs to get processed first. */ + if (pDecoder->inputCacheRemaining > 0) { + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > pDecoder->inputCacheRemaining) { + framesToReadThisIterationIn = pDecoder->inputCacheRemaining; + } + + result = ma_data_converter_process_pcm_frames(&pDecoder->converter, ma_offset_pcm_frames_ptr(pDecoder->pInputCache, pDecoder->inputCacheConsumed, internalFormat, internalChannels), &framesToReadThisIterationIn, pRunningFramesOut, &framesToReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } + + pDecoder->inputCacheConsumed += framesToReadThisIterationIn; + pDecoder->inputCacheRemaining -= framesToReadThisIterationIn; + + totalFramesReadOut += framesToReadThisIterationOut; + + if (pRunningFramesOut != NULL) { + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesToReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); + } + + if (framesToReadThisIterationIn == 0 && framesToReadThisIterationOut == 0) { + break; /* We're done. */ + } + } + + /* Getting here means there's no data in the cache and we need to fill it up from the data source. */ + if (pDecoder->inputCacheRemaining == 0) { + pDecoder->inputCacheConsumed = 0; + + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pDecoder->pInputCache, pDecoder->inputCacheCap, &pDecoder->inputCacheRemaining); + if (result != MA_SUCCESS) { + break; + } + } + } + } else { + /* We have a way of determining the required number of input frames so just use the stack. */ + while (totalFramesReadOut < frameCount) { + ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In internal format. */ + ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(internalFormat, internalChannels); + ma_uint64 framesToReadThisIterationIn; + ma_uint64 framesReadThisIterationIn; + ma_uint64 framesToReadThisIterationOut; + ma_uint64 framesReadThisIterationOut; + ma_uint64 requiredInputFrameCount; + + framesToReadThisIterationOut = (frameCount - totalFramesReadOut); + framesToReadThisIterationIn = framesToReadThisIterationOut; + if (framesToReadThisIterationIn > intermediaryBufferCap) { + framesToReadThisIterationIn = intermediaryBufferCap; + } + + ma_data_converter_get_required_input_frame_count(&pDecoder->converter, framesToReadThisIterationOut, &requiredInputFrameCount); + if (framesToReadThisIterationIn > requiredInputFrameCount) { + framesToReadThisIterationIn = requiredInputFrameCount; + } + + if (requiredInputFrameCount > 0) { + result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn); + } else { + framesReadThisIterationIn = 0; + } + + /* + At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any + input frames, we still want to try processing frames because there may some output frames generated from cached input data. + */ + framesReadThisIterationOut = framesToReadThisIterationOut; + result = ma_data_converter_process_pcm_frames(&pDecoder->converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); + if (result != MA_SUCCESS) { + break; + } + + totalFramesReadOut += framesReadThisIterationOut; + + if (pRunningFramesOut != NULL) { + pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); + } + + if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { + break; /* We're done. */ + } + } + } + } + } + + pDecoder->readPointerInPCMFrames += totalFramesReadOut; + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesReadOut; + } + + if (result == MA_SUCCESS && totalFramesReadOut == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex) +{ + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pDecoder->pBackend != NULL) { + ma_result result; + ma_uint64 internalFrameIndex; + ma_uint32 internalSampleRate; + ma_uint64 currentFrameIndex; + + result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal sample rate. */ + } + + if (internalSampleRate == pDecoder->outputSampleRate) { + internalFrameIndex = frameIndex; + } else { + internalFrameIndex = ma_calculate_frame_count_after_resampling(internalSampleRate, pDecoder->outputSampleRate, frameIndex); + } + + /* Only seek if we're requesting a different frame to what we're currently sitting on. */ + ma_data_source_get_cursor_in_pcm_frames(pDecoder->pBackend, ¤tFrameIndex); + if (currentFrameIndex != internalFrameIndex) { + result = ma_data_source_seek_to_pcm_frame(pDecoder->pBackend, internalFrameIndex); + if (result == MA_SUCCESS) { + pDecoder->readPointerInPCMFrames = frameIndex; + } + + /* Reset the data converter so that any cached data in the resampler is cleared. */ + ma_data_converter_reset(&pDecoder->converter); + } + + return result; + } + + /* Should never get here, but if we do it means onSeekToPCMFrame was not set by the backend. */ + return MA_INVALID_ARGS; +} + +MA_API ma_result ma_decoder_get_data_format(ma_decoder* pDecoder, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pDecoder == NULL) { + return MA_INVALID_ARGS; + } + + if (pFormat != NULL) { + *pFormat = pDecoder->outputFormat; + } + + if (pChannels != NULL) { + *pChannels = pDecoder->outputChannels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pDecoder->outputSampleRate; + } + + if (pChannelMap != NULL) { + ma_data_converter_get_output_channel_map(&pDecoder->converter, pChannelMap, channelMapCap); + } + return MA_SUCCESS; } @@ -51325,164 +62474,48 @@ MA_API ma_result ma_decoder_get_cursor_in_pcm_frames(ma_decoder* pDecoder, ma_ui return MA_SUCCESS; } -MA_API ma_uint64 ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder) +MA_API ma_result ma_decoder_get_length_in_pcm_frames(ma_decoder* pDecoder, ma_uint64* pLength) { - if (pDecoder == NULL) { - return 0; + if (pLength == NULL) { + return MA_INVALID_ARGS; } - if (pDecoder->pBackend != NULL) { - ma_result result; - ma_uint64 nativeLengthInPCMFrames; - ma_uint32 internalSampleRate; + *pLength = 0; - ma_data_source_get_length_in_pcm_frames(pDecoder->pBackend, &nativeLengthInPCMFrames); - - result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate); - if (result != MA_SUCCESS) { - return 0; /* Failed to retrieve the internal sample rate. */ - } - - if (internalSampleRate == pDecoder->outputSampleRate) { - return nativeLengthInPCMFrames; - } else { - return ma_calculate_frame_count_after_resampling(pDecoder->outputSampleRate, internalSampleRate, nativeLengthInPCMFrames); - } - } - - return 0; -} - -MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesOut, ma_uint64 frameCount) -{ - ma_result result; - ma_uint64 totalFramesReadOut; - ma_uint64 totalFramesReadIn; - void* pRunningFramesOut; - - if (pDecoder == NULL) { - return 0; - } - - if (pDecoder->pBackend == NULL) { - return 0; - } - - /* Fast path. */ - if (pDecoder->converter.isPassthrough) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pFramesOut, frameCount, &totalFramesReadOut, MA_FALSE); - } else { - /* - Getting here means we need to do data conversion. If we're seeking forward and are _not_ doing resampling we can run this in a fast path. If we're doing resampling we - need to run through each sample because we need to ensure it's internal cache is updated. - */ - if (pFramesOut == NULL && pDecoder->converter.hasResampler == MA_FALSE) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, NULL, frameCount, &totalFramesReadOut, MA_FALSE); - } else { - /* Slow path. Need to run everything through the data converter. */ - ma_format internalFormat; - ma_uint32 internalChannels; - - totalFramesReadOut = 0; - totalFramesReadIn = 0; - pRunningFramesOut = pFramesOut; - - result = ma_data_source_get_data_format(pDecoder->pBackend, &internalFormat, &internalChannels, NULL); - if (result != MA_SUCCESS) { - return 0; /* Failed to retrieve the internal format and channel count. */ - } - - while (totalFramesReadOut < frameCount) { - ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In internal format. */ - ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(internalFormat, internalChannels); - ma_uint64 framesToReadThisIterationIn; - ma_uint64 framesReadThisIterationIn; - ma_uint64 framesToReadThisIterationOut; - ma_uint64 framesReadThisIterationOut; - ma_uint64 requiredInputFrameCount; - - framesToReadThisIterationOut = (frameCount - totalFramesReadOut); - framesToReadThisIterationIn = framesToReadThisIterationOut; - if (framesToReadThisIterationIn > intermediaryBufferCap) { - framesToReadThisIterationIn = intermediaryBufferCap; - } - - requiredInputFrameCount = ma_data_converter_get_required_input_frame_count(&pDecoder->converter, framesToReadThisIterationOut); - if (framesToReadThisIterationIn > requiredInputFrameCount) { - framesToReadThisIterationIn = requiredInputFrameCount; - } - - if (requiredInputFrameCount > 0) { - result = ma_data_source_read_pcm_frames(pDecoder->pBackend, pIntermediaryBuffer, framesToReadThisIterationIn, &framesReadThisIterationIn, MA_FALSE); - totalFramesReadIn += framesReadThisIterationIn; - } else { - framesReadThisIterationIn = 0; - } - - /* - At this point we have our decoded data in input format and now we need to convert to output format. Note that even if we didn't read any - input frames, we still want to try processing frames because there may some output frames generated from cached input data. - */ - framesReadThisIterationOut = framesToReadThisIterationOut; - result = ma_data_converter_process_pcm_frames(&pDecoder->converter, pIntermediaryBuffer, &framesReadThisIterationIn, pRunningFramesOut, &framesReadThisIterationOut); - if (result != MA_SUCCESS) { - break; - } - - totalFramesReadOut += framesReadThisIterationOut; - - if (pRunningFramesOut != NULL) { - pRunningFramesOut = ma_offset_ptr(pRunningFramesOut, framesReadThisIterationOut * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels)); - } - - if (framesReadThisIterationIn == 0 && framesReadThisIterationOut == 0) { - break; /* We're done. */ - } - } - } - } - - pDecoder->readPointerInPCMFrames += totalFramesReadOut; - - return totalFramesReadOut; -} - -MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 frameIndex) -{ if (pDecoder == NULL) { return MA_INVALID_ARGS; } if (pDecoder->pBackend != NULL) { ma_result result; - ma_uint64 internalFrameIndex; + ma_uint64 internalLengthInPCMFrames; ma_uint32 internalSampleRate; - result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate); + result = ma_data_source_get_length_in_pcm_frames(pDecoder->pBackend, &internalLengthInPCMFrames); if (result != MA_SUCCESS) { - return result; /* Failed to retrieve the internal sample rate. */ + return result; /* Failed to retrieve the internal length. */ + } + + result = ma_data_source_get_data_format(pDecoder->pBackend, NULL, NULL, &internalSampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the internal sample rate. */ } if (internalSampleRate == pDecoder->outputSampleRate) { - internalFrameIndex = frameIndex; + *pLength = internalLengthInPCMFrames; } else { - internalFrameIndex = ma_calculate_frame_count_after_resampling(internalSampleRate, pDecoder->outputSampleRate, frameIndex); + *pLength = ma_calculate_frame_count_after_resampling(pDecoder->outputSampleRate, internalSampleRate, internalLengthInPCMFrames); } - result = ma_data_source_seek_to_pcm_frame(pDecoder->pBackend, internalFrameIndex); - if (result == MA_SUCCESS) { - pDecoder->readPointerInPCMFrames = frameIndex; - } - - return result; + return MA_SUCCESS; + } else { + return MA_NO_BACKEND; } - - /* Should never get here, but if we do it means onSeekToPCMFrame was not set by the backend. */ - return MA_INVALID_ARGS; } MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64* pAvailableFrames) { + ma_result result; ma_uint64 totalFrameCount; if (pAvailableFrames == NULL) { @@ -51495,9 +62528,9 @@ MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64 return MA_INVALID_ARGS; } - totalFrameCount = ma_decoder_get_length_in_pcm_frames(pDecoder); - if (totalFrameCount == 0) { - return MA_NOT_IMPLEMENTED; + result = ma_decoder_get_length_in_pcm_frames(pDecoder, &totalFrameCount); + if (result != MA_SUCCESS) { + return result; } if (totalFrameCount <= pDecoder->readPointerInPCMFrames) { @@ -51506,12 +62539,13 @@ MA_API ma_result ma_decoder_get_available_frames(ma_decoder* pDecoder, ma_uint64 *pAvailableFrames = totalFrameCount - pDecoder->readPointerInPCMFrames; } - return MA_SUCCESS; /* No frames available. */ + return MA_SUCCESS; } static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_decoder_config* pConfigOut, ma_uint64* pFrameCountOut, void** ppPCMFramesOut) { + ma_result result; ma_uint64 totalFrameCount; ma_uint64 bpf; ma_uint64 dataCapInFrames; @@ -51532,21 +62566,19 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec /* Make room if there's not enough. */ if (totalFrameCount == dataCapInFrames) { void* pNewPCMFramesOut; - ma_uint64 oldDataCapInFrames = dataCapInFrames; ma_uint64 newDataCapInFrames = dataCapInFrames*2; if (newDataCapInFrames == 0) { newDataCapInFrames = 4096; } if ((newDataCapInFrames * bpf) > MA_SIZE_MAX) { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); return MA_TOO_BIG; } - - pNewPCMFramesOut = (void*)ma__realloc_from_callbacks(pPCMFramesOut, (size_t)(newDataCapInFrames * bpf), (size_t)(oldDataCapInFrames * bpf), &pDecoder->allocationCallbacks); + pNewPCMFramesOut = (void*)ma_realloc(pPCMFramesOut, (size_t)(newDataCapInFrames * bpf), &pDecoder->allocationCallbacks); if (pNewPCMFramesOut == NULL) { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); return MA_OUT_OF_MEMORY; } @@ -51557,9 +62589,13 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec frameCountToTryReading = dataCapInFrames - totalFrameCount; MA_ASSERT(frameCountToTryReading > 0); - framesJustRead = ma_decoder_read_pcm_frames(pDecoder, (ma_uint8*)pPCMFramesOut + (totalFrameCount * bpf), frameCountToTryReading); + result = ma_decoder_read_pcm_frames(pDecoder, (ma_uint8*)pPCMFramesOut + (totalFrameCount * bpf), frameCountToTryReading, &framesJustRead); totalFrameCount += framesJustRead; + if (result != MA_SUCCESS) { + break; + } + if (framesJustRead < frameCountToTryReading) { break; } @@ -51567,16 +62603,15 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec if (pConfigOut != NULL) { - pConfigOut->format = pDecoder->outputFormat; - pConfigOut->channels = pDecoder->outputChannels; + pConfigOut->format = pDecoder->outputFormat; + pConfigOut->channels = pDecoder->outputChannels; pConfigOut->sampleRate = pDecoder->outputSampleRate; - ma_channel_map_copy(pConfigOut->channelMap, pDecoder->outputChannelMap, pDecoder->outputChannels); } if (ppPCMFramesOut != NULL) { *ppPCMFramesOut = pPCMFramesOut; } else { - ma__free_from_callbacks(pPCMFramesOut, &pDecoder->allocationCallbacks); + ma_free(pPCMFramesOut, &pDecoder->allocationCallbacks); } if (pFrameCountOut != NULL) { @@ -51652,17 +62687,27 @@ MA_API ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder static size_t ma_encoder__internal_on_write_wav(void* pUserData, const void* pData, size_t bytesToWrite) { ma_encoder* pEncoder = (ma_encoder*)pUserData; + size_t bytesWritten = 0; + MA_ASSERT(pEncoder != NULL); - return pEncoder->onWrite(pEncoder, pData, bytesToWrite); + pEncoder->onWrite(pEncoder, pData, bytesToWrite, &bytesWritten); + return bytesWritten; } static drwav_bool32 ma_encoder__internal_on_seek_wav(void* pUserData, int offset, drwav_seek_origin origin) { ma_encoder* pEncoder = (ma_encoder*)pUserData; + ma_result result; + MA_ASSERT(pEncoder != NULL); - return pEncoder->onSeek(pEncoder, offset, (origin == drwav_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + result = pEncoder->onSeek(pEncoder, offset, (origin == drwav_seek_origin_start) ? ma_seek_origin_start : ma_seek_origin_current); + if (result != MA_SUCCESS) { + return DRWAV_FALSE; + } else { + return DRWAV_TRUE; + } } static ma_result ma_encoder__on_init_wav(ma_encoder* pEncoder) @@ -51673,7 +62718,7 @@ static ma_result ma_encoder__on_init_wav(ma_encoder* pEncoder) MA_ASSERT(pEncoder != NULL); - pWav = (drwav*)ma__malloc_from_callbacks(sizeof(*pWav), &pEncoder->config.allocationCallbacks); + pWav = (drwav*)ma_malloc(sizeof(*pWav), &pEncoder->config.allocationCallbacks); if (pWav == NULL) { return MA_OUT_OF_MEMORY; } @@ -51712,28 +62757,35 @@ static void ma_encoder__on_uninit_wav(ma_encoder* pEncoder) MA_ASSERT(pWav != NULL); drwav_uninit(pWav); - ma__free_from_callbacks(pWav, &pEncoder->config.allocationCallbacks); + ma_free(pWav, &pEncoder->config.allocationCallbacks); } -static ma_uint64 ma_encoder__on_write_pcm_frames_wav(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount) +static ma_result ma_encoder__on_write_pcm_frames_wav(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten) { drwav* pWav; + ma_uint64 framesWritten; MA_ASSERT(pEncoder != NULL); pWav = (drwav*)pEncoder->pInternalEncoder; MA_ASSERT(pWav != NULL); - return drwav_write_pcm_frames(pWav, frameCount, pFramesIn); + framesWritten = drwav_write_pcm_frames(pWav, frameCount, pFramesIn); + + if (pFramesWritten != NULL) { + *pFramesWritten = framesWritten; + } + + return MA_SUCCESS; } #endif -MA_API ma_encoder_config ma_encoder_config_init(ma_resource_format resourceFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +MA_API ma_encoder_config ma_encoder_config_init(ma_encoding_format encodingFormat, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) { ma_encoder_config config; MA_ZERO_OBJECT(&config); - config.resourceFormat = resourceFormat; + config.encodingFormat = encodingFormat; config.format = format; config.channels = channels; config.sampleRate = sampleRate; @@ -51784,9 +62836,9 @@ MA_API ma_result ma_encoder_init__internal(ma_encoder_write_proc onWrite, ma_enc pEncoder->onSeek = onSeek; pEncoder->pUserData = pUserData; - switch (pEncoder->config.resourceFormat) + switch (pEncoder->config.encodingFormat) { - case ma_resource_format_wav: + case ma_encoding_format_wav: { #if defined(MA_HAS_WAV) pEncoder->onInit = ma_encoder__on_init_wav; @@ -51806,64 +62858,85 @@ MA_API ma_result ma_encoder_init__internal(ma_encoder_write_proc onWrite, ma_enc /* Getting here means we should have our backend callbacks set up. */ if (result == MA_SUCCESS) { result = pEncoder->onInit(pEncoder); - if (result != MA_SUCCESS) { - return result; - } + } + + return result; +} + +static ma_result ma_encoder__on_write_vfs(ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite, size_t* pBytesWritten) +{ + return ma_vfs_or_default_write(pEncoder->data.vfs.pVFS, pEncoder->data.vfs.file, pBufferIn, bytesToWrite, pBytesWritten); +} + +static ma_result ma_encoder__on_seek_vfs(ma_encoder* pEncoder, ma_int64 offset, ma_seek_origin origin) +{ + return ma_vfs_or_default_seek(pEncoder->data.vfs.pVFS, pEncoder->data.vfs.file, offset, origin); +} + +MA_API ma_result ma_encoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder) +{ + ma_result result; + ma_vfs_file file; + + result = ma_encoder_preinit(pConfig, pEncoder); + if (result != MA_SUCCESS) { + return result; + } + + /* Now open the file. If this fails we don't need to uninitialize the encoder. */ + result = ma_vfs_or_default_open(pVFS, pFilePath, MA_OPEN_MODE_WRITE, &file); + if (result != MA_SUCCESS) { + return result; + } + + pEncoder->data.vfs.pVFS = pVFS; + pEncoder->data.vfs.file = file; + + result = ma_encoder_init__internal(ma_encoder__on_write_vfs, ma_encoder__on_seek_vfs, NULL, pEncoder); + if (result != MA_SUCCESS) { + ma_vfs_or_default_close(pVFS, file); + return result; } return MA_SUCCESS; } -MA_API size_t ma_encoder__on_write_stdio(ma_encoder* pEncoder, const void* pBufferIn, size_t bytesToWrite) +MA_API ma_result ma_encoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder) { - return fwrite(pBufferIn, 1, bytesToWrite, (FILE*)pEncoder->pFile); -} + ma_result result; + ma_vfs_file file; -MA_API ma_bool32 ma_encoder__on_seek_stdio(ma_encoder* pEncoder, int byteOffset, ma_seek_origin origin) -{ - return fseek((FILE*)pEncoder->pFile, byteOffset, (origin == ma_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; + result = ma_encoder_preinit(pConfig, pEncoder); + if (result != MA_SUCCESS) { + return result; + } + + /* Now open the file. If this fails we don't need to uninitialize the encoder. */ + result = ma_vfs_or_default_open_w(pVFS, pFilePath, MA_OPEN_MODE_WRITE, &file); + if (result != MA_SUCCESS) { + return result; + } + + pEncoder->data.vfs.pVFS = pVFS; + pEncoder->data.vfs.file = file; + + result = ma_encoder_init__internal(ma_encoder__on_write_vfs, ma_encoder__on_seek_vfs, NULL, pEncoder); + if (result != MA_SUCCESS) { + ma_vfs_or_default_close(pVFS, file); + return result; + } + + return MA_SUCCESS; } MA_API ma_result ma_encoder_init_file(const char* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder) { - ma_result result; - FILE* pFile; - - result = ma_encoder_preinit(pConfig, pEncoder); - if (result != MA_SUCCESS) { - return result; - } - - /* Now open the file. If this fails we don't need to uninitialize the encoder. */ - result = ma_fopen(&pFile, pFilePath, "wb"); - if (pFile == NULL) { - return result; - } - - pEncoder->pFile = pFile; - - return ma_encoder_init__internal(ma_encoder__on_write_stdio, ma_encoder__on_seek_stdio, NULL, pEncoder); + return ma_encoder_init_vfs(NULL, pFilePath, pConfig, pEncoder); } MA_API ma_result ma_encoder_init_file_w(const wchar_t* pFilePath, const ma_encoder_config* pConfig, ma_encoder* pEncoder) { - ma_result result; - FILE* pFile; - - result = ma_encoder_preinit(pConfig, pEncoder); - if (result != MA_SUCCESS) { - return result; - } - - /* Now open the file. If this fails we don't need to uninitialize the encoder. */ - result = ma_wfopen(&pFile, pFilePath, L"wb", &pEncoder->config.allocationCallbacks); - if (pFile == NULL) { - return result; - } - - pEncoder->pFile = pFile; - - return ma_encoder_init__internal(ma_encoder__on_write_stdio, ma_encoder__on_seek_stdio, NULL, pEncoder); + return ma_encoder_init_vfs_w(NULL, pFilePath, pConfig, pEncoder); } MA_API ma_result ma_encoder_init(ma_encoder_write_proc onWrite, ma_encoder_seek_proc onSeek, void* pUserData, const ma_encoder_config* pConfig, ma_encoder* pEncoder) @@ -51890,19 +62963,24 @@ MA_API void ma_encoder_uninit(ma_encoder* pEncoder) } /* If we have a file handle, close it. */ - if (pEncoder->onWrite == ma_encoder__on_write_stdio) { - fclose((FILE*)pEncoder->pFile); + if (pEncoder->onWrite == ma_encoder__on_write_vfs) { + ma_vfs_or_default_close(pEncoder->data.vfs.pVFS, pEncoder->data.vfs.file); + pEncoder->data.vfs.file = NULL; } } -MA_API ma_uint64 ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount) +MA_API ma_result ma_encoder_write_pcm_frames(ma_encoder* pEncoder, const void* pFramesIn, ma_uint64 frameCount, ma_uint64* pFramesWritten) { - if (pEncoder == NULL || pFramesIn == NULL) { - return 0; + if (pFramesWritten != NULL) { + *pFramesWritten = 0; } - return pEncoder->onWritePCMFrames(pEncoder, pFramesIn, frameCount); + if (pEncoder == NULL || pFramesIn == NULL) { + return MA_INVALID_ARGS; + } + + return pEncoder->onWritePCMFrames(pEncoder, pFramesIn, frameCount, pFramesWritten); } #endif /* MA_NO_ENCODING */ @@ -51931,17 +63009,7 @@ MA_API ma_waveform_config ma_waveform_config_init(ma_format format, ma_uint32 ch static ma_result ma_waveform__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_waveform_read_pcm_frames((ma_waveform*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_waveform_read_pcm_frames((ma_waveform*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_waveform__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -51949,13 +63017,14 @@ static ma_result ma_waveform__data_source_on_seek(ma_data_source* pDataSource, m return ma_waveform_seek_to_pcm_frame((ma_waveform*)pDataSource, frameIndex); } -static ma_result ma_waveform__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_waveform__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_waveform* pWaveform = (ma_waveform*)pDataSource; *pFormat = pWaveform->config.format; *pChannels = pWaveform->config.channels; *pSampleRate = pWaveform->config.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pWaveform->config.channels); return MA_SUCCESS; } @@ -51983,11 +63052,11 @@ static ma_data_source_vtable g_ma_waveform_data_source_vtable = { ma_waveform__data_source_on_read, ma_waveform__data_source_on_seek, - NULL, /* onMap */ - NULL, /* onUnmap */ ma_waveform__data_source_on_get_data_format, ma_waveform__data_source_on_get_cursor, - NULL /* onGetLength. There's no notion of a length in waveforms. */ + NULL, /* onGetLength. There's no notion of a length in waveforms. */ + NULL, /* onSetLooping */ + 0 }; MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform) @@ -52296,10 +63365,18 @@ static void ma_waveform_read_pcm_frames__sawtooth(ma_waveform* pWaveform, void* } } -MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount) +MA_API ma_result ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pWaveform == NULL) { - return 0; + return MA_INVALID_ARGS; } if (pFramesOut != NULL) { @@ -52325,13 +63402,17 @@ MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFram ma_waveform_read_pcm_frames__sawtooth(pWaveform, pFramesOut, frameCount); } break; - default: return 0; + default: return MA_INVALID_OPERATION; /* Unknown waveform type. */ } } else { pWaveform->time += pWaveform->advance * (ma_int64)frameCount; /* Cast to int64 required for VC6. Won't affect anything in practice. */ } - return frameCount; + if (pFramesRead != NULL) { + *pFramesRead = frameCount; + } + + return MA_SUCCESS; } MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex) @@ -52367,17 +63448,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels static ma_result ma_noise__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { - ma_uint64 framesRead = ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead == 0) { - return MA_AT_END; - } - - return MA_SUCCESS; + return ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount, pFramesRead); } static ma_result ma_noise__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) @@ -52388,13 +63459,14 @@ static ma_result ma_noise__data_source_on_seek(ma_data_source* pDataSource, ma_u return MA_SUCCESS; } -static ma_result ma_noise__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +static ma_result ma_noise__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) { ma_noise* pNoise = (ma_noise*)pDataSource; *pFormat = pNoise->config.format; *pChannels = pNoise->config.channels; *pSampleRate = 0; /* There is no notion of sample rate with noise generation. */ + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pNoise->config.channels); return MA_SUCCESS; } @@ -52403,17 +63475,105 @@ static ma_data_source_vtable g_ma_noise_data_source_vtable = { ma_noise__data_source_on_read, ma_noise__data_source_on_seek, /* No-op for noise. */ - NULL, /* onMap */ - NULL, /* onUnmap */ ma_noise__data_source_on_get_data_format, NULL, /* onGetCursor. No notion of a cursor for noise. */ - NULL /* onGetLength. No notion of a length for noise. */ + NULL, /* onGetLength. No notion of a length for noise. */ + NULL, /* onSetLooping */ + 0 }; -MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) + +#ifndef MA_PINK_NOISE_BIN_SIZE +#define MA_PINK_NOISE_BIN_SIZE 16 +#endif + +typedef struct +{ + size_t sizeInBytes; + struct + { + size_t binOffset; + size_t accumulationOffset; + size_t counterOffset; + } pink; + struct + { + size_t accumulationOffset; + } brownian; +} ma_noise_heap_layout; + +static ma_result ma_noise_get_heap_layout(const ma_noise_config* pConfig, ma_noise_heap_layout* pHeapLayout) +{ + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->channels == 0) { + return MA_INVALID_ARGS; + } + + pHeapLayout->sizeInBytes = 0; + + /* Pink. */ + if (pConfig->type == ma_noise_type_pink) { + /* bin */ + pHeapLayout->pink.binOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double*) * pConfig->channels; + pHeapLayout->sizeInBytes += sizeof(double ) * pConfig->channels * MA_PINK_NOISE_BIN_SIZE; + + /* accumulation */ + pHeapLayout->pink.accumulationOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double) * pConfig->channels; + + /* counter */ + pHeapLayout->pink.counterOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(ma_uint32) * pConfig->channels; + } + + /* Brownian. */ + if (pConfig->type == ma_noise_type_brownian) { + /* accumulation */ + pHeapLayout->brownian.accumulationOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += sizeof(double) * pConfig->channels; + } + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_noise_get_heap_size(const ma_noise_config* pConfig, size_t* pHeapSizeInBytes) { ma_result result; + ma_noise_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_noise_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_noise_init_preallocated(const ma_noise_config* pConfig, void* pHeap, ma_noise* pNoise) +{ + ma_result result; + ma_noise_heap_layout heapLayout; ma_data_source_config dataSourceConfig; + ma_uint32 iChannel; if (pNoise == NULL) { return MA_INVALID_ARGS; @@ -52421,13 +63581,13 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) MA_ZERO_OBJECT(pNoise); - if (pConfig == NULL) { - return MA_INVALID_ARGS; + result = ma_noise_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; } - if (pConfig->channels < MA_MIN_CHANNELS || pConfig->channels > MA_MAX_CHANNELS) { - return MA_INVALID_ARGS; - } + pNoise->_pHeap = pHeap; + MA_ZERO_MEMORY(pNoise->_pHeap, heapLayout.sizeInBytes); dataSourceConfig = ma_data_source_config_init(); dataSourceConfig.vtable = &g_ma_noise_data_source_vtable; @@ -52441,15 +63601,20 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) ma_lcg_seed(&pNoise->lcg, pConfig->seed); if (pNoise->config.type == ma_noise_type_pink) { - ma_uint32 iChannel; + pNoise->state.pink.bin = (double** )ma_offset_ptr(pHeap, heapLayout.pink.binOffset); + pNoise->state.pink.accumulation = (double* )ma_offset_ptr(pHeap, heapLayout.pink.accumulationOffset); + pNoise->state.pink.counter = (ma_uint32*)ma_offset_ptr(pHeap, heapLayout.pink.counterOffset); + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { + pNoise->state.pink.bin[iChannel] = (double*)ma_offset_ptr(pHeap, heapLayout.pink.binOffset + (sizeof(double*) * pConfig->channels) + (sizeof(double) * MA_PINK_NOISE_BIN_SIZE * iChannel)); pNoise->state.pink.accumulation[iChannel] = 0; pNoise->state.pink.counter[iChannel] = 1; } } if (pNoise->config.type == ma_noise_type_brownian) { - ma_uint32 iChannel; + pNoise->state.brownian.accumulation = (double*)ma_offset_ptr(pHeap, heapLayout.brownian.accumulationOffset); + for (iChannel = 0; iChannel < pConfig->channels; iChannel += 1) { pNoise->state.brownian.accumulation[iChannel] = 0; } @@ -52458,13 +63623,47 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) return MA_SUCCESS; } -MA_API void ma_noise_uninit(ma_noise* pNoise) +MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_noise* pNoise) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_noise_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_noise_init_preallocated(pConfig, pHeap, pNoise); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pNoise->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_noise_uninit(ma_noise* pNoise, const ma_allocation_callbacks* pAllocationCallbacks) { if (pNoise == NULL) { return; } ma_data_source_uninit(&pNoise->ds); + + if (pNoise->_ownsHeap) { + ma_free(pNoise->_pHeap, pAllocationCallbacks); + } } MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude) @@ -52513,7 +63712,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__white(ma_noise* pNoise, voi ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -52607,7 +63806,7 @@ static MA_INLINE float ma_noise_f32_pink(ma_noise* pNoise, ma_uint32 iChannel) double binNext; unsigned int ibin; - ibin = ma_tzcnt32(pNoise->state.pink.counter[iChannel]) & (ma_countof(pNoise->state.pink.bin[0]) - 1); + ibin = ma_tzcnt32(pNoise->state.pink.counter[iChannel]) & (MA_PINK_NOISE_BIN_SIZE - 1); binPrev = pNoise->state.pink.bin[iChannel][ibin]; binNext = ma_lcg_rand_f64(&pNoise->lcg); @@ -52632,7 +63831,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__pink(ma_noise* pNoise, void ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -52714,7 +63913,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__brownian(ma_noise* pNoise, ma_uint64 iFrame; ma_uint32 iChannel; const ma_uint32 channels = pNoise->config.channels; - MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + MA_ASSUME(channels > 0); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; @@ -52772,37 +63971,9798 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__brownian(ma_noise* pNoise, return frameCount; } -MA_API ma_uint64 ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount) +MA_API ma_result ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { + ma_uint64 framesRead = 0; + + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + if (pNoise == NULL) { - return 0; + return MA_INVALID_ARGS; } /* The output buffer is allowed to be NULL. Since we aren't tracking cursors or anything we can just do nothing and pretend to be successful. */ if (pFramesOut == NULL) { - return frameCount; + framesRead = frameCount; + } else { + switch (pNoise->config.type) { + case ma_noise_type_white: framesRead = ma_noise_read_pcm_frames__white (pNoise, pFramesOut, frameCount); break; + case ma_noise_type_pink: framesRead = ma_noise_read_pcm_frames__pink (pNoise, pFramesOut, frameCount); break; + case ma_noise_type_brownian: framesRead = ma_noise_read_pcm_frames__brownian(pNoise, pFramesOut, frameCount); break; + default: return MA_INVALID_OPERATION; /* Unknown noise type. */ + } } - if (pNoise->config.type == ma_noise_type_white) { - return ma_noise_read_pcm_frames__white(pNoise, pFramesOut, frameCount); + if (pFramesRead != NULL) { + *pFramesRead = framesRead; } - if (pNoise->config.type == ma_noise_type_pink) { - return ma_noise_read_pcm_frames__pink(pNoise, pFramesOut, frameCount); - } - - if (pNoise->config.type == ma_noise_type_brownian) { - return ma_noise_read_pcm_frames__brownian(pNoise, pFramesOut, frameCount); - } - - /* Should never get here. */ - MA_ASSERT(MA_FALSE); - return 0; + return MA_SUCCESS; } #endif /* MA_NO_GENERATION */ +#ifndef MA_NO_RESOURCE_MANAGER +#ifndef MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS +#define MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS 1000 +#endif + +#ifndef MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY +#define MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY 1024 +#endif + +MA_API ma_resource_manager_pipeline_notifications ma_resource_manager_pipeline_notifications_init(void) +{ + ma_resource_manager_pipeline_notifications notifications; + + MA_ZERO_OBJECT(¬ifications); + + return notifications; +} + +static void ma_resource_manager_pipeline_notifications_signal_all_notifications(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pNotification) { ma_async_notification_signal(pPipelineNotifications->init.pNotification); } + if (pPipelineNotifications->done.pNotification) { ma_async_notification_signal(pPipelineNotifications->done.pNotification); } +} + +static void ma_resource_manager_pipeline_notifications_acquire_all_fences(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pFence != NULL) { ma_fence_acquire(pPipelineNotifications->init.pFence); } + if (pPipelineNotifications->done.pFence != NULL) { ma_fence_acquire(pPipelineNotifications->done.pFence); } +} + +static void ma_resource_manager_pipeline_notifications_release_all_fences(const ma_resource_manager_pipeline_notifications* pPipelineNotifications) +{ + if (pPipelineNotifications == NULL) { + return; + } + + if (pPipelineNotifications->init.pFence != NULL) { ma_fence_release(pPipelineNotifications->init.pFence); } + if (pPipelineNotifications->done.pFence != NULL) { ma_fence_release(pPipelineNotifications->done.pFence); } +} + + + +#ifndef MA_DEFAULT_HASH_SEED +#define MA_DEFAULT_HASH_SEED 42 +#endif + +/* MurmurHash3. Based on code from https://github.com/PeterScott/murmur3/blob/master/murmur3.c (public domain). */ +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #if __GNUC__ >= 7 + #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" + #endif +#endif + +static MA_INLINE ma_uint32 ma_rotl32(ma_uint32 x, ma_int8 r) +{ + return (x << r) | (x >> (32 - r)); +} + +static MA_INLINE ma_uint32 ma_hash_getblock(const ma_uint32* blocks, int i) +{ + if (ma_is_little_endian()) { + return blocks[i]; + } else { + return ma_swap_endian_uint32(blocks[i]); + } +} + +static MA_INLINE ma_uint32 ma_hash_fmix32(ma_uint32 h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +static ma_uint32 ma_hash_32(const void* key, int len, ma_uint32 seed) +{ + const ma_uint8* data = (const ma_uint8*)key; + const ma_uint32* blocks; + const ma_uint8* tail; + const int nblocks = len / 4; + ma_uint32 h1 = seed; + ma_uint32 c1 = 0xcc9e2d51; + ma_uint32 c2 = 0x1b873593; + ma_uint32 k1; + int i; + + blocks = (const ma_uint32 *)(data + nblocks*4); + + for(i = -nblocks; i; i++) { + k1 = ma_hash_getblock(blocks,i); + + k1 *= c1; + k1 = ma_rotl32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = ma_rotl32(h1, 13); + h1 = h1*5 + 0xe6546b64; + } + + + tail = (const ma_uint8*)(data + nblocks*4); + + k1 = 0; + switch(len & 3) { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 *= c1; k1 = ma_rotl32(k1, 15); k1 *= c2; h1 ^= k1; + }; + + + h1 ^= len; + h1 = ma_hash_fmix32(h1); + + return h1; +} + +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push +#endif +/* End MurmurHash3 */ + +static ma_uint32 ma_hash_string_32(const char* str) +{ + return ma_hash_32(str, (int)strlen(str), MA_DEFAULT_HASH_SEED); +} + +static ma_uint32 ma_hash_string_w_32(const wchar_t* str) +{ + return ma_hash_32(str, (int)wcslen(str) * sizeof(*str), MA_DEFAULT_HASH_SEED); +} + + + + +/* +Basic BST Functions +*/ +static ma_result ma_resource_manager_data_buffer_node_search(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(ppDataBufferNode != NULL); + + pCurrentNode = pResourceManager->pRootDataBufferNode; + while (pCurrentNode != NULL) { + if (hashedName32 == pCurrentNode->hashedName32) { + break; /* Found. */ + } else if (hashedName32 < pCurrentNode->hashedName32) { + pCurrentNode = pCurrentNode->pChildLo; + } else { + pCurrentNode = pCurrentNode->pChildHi; + } + } + + *ppDataBufferNode = pCurrentNode; + + if (pCurrentNode == NULL) { + return MA_DOES_NOT_EXIST; + } else { + return MA_SUCCESS; + } +} + +static ma_result ma_resource_manager_data_buffer_node_insert_point(ma_resource_manager* pResourceManager, ma_uint32 hashedName32, ma_resource_manager_data_buffer_node** ppInsertPoint) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(ppInsertPoint != NULL); + + *ppInsertPoint = NULL; + + if (pResourceManager->pRootDataBufferNode == NULL) { + return MA_SUCCESS; /* No items. */ + } + + /* We need to find the node that will become the parent of the new node. If a node is found that already has the same hashed name we need to return MA_ALREADY_EXISTS. */ + pCurrentNode = pResourceManager->pRootDataBufferNode; + while (pCurrentNode != NULL) { + if (hashedName32 == pCurrentNode->hashedName32) { + result = MA_ALREADY_EXISTS; + break; + } else { + if (hashedName32 < pCurrentNode->hashedName32) { + if (pCurrentNode->pChildLo == NULL) { + result = MA_SUCCESS; + break; + } else { + pCurrentNode = pCurrentNode->pChildLo; + } + } else { + if (pCurrentNode->pChildHi == NULL) { + result = MA_SUCCESS; + break; + } else { + pCurrentNode = pCurrentNode->pChildHi; + } + } + } + } + + *ppInsertPoint = pCurrentNode; + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_insert_at(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_resource_manager_data_buffer_node* pInsertPoint) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + /* The key must have been set before calling this function. */ + MA_ASSERT(pDataBufferNode->hashedName32 != 0); + + if (pInsertPoint == NULL) { + /* It's the first node. */ + pResourceManager->pRootDataBufferNode = pDataBufferNode; + } else { + /* It's not the first node. It needs to be inserted. */ + if (pDataBufferNode->hashedName32 < pInsertPoint->hashedName32) { + MA_ASSERT(pInsertPoint->pChildLo == NULL); + pInsertPoint->pChildLo = pDataBufferNode; + } else { + MA_ASSERT(pInsertPoint->pChildHi == NULL); + pInsertPoint->pChildHi = pDataBufferNode; + } + } + + pDataBufferNode->pParent = pInsertPoint; + + return MA_SUCCESS; +} + +#if 0 /* Unused for now. */ +static ma_result ma_resource_manager_data_buffer_node_insert(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_result result; + ma_resource_manager_data_buffer_node* pInsertPoint; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, pDataBufferNode->hashedName32, &pInsertPoint); + if (result != MA_SUCCESS) { + return MA_INVALID_ARGS; + } + + return ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint); +} +#endif + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_min(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pDataBufferNode != NULL); + + pCurrentNode = pDataBufferNode; + while (pCurrentNode->pChildLo != NULL) { + pCurrentNode = pCurrentNode->pChildLo; + } + + return pCurrentNode; +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_max(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + ma_resource_manager_data_buffer_node* pCurrentNode; + + MA_ASSERT(pDataBufferNode != NULL); + + pCurrentNode = pDataBufferNode; + while (pCurrentNode->pChildHi != NULL) { + pCurrentNode = pCurrentNode->pChildHi; + } + + return pCurrentNode; +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_successor(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->pChildHi != NULL); + + return ma_resource_manager_data_buffer_node_find_min(pDataBufferNode->pChildHi); +} + +static MA_INLINE ma_resource_manager_data_buffer_node* ma_resource_manager_data_buffer_node_find_inorder_predecessor(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->pChildLo != NULL); + + return ma_resource_manager_data_buffer_node_find_max(pDataBufferNode->pChildLo); +} + +static ma_result ma_resource_manager_data_buffer_node_remove(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + if (pDataBufferNode->pChildLo == NULL) { + if (pDataBufferNode->pChildHi == NULL) { + /* Simple case - deleting a buffer with no children. */ + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); /* There is only a single buffer in the tree which should be equal to the root node. */ + pResourceManager->pRootDataBufferNode = NULL; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = NULL; + } else { + pDataBufferNode->pParent->pChildHi = NULL; + } + } + } else { + /* Node has one child - pChildHi != NULL. */ + pDataBufferNode->pChildHi->pParent = pDataBufferNode->pParent; + + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); + pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildHi; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildHi; + } else { + pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildHi; + } + } + } + } else { + if (pDataBufferNode->pChildHi == NULL) { + /* Node has one child - pChildLo != NULL. */ + pDataBufferNode->pChildLo->pParent = pDataBufferNode->pParent; + + if (pDataBufferNode->pParent == NULL) { + MA_ASSERT(pResourceManager->pRootDataBufferNode == pDataBufferNode); + pResourceManager->pRootDataBufferNode = pDataBufferNode->pChildLo; + } else { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pDataBufferNode->pChildLo; + } else { + pDataBufferNode->pParent->pChildHi = pDataBufferNode->pChildLo; + } + } + } else { + /* Complex case - deleting a node with two children. */ + ma_resource_manager_data_buffer_node* pReplacementDataBufferNode; + + /* For now we are just going to use the in-order successor as the replacement, but we may want to try to keep this balanced by switching between the two. */ + pReplacementDataBufferNode = ma_resource_manager_data_buffer_node_find_inorder_successor(pDataBufferNode); + MA_ASSERT(pReplacementDataBufferNode != NULL); + + /* + Now that we have our replacement node we can make the change. The simple way to do this would be to just exchange the values, and then remove the replacement + node, however we track specific nodes via pointers which means we can't just swap out the values. We need to instead just change the pointers around. The + replacement node should have at most 1 child. Therefore, we can detach it in terms of our simpler cases above. What we're essentially doing is detaching the + replacement node and reinserting it into the same position as the deleted node. + */ + MA_ASSERT(pReplacementDataBufferNode->pParent != NULL); /* The replacement node should never be the root which means it should always have a parent. */ + MA_ASSERT(pReplacementDataBufferNode->pChildLo == NULL); /* Because we used in-order successor. This would be pChildHi == NULL if we used in-order predecessor. */ + + if (pReplacementDataBufferNode->pChildHi == NULL) { + if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) { + pReplacementDataBufferNode->pParent->pChildLo = NULL; + } else { + pReplacementDataBufferNode->pParent->pChildHi = NULL; + } + } else { + pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode->pParent; + if (pReplacementDataBufferNode->pParent->pChildLo == pReplacementDataBufferNode) { + pReplacementDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode->pChildHi; + } else { + pReplacementDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode->pChildHi; + } + } + + + /* The replacement node has essentially been detached from the binary tree, so now we need to replace the old data buffer with it. The first thing to update is the parent */ + if (pDataBufferNode->pParent != NULL) { + if (pDataBufferNode->pParent->pChildLo == pDataBufferNode) { + pDataBufferNode->pParent->pChildLo = pReplacementDataBufferNode; + } else { + pDataBufferNode->pParent->pChildHi = pReplacementDataBufferNode; + } + } + + /* Now need to update the replacement node's pointers. */ + pReplacementDataBufferNode->pParent = pDataBufferNode->pParent; + pReplacementDataBufferNode->pChildLo = pDataBufferNode->pChildLo; + pReplacementDataBufferNode->pChildHi = pDataBufferNode->pChildHi; + + /* Now the children of the replacement node need to have their parent pointers updated. */ + if (pReplacementDataBufferNode->pChildLo != NULL) { + pReplacementDataBufferNode->pChildLo->pParent = pReplacementDataBufferNode; + } + if (pReplacementDataBufferNode->pChildHi != NULL) { + pReplacementDataBufferNode->pChildHi->pParent = pReplacementDataBufferNode; + } + + /* Now the root node needs to be updated. */ + if (pResourceManager->pRootDataBufferNode == pDataBufferNode) { + pResourceManager->pRootDataBufferNode = pReplacementDataBufferNode; + } + } + } + + return MA_SUCCESS; +} + +#if 0 /* Unused for now. */ +static ma_result ma_resource_manager_data_buffer_node_remove_by_key(ma_resource_manager* pResourceManager, ma_uint32 hashedName32) +{ + ma_result result; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + result = ma_resource_manager_data_buffer_search(pResourceManager, hashedName32, &pDataBufferNode); + if (result != MA_SUCCESS) { + return result; /* Could not find the data buffer. */ + } + + return ma_resource_manager_data_buffer_remove(pResourceManager, pDataBufferNode); +} +#endif + +static ma_resource_manager_data_supply_type ma_resource_manager_data_buffer_node_get_data_supply_type(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + return (ma_resource_manager_data_supply_type)c89atomic_load_i32(&pDataBufferNode->data.type); +} + +static void ma_resource_manager_data_buffer_node_set_data_supply_type(ma_resource_manager_data_buffer_node* pDataBufferNode, ma_resource_manager_data_supply_type supplyType) +{ + c89atomic_exchange_i32(&pDataBufferNode->data.type, supplyType); +} + +static ma_result ma_resource_manager_data_buffer_node_increment_ref(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_uint32* pNewRefCount) +{ + ma_uint32 refCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + (void)pResourceManager; + + refCount = c89atomic_fetch_add_32(&pDataBufferNode->refCount, 1) + 1; + + if (pNewRefCount != NULL) { + *pNewRefCount = refCount; + } + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_decrement_ref(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_uint32* pNewRefCount) +{ + ma_uint32 refCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + (void)pResourceManager; + + refCount = c89atomic_fetch_sub_32(&pDataBufferNode->refCount, 1) - 1; + + if (pNewRefCount != NULL) { + *pNewRefCount = refCount; + } + + return MA_SUCCESS; +} + +static void ma_resource_manager_data_buffer_node_free(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + + if (pDataBufferNode->isDataOwnedByResourceManager) { + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_encoded) { + ma_free((void*)pDataBufferNode->data.backend.encoded.pData, &pResourceManager->config.allocationCallbacks); + pDataBufferNode->data.backend.encoded.pData = NULL; + pDataBufferNode->data.backend.encoded.sizeInBytes = 0; + } else if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_decoded) { + ma_free((void*)pDataBufferNode->data.backend.decoded.pData, &pResourceManager->config.allocationCallbacks); + pDataBufferNode->data.backend.decoded.pData = NULL; + pDataBufferNode->data.backend.decoded.totalFrameCount = 0; + } else if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode) == ma_resource_manager_data_supply_type_decoded_paged) { + ma_paged_audio_buffer_data_uninit(&pDataBufferNode->data.backend.decodedPaged.data, &pResourceManager->config.allocationCallbacks); + } else { + /* Should never hit this if the node was successfully initialized. */ + MA_ASSERT(pDataBufferNode->result != MA_SUCCESS); + } + } + + /* The data buffer itself needs to be freed. */ + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); +} + +static ma_result ma_resource_manager_data_buffer_node_result(const ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + + return (ma_result)c89atomic_load_i32((ma_result*)&pDataBufferNode->result); /* Need a naughty const-cast here. */ +} + + +static ma_bool32 ma_resource_manager_is_threading_enabled(const ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + return (pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) == 0; +} + + +typedef struct +{ + union + { + ma_async_notification_event e; + ma_async_notification_poll p; + } backend; /* Must be the first member. */ + ma_resource_manager* pResourceManager; +} ma_resource_manager_inline_notification; + +static ma_result ma_resource_manager_inline_notification_init(ma_resource_manager* pResourceManager, ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pNotification != NULL); + + pNotification->pResourceManager = pResourceManager; + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + return ma_async_notification_event_init(&pNotification->backend.e); + } else { + return ma_async_notification_poll_init(&pNotification->backend.p); + } +} + +static void ma_resource_manager_inline_notification_uninit(ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pNotification != NULL); + + if (ma_resource_manager_is_threading_enabled(pNotification->pResourceManager)) { + ma_async_notification_event_uninit(&pNotification->backend.e); + } else { + /* No need to uninitialize a polling notification. */ + } +} + +static void ma_resource_manager_inline_notification_wait(ma_resource_manager_inline_notification* pNotification) +{ + MA_ASSERT(pNotification != NULL); + + if (ma_resource_manager_is_threading_enabled(pNotification->pResourceManager)) { + ma_async_notification_event_wait(&pNotification->backend.e); + } else { + while (ma_async_notification_poll_is_signalled(&pNotification->backend.p) == MA_FALSE) { + ma_result result = ma_resource_manager_process_next_job(pNotification->pResourceManager); + if (result == MA_NO_DATA_AVAILABLE || result == MA_CANCELLED) { + break; + } + } + } +} + +static void ma_resource_manager_inline_notification_wait_and_uninit(ma_resource_manager_inline_notification* pNotification) +{ + ma_resource_manager_inline_notification_wait(pNotification); + ma_resource_manager_inline_notification_uninit(pNotification); +} + + +static void ma_resource_manager_data_buffer_bst_lock(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_lock(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } else { + /* Threading not enabled. Do nothing. */ + } +} + +static void ma_resource_manager_data_buffer_bst_unlock(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager != NULL); + + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_unlock(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } else { + /* Threading not enabled. Do nothing. */ + } +} + +#ifndef MA_NO_THREADING +static ma_thread_result MA_THREADCALL ma_resource_manager_job_thread(void* pUserData) +{ + ma_resource_manager* pResourceManager = (ma_resource_manager*)pUserData; + MA_ASSERT(pResourceManager != NULL); + + for (;;) { + ma_result result; + ma_job job; + + result = ma_resource_manager_next_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + break; + } + + /* Terminate if we got a quit message. */ + if (job.toc.breakup.code == MA_JOB_TYPE_QUIT) { + break; + } + + ma_job_process(&job); + } + + return (ma_thread_result)0; +} +#endif + +MA_API ma_resource_manager_config ma_resource_manager_config_init(void) +{ + ma_resource_manager_config config; + + MA_ZERO_OBJECT(&config); + config.decodedFormat = ma_format_unknown; + config.decodedChannels = 0; + config.decodedSampleRate = 0; + config.jobThreadCount = 1; /* A single miniaudio-managed job thread by default. */ + config.jobQueueCapacity = MA_JOB_TYPE_RESOURCE_MANAGER_QUEUE_CAPACITY; + + /* Flags. */ + config.flags = 0; + #ifdef MA_NO_THREADING + { + /* Threading is disabled at compile time so disable threading at runtime as well by default. */ + config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + config.jobThreadCount = 0; + } + #endif + + return config; +} + + +MA_API ma_result ma_resource_manager_init(const ma_resource_manager_config* pConfig, ma_resource_manager* pResourceManager) +{ + ma_result result; + ma_job_queue_config jobQueueConfig; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pResourceManager); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + #ifndef MA_NO_THREADING + { + if (pConfig->jobThreadCount > ma_countof(pResourceManager->jobThreads)) { + return MA_INVALID_ARGS; /* Requesting too many job threads. */ + } + } + #endif + + pResourceManager->config = *pConfig; + ma_allocation_callbacks_init_copy(&pResourceManager->config.allocationCallbacks, &pConfig->allocationCallbacks); + + /* Get the log set up early so we can start using it as soon as possible. */ + if (pResourceManager->config.pLog == NULL) { + result = ma_log_init(&pResourceManager->config.allocationCallbacks, &pResourceManager->log); + if (result == MA_SUCCESS) { + pResourceManager->config.pLog = &pResourceManager->log; + } else { + pResourceManager->config.pLog = NULL; /* Logging is unavailable. */ + } + } + + if (pResourceManager->config.pVFS == NULL) { + result = ma_default_vfs_init(&pResourceManager->defaultVFS, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the default file system. */ + } + + pResourceManager->config.pVFS = &pResourceManager->defaultVFS; + } + + /* If threading has been disabled at compile time, enfore it at run time as well. */ + #ifdef MA_NO_THREADING + { + pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + } + #endif + + /* We need to force MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING if MA_RESOURCE_MANAGER_FLAG_NO_THREADING is set. */ + if ((pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) != 0) { + pResourceManager->config.flags |= MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING; + + /* We cannot allow job threads when MA_RESOURCE_MANAGER_FLAG_NO_THREADING has been set. This is an invalid use case. */ + if (pResourceManager->config.jobThreadCount > 0) { + return MA_INVALID_ARGS; + } + } + + /* Job queue. */ + jobQueueConfig.capacity = pResourceManager->config.jobQueueCapacity; + jobQueueConfig.flags = 0; + if ((pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING) != 0) { + if (pResourceManager->config.jobThreadCount > 0) { + return MA_INVALID_ARGS; /* Non-blocking mode is only valid for self-managed job threads. */ + } + + jobQueueConfig.flags |= MA_JOB_QUEUE_FLAG_NON_BLOCKING; + } + + result = ma_job_queue_init(&jobQueueConfig, &pResourceManager->config.allocationCallbacks, &pResourceManager->jobQueue); + if (result != MA_SUCCESS) { + return result; + } + + + /* Custom decoding backends. */ + if (pConfig->ppCustomDecodingBackendVTables != NULL && pConfig->customDecodingBackendCount > 0) { + size_t sizeInBytes = sizeof(*pResourceManager->config.ppCustomDecodingBackendVTables) * pConfig->customDecodingBackendCount; + + pResourceManager->config.ppCustomDecodingBackendVTables = (ma_decoding_backend_vtable**)ma_malloc(sizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pResourceManager->config.ppCustomDecodingBackendVTables == NULL) { + ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + MA_COPY_MEMORY(pResourceManager->config.ppCustomDecodingBackendVTables, pConfig->ppCustomDecodingBackendVTables, sizeInBytes); + + pResourceManager->config.customDecodingBackendCount = pConfig->customDecodingBackendCount; + pResourceManager->config.pCustomDecodingBackendUserData = pConfig->pCustomDecodingBackendUserData; + } + + + + /* Here is where we initialize our threading stuff. We don't do this if we don't support threading. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_uint32 iJobThread; + + /* Data buffer lock. */ + result = ma_mutex_init(&pResourceManager->dataBufferBSTLock); + if (result != MA_SUCCESS) { + ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* Create the job threads last to ensure the threads has access to valid data. */ + for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) { + result = ma_thread_create(&pResourceManager->jobThreads[iJobThread], ma_thread_priority_normal, 0, ma_resource_manager_job_thread, pResourceManager, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + ma_mutex_uninit(&pResourceManager->dataBufferBSTLock); + ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + return result; + } + } + } + #else + { + /* Threading is disabled at compile time. We should never get here because validation checks should have already been performed. */ + MA_ASSERT(MA_FALSE); + } + #endif + } + + return MA_SUCCESS; +} + + +static void ma_resource_manager_delete_all_data_buffer_nodes(ma_resource_manager* pResourceManager) +{ + MA_ASSERT(pResourceManager); + + /* If everything was done properly, there shouldn't be any active data buffers. */ + while (pResourceManager->pRootDataBufferNode != NULL) { + ma_resource_manager_data_buffer_node* pDataBufferNode = pResourceManager->pRootDataBufferNode; + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + + /* The data buffer has been removed from the BST, so now we need to free it's data. */ + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + } +} + +MA_API void ma_resource_manager_uninit(ma_resource_manager* pResourceManager) +{ + if (pResourceManager == NULL) { + return; + } + + /* + Job threads need to be killed first. To do this we need to post a quit message to the message queue and then wait for the thread. The quit message will never be removed from the + queue which means it will never not be returned after being encounted for the first time which means all threads will eventually receive it. + */ + ma_resource_manager_post_job_quit(pResourceManager); + + /* Wait for every job to finish before continuing to ensure nothing is sill trying to access any of our objects below. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_uint32 iJobThread; + + for (iJobThread = 0; iJobThread < pResourceManager->config.jobThreadCount; iJobThread += 1) { + ma_thread_wait(&pResourceManager->jobThreads[iJobThread]); + } + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } + + /* At this point the thread should have returned and no other thread should be accessing our data. We can now delete all data buffers. */ + ma_resource_manager_delete_all_data_buffer_nodes(pResourceManager); + + /* The job queue is no longer needed. */ + ma_job_queue_uninit(&pResourceManager->jobQueue, &pResourceManager->config.allocationCallbacks); + + /* We're no longer doing anything with data buffers so the lock can now be uninitialized. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager)) { + #ifndef MA_NO_THREADING + { + ma_mutex_uninit(&pResourceManager->dataBufferBSTLock); + } + #else + { + MA_ASSERT(MA_FALSE); /* Should never hit this. */ + } + #endif + } + + ma_free(pResourceManager->config.ppCustomDecodingBackendVTables, &pResourceManager->config.allocationCallbacks); + + if (pResourceManager->config.pLog == &pResourceManager->log) { + ma_log_uninit(&pResourceManager->log); + } +} + +MA_API ma_log* ma_resource_manager_get_log(ma_resource_manager* pResourceManager) +{ + if (pResourceManager == NULL) { + return NULL; + } + + return pResourceManager->config.pLog; +} + + + +MA_API ma_resource_manager_data_source_config ma_resource_manager_data_source_config_init(void) +{ + ma_resource_manager_data_source_config config; + + MA_ZERO_OBJECT(&config); + config.rangeEndInPCMFrames = ~((ma_uint64)0); + config.loopPointEndInPCMFrames = ~((ma_uint64)0); + + return config; +} + + +static ma_decoder_config ma_resource_manager__init_decoder_config(ma_resource_manager* pResourceManager) +{ + ma_decoder_config config; + + config = ma_decoder_config_init(pResourceManager->config.decodedFormat, pResourceManager->config.decodedChannels, pResourceManager->config.decodedSampleRate); + config.allocationCallbacks = pResourceManager->config.allocationCallbacks; + config.ppCustomBackendVTables = pResourceManager->config.ppCustomDecodingBackendVTables; + config.customBackendCount = pResourceManager->config.customDecodingBackendCount; + config.pCustomBackendUserData = pResourceManager->config.pCustomDecodingBackendUserData; + + return config; +} + +static ma_result ma_resource_manager__init_decoder(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_decoder* pDecoder) +{ + ma_result result; + ma_decoder_config config; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + MA_ASSERT(pDecoder != NULL); + + config = ma_resource_manager__init_decoder_config(pResourceManager); + + if (pFilePath != NULL) { + result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pFilePath, &config, pDecoder); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result)); + return result; + } + } else { + result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pFilePathW, &config, pDecoder); + if (result != MA_SUCCESS) { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result)); + #endif + return result; + } + } + + return MA_SUCCESS; +} + +static ma_data_source* ma_resource_manager_data_buffer_get_connector(ma_resource_manager_data_buffer* pDataBuffer) +{ + switch (pDataBuffer->pNode->data.type) + { + case ma_resource_manager_data_supply_type_encoded: return &pDataBuffer->connector.decoder; + case ma_resource_manager_data_supply_type_decoded: return &pDataBuffer->connector.buffer; + case ma_resource_manager_data_supply_type_decoded_paged: return &pDataBuffer->connector.pagedBuffer; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + ma_log_postf(ma_resource_manager_get_log(pDataBuffer->pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to retrieve data buffer connector. Unknown data supply type.\n"); + return NULL; + }; + }; +} + +static ma_result ma_resource_manager_data_buffer_init_connector(ma_resource_manager_data_buffer* pDataBuffer, const ma_resource_manager_data_source_config* pConfig, ma_async_notification* pInitNotification, ma_fence* pInitFence) +{ + ma_result result; + + MA_ASSERT(pDataBuffer != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDataBuffer->isConnectorInitialized == MA_FALSE); + + /* The underlying data buffer must be initialized before we'll be able to know how to initialize the backend. */ + result = ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode); + if (result != MA_SUCCESS && result != MA_BUSY) { + return result; /* The data buffer is in an erroneous state. */ + } + + /* + We need to initialize either a ma_decoder or an ma_audio_buffer depending on whether or not the backing data is encoded or decoded. These act as the + "instance" to the data and are used to form the connection between underlying data buffer and the data source. If the data buffer is decoded, we can use + an ma_audio_buffer. This enables us to use memory mapping when mixing which saves us a bit of data movement overhead. + */ + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: /* Connector is a decoder. */ + { + ma_decoder_config config; + config = ma_resource_manager__init_decoder_config(pDataBuffer->pResourceManager); + result = ma_decoder_init_memory(pDataBuffer->pNode->data.backend.encoded.pData, pDataBuffer->pNode->data.backend.encoded.sizeInBytes, &config, &pDataBuffer->connector.decoder); + } break; + + case ma_resource_manager_data_supply_type_decoded: /* Connector is an audio buffer. */ + { + ma_audio_buffer_config config; + config = ma_audio_buffer_config_init(pDataBuffer->pNode->data.backend.decoded.format, pDataBuffer->pNode->data.backend.decoded.channels, pDataBuffer->pNode->data.backend.decoded.totalFrameCount, pDataBuffer->pNode->data.backend.decoded.pData, NULL); + result = ma_audio_buffer_init(&config, &pDataBuffer->connector.buffer); + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: /* Connector is a paged audio buffer. */ + { + ma_paged_audio_buffer_config config; + config = ma_paged_audio_buffer_config_init(&pDataBuffer->pNode->data.backend.decodedPaged.data); + result = ma_paged_audio_buffer_init(&config, &pDataBuffer->connector.pagedBuffer); + } break; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown data supply type. Should never happen. Need to post an error here. */ + return MA_INVALID_ARGS; + }; + } + + /* + Initialization of the connector is when we can fire the init notification. This will give the application access to + the format/channels/rate of the data source. + */ + if (result == MA_SUCCESS) { + /* + Make sure the looping state is set before returning in order to handle the case where the + loop state was set on the data buffer before the connector was initialized. + */ + ma_data_source_set_range_in_pcm_frames(pDataBuffer, pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); + ma_data_source_set_loop_point_in_pcm_frames(pDataBuffer, pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + ma_data_source_set_looping(pDataBuffer, pConfig->isLooping); + + pDataBuffer->isConnectorInitialized = MA_TRUE; + + if (pInitNotification != NULL) { + ma_async_notification_signal(pInitNotification); + } + + if (pInitFence != NULL) { + ma_fence_release(pInitFence); + } + } + + /* At this point the backend should be initialized. We do *not* want to set pDataSource->result here - that needs to be done at a higher level to ensure it's done as the last step. */ + return result; +} + +static ma_result ma_resource_manager_data_buffer_uninit_connector(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBuffer != NULL); + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: /* Connector is a decoder. */ + { + ma_decoder_uninit(&pDataBuffer->connector.decoder); + } break; + + case ma_resource_manager_data_supply_type_decoded: /* Connector is an audio buffer. */ + { + ma_audio_buffer_uninit(&pDataBuffer->connector.buffer); + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: /* Connector is a paged audio buffer. */ + { + ma_paged_audio_buffer_uninit(&pDataBuffer->connector.pagedBuffer); + } break; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown data supply type. Should never happen. Need to post an error here. */ + return MA_INVALID_ARGS; + }; + } + + return MA_SUCCESS; +} + +static ma_uint32 ma_resource_manager_data_buffer_node_next_execution_order(ma_resource_manager_data_buffer_node* pDataBufferNode) +{ + MA_ASSERT(pDataBufferNode != NULL); + return c89atomic_fetch_add_32(&pDataBufferNode->executionCounter, 1); +} + +static ma_result ma_resource_manager_data_buffer_node_init_supply_encoded(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pFilePath, const wchar_t* pFilePathW) +{ + ma_result result; + size_t dataSizeInBytes; + void* pData; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + + result = ma_vfs_open_and_read_file_ex(pResourceManager->config.pVFS, pFilePath, pFilePathW, &pData, &dataSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (result != MA_SUCCESS) { + if (pFilePath != NULL) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%s\". %s.\n", pFilePath, ma_result_description(result)); + } else { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to load file \"%ls\". %s.\n", pFilePathW, ma_result_description(result)); + #endif + } + + return result; + } + + pDataBufferNode->data.backend.encoded.pData = pData; + pDataBufferNode->data.backend.encoded.sizeInBytes = dataSizeInBytes; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_encoded); /* <-- Must be set last. */ + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_init_supply_decoded(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 flags, ma_decoder** ppDecoder) +{ + ma_result result = MA_SUCCESS; + ma_decoder* pDecoder; + ma_uint64 totalFrameCount; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(ppDecoder != NULL); + MA_ASSERT(pFilePath != NULL || pFilePathW != NULL); + + *ppDecoder = NULL; /* For safety. */ + + pDecoder = (ma_decoder*)ma_malloc(sizeof(*pDecoder), &pResourceManager->config.allocationCallbacks); + if (pDecoder == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_resource_manager__init_decoder(pResourceManager, pFilePath, pFilePathW, pDecoder); + if (result != MA_SUCCESS) { + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* + At this point we have the decoder and we now need to initialize the data supply. This will + be either a decoded buffer, or a decoded paged buffer. A regular buffer is just one big heap + allocated buffer, whereas a paged buffer is a linked list of paged-sized buffers. The latter + is used when the length of a sound is unknown until a full decode has been performed. + */ + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH) == 0) { + result = ma_decoder_get_length_in_pcm_frames(pDecoder, &totalFrameCount); + if (result != MA_SUCCESS) { + return result; + } + } else { + totalFrameCount = 0; + } + + if (totalFrameCount > 0) { + /* It's a known length. The data supply is a regular decoded buffer. */ + ma_uint64 dataSizeInBytes; + void* pData; + + dataSizeInBytes = totalFrameCount * ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels); + if (dataSizeInBytes > MA_SIZE_MAX) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return MA_TOO_BIG; + } + + pData = ma_malloc((size_t)dataSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pData == NULL) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + /* The buffer needs to be initialized to silence in case the caller reads from it. */ + ma_silence_pcm_frames(pData, totalFrameCount, pDecoder->outputFormat, pDecoder->outputChannels); + + /* Data has been allocated and the data supply can now be initialized. */ + pDataBufferNode->data.backend.decoded.pData = pData; + pDataBufferNode->data.backend.decoded.totalFrameCount = totalFrameCount; + pDataBufferNode->data.backend.decoded.format = pDecoder->outputFormat; + pDataBufferNode->data.backend.decoded.channels = pDecoder->outputChannels; + pDataBufferNode->data.backend.decoded.sampleRate = pDecoder->outputSampleRate; + pDataBufferNode->data.backend.decoded.decodedFrameCount = 0; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_decoded); /* <-- Must be set last. */ + } else { + /* + It's an unknown length. The data supply is a paged decoded buffer. Setting this up is + actually easier than the non-paged decoded buffer because we just need to initialize + a ma_paged_audio_buffer object. + */ + result = ma_paged_audio_buffer_data_init(pDecoder->outputFormat, pDecoder->outputChannels, &pDataBufferNode->data.backend.decodedPaged.data); + if (result != MA_SUCCESS) { + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + return result; + } + + pDataBufferNode->data.backend.decodedPaged.sampleRate = pDecoder->outputSampleRate; + pDataBufferNode->data.backend.decodedPaged.decodedFrameCount = 0; + ma_resource_manager_data_buffer_node_set_data_supply_type(pDataBufferNode, ma_resource_manager_data_supply_type_decoded_paged); /* <-- Must be set last. */ + } + + *ppDecoder = pDecoder; + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_buffer_node_decode_next_page(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, ma_decoder* pDecoder) +{ + ma_result result = MA_SUCCESS; + ma_uint64 pageSizeInFrames; + ma_uint64 framesToTryReading; + ma_uint64 framesRead; + + MA_ASSERT(pResourceManager != NULL); + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDecoder != NULL); + + /* We need to know the size of a page in frames to know how many frames to decode. */ + pageSizeInFrames = MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDecoder->outputSampleRate/1000); + framesToTryReading = pageSizeInFrames; + + /* + Here is where we do the decoding of the next page. We'll run a slightly different path depending + on whether or not we're using a flat or paged buffer because the allocation of the page differs + between the two. For a flat buffer it's an offset to an already-allocated buffer. For a paged + buffer, we need to allocate a new page and attach it to the linked list. + */ + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode)) + { + case ma_resource_manager_data_supply_type_decoded: + { + /* The destination buffer is an offset to the existing buffer. Don't read more than we originally retrieved when we first initialized the decoder. */ + void* pDst; + ma_uint64 framesRemaining = pDataBufferNode->data.backend.decoded.totalFrameCount - pDataBufferNode->data.backend.decoded.decodedFrameCount; + if (framesToTryReading > framesRemaining) { + framesToTryReading = framesRemaining; + } + + if (framesToTryReading > 0) { + pDst = ma_offset_ptr( + pDataBufferNode->data.backend.decoded.pData, + pDataBufferNode->data.backend.decoded.decodedFrameCount * ma_get_bytes_per_frame(pDataBufferNode->data.backend.decoded.format, pDataBufferNode->data.backend.decoded.channels) + ); + MA_ASSERT(pDst != NULL); + + result = ma_decoder_read_pcm_frames(pDecoder, pDst, framesToTryReading, &framesRead); + if (framesRead > 0) { + pDataBufferNode->data.backend.decoded.decodedFrameCount += framesRead; + } + } else { + framesRead = 0; + } + } break; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + /* The destination buffer is a freshly allocated page. */ + ma_paged_audio_buffer_page* pPage; + + result = ma_paged_audio_buffer_data_allocate_page(&pDataBufferNode->data.backend.decodedPaged.data, framesToTryReading, NULL, &pResourceManager->config.allocationCallbacks, &pPage); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_decoder_read_pcm_frames(pDecoder, pPage->pAudioData, framesToTryReading, &framesRead); + if (framesRead > 0) { + pPage->sizeInFrames = framesRead; + + result = ma_paged_audio_buffer_data_append_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage); + if (result == MA_SUCCESS) { + pDataBufferNode->data.backend.decodedPaged.decodedFrameCount += framesRead; + } else { + /* Failed to append the page. Just abort and set the status to MA_AT_END. */ + ma_paged_audio_buffer_data_free_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage, &pResourceManager->config.allocationCallbacks); + result = MA_AT_END; + } + } else { + /* No frames were read. Free the page and just set the status to MA_AT_END. */ + ma_paged_audio_buffer_data_free_page(&pDataBufferNode->data.backend.decodedPaged.data, pPage, &pResourceManager->config.allocationCallbacks); + result = MA_AT_END; + } + } break; + + case ma_resource_manager_data_supply_type_encoded: + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unexpected data supply type. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Unexpected data supply type (%d) when decoding page.", ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBufferNode)); + return MA_ERROR; + }; + } + + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_acquire_critical_section(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 hashedName32, ma_uint32 flags, const ma_resource_manager_data_supply* pExistingData, ma_fence* pInitFence, ma_fence* pDoneFence, ma_resource_manager_inline_notification* pInitNotification, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pDataBufferNode = NULL; + ma_resource_manager_data_buffer_node* pInsertPoint; + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = NULL; + } + + result = ma_resource_manager_data_buffer_node_insert_point(pResourceManager, hashedName32, &pInsertPoint); + if (result == MA_ALREADY_EXISTS) { + /* The node already exists. We just need to increment the reference count. */ + pDataBufferNode = pInsertPoint; + + result = ma_resource_manager_data_buffer_node_increment_ref(pResourceManager, pDataBufferNode, NULL); + if (result != MA_SUCCESS) { + return result; /* Should never happen. Failed to increment the reference count. */ + } + + result = MA_ALREADY_EXISTS; + goto done; + } else { + /* + The node does not already exist. We need to post a LOAD_DATA_BUFFER_NODE job here. This + needs to be done inside the critical section to ensure an uninitialization of the node + does not occur before initialization on another thread. + */ + pDataBufferNode = (ma_resource_manager_data_buffer_node*)ma_malloc(sizeof(*pDataBufferNode), &pResourceManager->config.allocationCallbacks); + if (pDataBufferNode == NULL) { + return MA_OUT_OF_MEMORY; + } + + MA_ZERO_OBJECT(pDataBufferNode); + pDataBufferNode->hashedName32 = hashedName32; + pDataBufferNode->refCount = 1; /* Always set to 1 by default (this is our first reference). */ + + if (pExistingData == NULL) { + pDataBufferNode->data.type = ma_resource_manager_data_supply_type_unknown; /* <-- We won't know this until we start decoding. */ + pDataBufferNode->result = MA_BUSY; /* Must be set to MA_BUSY before we leave the critical section, so might as well do it now. */ + pDataBufferNode->isDataOwnedByResourceManager = MA_TRUE; + } else { + pDataBufferNode->data = *pExistingData; + pDataBufferNode->result = MA_SUCCESS; /* Not loading asynchronously, so just set the status */ + pDataBufferNode->isDataOwnedByResourceManager = MA_FALSE; + } + + result = ma_resource_manager_data_buffer_node_insert_at(pResourceManager, pDataBufferNode, pInsertPoint); + if (result != MA_SUCCESS) { + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + return result; /* Should never happen. Failed to insert the data buffer into the BST. */ + } + + /* + Here is where we'll post the job, but only if we're loading asynchronously. If we're + loading synchronously we'll defer loading to a later stage, outside of the critical + section. + */ + if (pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0) { + /* Loading asynchronously. Post the job. */ + ma_job job; + char* pFilePathCopy = NULL; + wchar_t* pFilePathWCopy = NULL; + + /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */ + if (pFilePath != NULL) { + pFilePathCopy = ma_copy_string(pFilePath, &pResourceManager->config.allocationCallbacks); + } else { + pFilePathWCopy = ma_copy_string_w(pFilePathW, &pResourceManager->config.allocationCallbacks); + } + + if (pFilePathCopy == NULL && pFilePathWCopy == NULL) { + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + return MA_OUT_OF_MEMORY; + } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, pInitNotification); + } + + /* Acquire init and done fences before posting the job. These will be unacquired by the job thread. */ + if (pInitFence != NULL) { ma_fence_acquire(pInitFence); } + if (pDoneFence != NULL) { ma_fence_acquire(pDoneFence); } + + /* We now have everything we need to post the job to the job thread. */ + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE); + job.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + job.data.resourceManager.loadDataBufferNode.pResourceManager = pResourceManager; + job.data.resourceManager.loadDataBufferNode.pDataBufferNode = pDataBufferNode; + job.data.resourceManager.loadDataBufferNode.pFilePath = pFilePathCopy; + job.data.resourceManager.loadDataBufferNode.pFilePathW = pFilePathWCopy; + job.data.resourceManager.loadDataBufferNode.flags = flags; + job.data.resourceManager.loadDataBufferNode.pInitNotification = ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) ? pInitNotification : NULL; + job.data.resourceManager.loadDataBufferNode.pDoneNotification = NULL; + job.data.resourceManager.loadDataBufferNode.pInitFence = pInitFence; + job.data.resourceManager.loadDataBufferNode.pDoneFence = pDoneFence; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + /* Failed to post job. Probably ran out of memory. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); + + /* + Fences were acquired before posting the job, but since the job was not able to + be posted, we need to make sure we release them so nothing gets stuck waiting. + */ + if (pInitFence != NULL) { ma_fence_release(pInitFence); } + if (pDoneFence != NULL) { ma_fence_release(pDoneFence); } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, pInitNotification); + } + + ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); + ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); + + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + + return result; + } + } + } + +done: + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = pDataBufferNode; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_acquire(ma_resource_manager* pResourceManager, const char* pFilePath, const wchar_t* pFilePathW, ma_uint32 hashedName32, ma_uint32 flags, const ma_resource_manager_data_supply* pExistingData, ma_fence* pInitFence, ma_fence* pDoneFence, ma_resource_manager_data_buffer_node** ppDataBufferNode) +{ + ma_result result = MA_SUCCESS; + ma_bool32 nodeAlreadyExists = MA_FALSE; + ma_resource_manager_data_buffer_node* pDataBufferNode = NULL; + ma_resource_manager_inline_notification initNotification; /* Used when the WAIT_INIT flag is set. */ + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = NULL; /* Safety. */ + } + + if (pResourceManager == NULL || (pFilePath == NULL && pFilePathW == NULL && hashedName32 == 0)) { + return MA_INVALID_ARGS; + } + + /* If we're specifying existing data, it must be valid. */ + if (pExistingData != NULL && pExistingData->type == ma_resource_manager_data_supply_type_unknown) { + return MA_INVALID_ARGS; + } + + /* If we don't support threading, remove the ASYNC flag to make the rest of this a bit simpler. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; + } + + if (hashedName32 == 0) { + if (pFilePath != NULL) { + hashedName32 = ma_hash_string_32(pFilePath); + } else { + hashedName32 = ma_hash_string_w_32(pFilePathW); + } + } + + /* + Here is where we either increment the node's reference count or allocate a new one and add it + to the BST. When allocating a new node, we need to make sure the LOAD_DATA_BUFFER_NODE job is + posted inside the critical section just in case the caller immediately uninitializes the node + as this will ensure the FREE_DATA_BUFFER_NODE job is given an execution order such that the + node is not uninitialized before initialization. + */ + ma_resource_manager_data_buffer_bst_lock(pResourceManager); + { + result = ma_resource_manager_data_buffer_node_acquire_critical_section(pResourceManager, pFilePath, pFilePathW, hashedName32, flags, pExistingData, pInitFence, pDoneFence, &initNotification, &pDataBufferNode); + } + ma_resource_manager_data_buffer_bst_unlock(pResourceManager); + + if (result == MA_ALREADY_EXISTS) { + nodeAlreadyExists = MA_TRUE; + result = MA_SUCCESS; + } else { + if (result != MA_SUCCESS) { + return result; + } + } + + /* + If we're loading synchronously, we'll need to load everything now. When loading asynchronously, + a job will have been posted inside the BST critical section so that an uninitialization can be + allocated an appropriate execution order thereby preventing it from being uninitialized before + the node is initialized by the decoding thread(s). + */ + if (nodeAlreadyExists == MA_FALSE) { /* Don't need to try loading anything if the node already exists. */ + if (pFilePath == NULL && pFilePathW == NULL) { + /* + If this path is hit, it means a buffer is being copied (i.e. initialized from only the + hashed name), but that node has been freed in the meantime, probably from some other + thread. This is an invalid operation. + */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Cloning data buffer node failed because the source node was released. The source node must remain valid until the cloning has completed.\n"); + result = MA_INVALID_OPERATION; + goto done; + } + + if (pDataBufferNode->isDataOwnedByResourceManager) { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) == 0) { + /* Loading synchronously. Load the sound in it's entirety here. */ + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE) == 0) { + /* No decoding. This is the simple case - just store the file contents in memory. */ + result = ma_resource_manager_data_buffer_node_init_supply_encoded(pResourceManager, pDataBufferNode, pFilePath, pFilePathW); + if (result != MA_SUCCESS) { + goto done; + } + } else { + /* Decoding. We do this the same way as we do when loading asynchronously. */ + ma_decoder* pDecoder; + result = ma_resource_manager_data_buffer_node_init_supply_decoded(pResourceManager, pDataBufferNode, pFilePath, pFilePathW, flags, &pDecoder); + if (result != MA_SUCCESS) { + goto done; + } + + /* We have the decoder, now decode page by page just like we do when loading asynchronously. */ + for (;;) { + /* Decode next page. */ + result = ma_resource_manager_data_buffer_node_decode_next_page(pResourceManager, pDataBufferNode, pDecoder); + if (result != MA_SUCCESS) { + break; /* Will return MA_AT_END when the last page has been decoded. */ + } + } + + /* Reaching the end needs to be considered successful. */ + if (result == MA_AT_END) { + result = MA_SUCCESS; + } + + /* + At this point the data buffer is either fully decoded or some error occurred. Either + way, the decoder is no longer necessary. + */ + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + } + + /* Getting here means we were successful. Make sure the status of the node is updated accordingly. */ + c89atomic_exchange_i32(&pDataBufferNode->result, result); + } else { + /* Loading asynchronously. We may need to wait for initialization. */ + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_wait(&initNotification); + } + } + } else { + /* The data is not managed by the resource manager so there's nothing else to do. */ + MA_ASSERT(pExistingData != NULL); + } + } + +done: + /* If we failed to initialize the data buffer we need to free it. */ + if (result != MA_SUCCESS) { + if (nodeAlreadyExists == MA_FALSE) { + ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + ma_free(pDataBufferNode, &pResourceManager->config.allocationCallbacks); + } + } + + /* + The init notification needs to be uninitialized. This will be used if the node does not already + exist, and we've specified ASYNC | WAIT_INIT. + */ + if (nodeAlreadyExists == MA_FALSE && pDataBufferNode->isDataOwnedByResourceManager && (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0) { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_uninit(&initNotification); + } + } + + if (ppDataBufferNode != NULL) { + *ppDataBufferNode = pDataBufferNode; + } + + return result; +} + +static ma_result ma_resource_manager_data_buffer_node_unacquire(ma_resource_manager* pResourceManager, ma_resource_manager_data_buffer_node* pDataBufferNode, const char* pName, const wchar_t* pNameW) +{ + ma_result result = MA_SUCCESS; + ma_uint32 refCount = 0xFFFFFFFF; /* The new reference count of the node after decrementing. Initialize to non-0 to be safe we don't fall into the freeing path. */ + ma_uint32 hashedName32 = 0; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + if (pDataBufferNode == NULL) { + if (pName == NULL && pNameW == NULL) { + return MA_INVALID_ARGS; + } + + if (pName != NULL) { + hashedName32 = ma_hash_string_32(pName); + } else { + hashedName32 = ma_hash_string_w_32(pNameW); + } + } + + /* + The first thing to do is decrement the reference counter of the node. Then, if the reference + count is zero, we need to free the node. If the node is still in the process of loading, we'll + need to post a job to the job queue to free the node. Otherwise we'll just do it here. + */ + ma_resource_manager_data_buffer_bst_lock(pResourceManager); + { + /* Might need to find the node. Must be done inside the critical section. */ + if (pDataBufferNode == NULL) { + result = ma_resource_manager_data_buffer_node_search(pResourceManager, hashedName32, &pDataBufferNode); + if (result != MA_SUCCESS) { + goto stage2; /* Couldn't find the node. */ + } + } + + result = ma_resource_manager_data_buffer_node_decrement_ref(pResourceManager, pDataBufferNode, &refCount); + if (result != MA_SUCCESS) { + goto stage2; /* Should never happen. */ + } + + if (refCount == 0) { + result = ma_resource_manager_data_buffer_node_remove(pResourceManager, pDataBufferNode); + if (result != MA_SUCCESS) { + goto stage2; /* An error occurred when trying to remove the data buffer. This should never happen. */ + } + } + } + ma_resource_manager_data_buffer_bst_unlock(pResourceManager); + +stage2: + if (result != MA_SUCCESS) { + return result; + } + + /* + Here is where we need to free the node. We don't want to do this inside the critical section + above because we want to keep that as small as possible for multi-threaded efficiency. + */ + if (refCount == 0) { + if (ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_BUSY) { + /* The sound is still loading. We need to delay the freeing of the node to a safe time. */ + ma_job job; + + /* We need to mark the node as unavailable for the sake of the resource manager worker threads. */ + c89atomic_exchange_i32(&pDataBufferNode->result, MA_UNAVAILABLE); + + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE); + job.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + job.data.resourceManager.freeDataBufferNode.pResourceManager = pResourceManager; + job.data.resourceManager.freeDataBufferNode.pDataBufferNode = pDataBufferNode; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER_NODE job. %s.\n", ma_result_description(result)); + return result; + } + + /* If we don't support threading, process the job queue here. */ + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + while (ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_BUSY) { + result = ma_resource_manager_process_next_job(pResourceManager); + if (result == MA_NO_DATA_AVAILABLE || result == MA_CANCELLED) { + result = MA_SUCCESS; + break; + } + } + } else { + /* Threading is enabled. The job queue will deal with the rest of the cleanup from here. */ + } + } else { + /* The sound isn't loading so we can just free the node here. */ + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + } + } + + return result; +} + + + +static ma_uint32 ma_resource_manager_data_buffer_next_execution_order(ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pDataBuffer != NULL); + return c89atomic_fetch_add_32(&pDataBuffer->executionCounter, 1); +} + +static ma_result ma_resource_manager_data_buffer_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_resource_manager_data_buffer_read_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_resource_manager_data_buffer_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_resource_manager_data_buffer_seek_to_pcm_frame((ma_resource_manager_data_buffer*)pDataSource, frameIndex); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + return ma_resource_manager_data_buffer_get_data_format((ma_resource_manager_data_buffer*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pCursor); +} + +static ma_result ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_resource_manager_data_buffer_get_length_in_pcm_frames((ma_resource_manager_data_buffer*)pDataSource, pLength); +} + +static ma_result ma_resource_manager_data_buffer_cb__set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + ma_resource_manager_data_buffer* pDataBuffer = (ma_resource_manager_data_buffer*)pDataSource; + MA_ASSERT(pDataBuffer != NULL); + + c89atomic_exchange_32(&pDataBuffer->isLooping, isLooping); + + /* The looping state needs to be set on the connector as well or else looping won't work when we read audio data. */ + ma_data_source_set_looping(ma_resource_manager_data_buffer_get_connector(pDataBuffer), isLooping); + + return MA_SUCCESS; +} + +static ma_data_source_vtable g_ma_resource_manager_data_buffer_vtable = +{ + ma_resource_manager_data_buffer_cb__read_pcm_frames, + ma_resource_manager_data_buffer_cb__seek_to_pcm_frame, + ma_resource_manager_data_buffer_cb__get_data_format, + ma_resource_manager_data_buffer_cb__get_cursor_in_pcm_frames, + ma_resource_manager_data_buffer_cb__get_length_in_pcm_frames, + ma_resource_manager_data_buffer_cb__set_looping, + 0 +}; + +static ma_result ma_resource_manager_data_buffer_init_ex_internal(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_uint32 hashedName32, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager_data_buffer_node* pDataBufferNode; + ma_data_source_config dataSourceConfig; + ma_bool32 async; + ma_uint32 flags; + ma_resource_manager_pipeline_notifications notifications; + + if (pDataBuffer == NULL) { + if (pConfig != NULL && pConfig->pNotifications != NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(pConfig->pNotifications); + } + + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataBuffer); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pNotifications != NULL) { + notifications = *pConfig->pNotifications; /* From here on out we should be referencing `notifications` instead of `pNotifications`. Set this to NULL to catch errors at testing time. */ + } else { + MA_ZERO_OBJECT(¬ifications); + } + + /* For safety, always remove the ASYNC flag if threading is disabled on the resource manager. */ + flags = pConfig->flags; + if (ma_resource_manager_is_threading_enabled(pResourceManager) == MA_FALSE) { + flags &= ~MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC; + } + + async = (flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) != 0; + + /* + Fences need to be acquired before doing anything. These must be aquired and released outside of + the node to ensure there's no holes where ma_fence_wait() could prematurely return before the + data buffer has completed initialization. + + When loading asynchronously, the node acquisition routine below will acquire the fences on this + thread and then release them on the async thread when the operation is complete. + + These fences are always released at the "done" tag at the end of this function. They'll be + acquired a second if loading asynchronously. This double acquisition system is just done to + simplify code maintanence. + */ + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + { + /* We first need to acquire a node. If ASYNC is not set, this will not return until the entire sound has been loaded. */ + result = ma_resource_manager_data_buffer_node_acquire(pResourceManager, pConfig->pFilePath, pConfig->pFilePathW, hashedName32, flags, NULL, notifications.init.pFence, notifications.done.pFence, &pDataBufferNode); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_resource_manager_data_buffer_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pDataBuffer->ds); + if (result != MA_SUCCESS) { + ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL); + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } + + pDataBuffer->pResourceManager = pResourceManager; + pDataBuffer->pNode = pDataBufferNode; + pDataBuffer->flags = flags; + pDataBuffer->result = MA_BUSY; /* Always default to MA_BUSY for safety. It'll be overwritten when loading completes or an error occurs. */ + + /* If we're loading asynchronously we need to post a job to the job queue to initialize the connector. */ + if (async == MA_FALSE || ma_resource_manager_data_buffer_node_result(pDataBufferNode) == MA_SUCCESS) { + /* Loading synchronously or the data has already been fully loaded. We can just initialize the connector from here without a job. */ + result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, pConfig, NULL, NULL); + c89atomic_exchange_i32(&pDataBuffer->result, result); + + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + goto done; + } else { + /* The node's data supply isn't initialized yet. The caller has requested that we load asynchronously so we need to post a job to do this. */ + ma_job job; + ma_resource_manager_inline_notification initNotification; /* Used when the WAIT_INIT flag is set. */ + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_init(pResourceManager, &initNotification); + } + + /* + The status of the data buffer needs to be set to MA_BUSY before posting the job so that the + worker thread is aware of it's busy state. If the LOAD_DATA_BUFFER job sees a status other + than MA_BUSY, it'll assume an error and fall through to an early exit. + */ + c89atomic_exchange_i32(&pDataBuffer->result, MA_BUSY); + + /* Acquire fences a second time. These will be released by the async thread. */ + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER); + job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer); + job.data.resourceManager.loadDataBuffer.pDataBuffer = pDataBuffer; + job.data.resourceManager.loadDataBuffer.pInitNotification = ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) ? &initNotification : notifications.init.pNotification; + job.data.resourceManager.loadDataBuffer.pDoneNotification = notifications.done.pNotification; + job.data.resourceManager.loadDataBuffer.pInitFence = notifications.init.pFence; + job.data.resourceManager.loadDataBuffer.pDoneFence = notifications.done.pFence; + job.data.resourceManager.loadDataBuffer.rangeBegInPCMFrames = pConfig->rangeBegInPCMFrames; + job.data.resourceManager.loadDataBuffer.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; + job.data.resourceManager.loadDataBuffer.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; + job.data.resourceManager.loadDataBuffer.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; + job.data.resourceManager.loadDataBuffer.isLooping = pConfig->isLooping; + + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + /* We failed to post the job. Most likely there isn't enough room in the queue's buffer. */ + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_BUFFER job. %s.\n", ma_result_description(result)); + c89atomic_exchange_i32(&pDataBuffer->result, result); + + /* Release the fences after the result has been set on the data buffer. */ + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + } else { + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_wait(&initNotification); + + if (notifications.init.pNotification != NULL) { + ma_async_notification_signal(notifications.init.pNotification); + } + + /* NOTE: Do not release the init fence here. It will have been done by the job. */ + + /* Make sure we return an error if initialization failed on the async thread. */ + result = ma_resource_manager_data_buffer_result(pDataBuffer); + if (result == MA_BUSY) { + result = MA_SUCCESS; + } + } + } + + if ((flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + ma_resource_manager_inline_notification_uninit(&initNotification); + } + } + + if (result != MA_SUCCESS) { + ma_resource_manager_data_buffer_node_unacquire(pResourceManager, pDataBufferNode, NULL, NULL); + goto done; + } + } +done: + if (result == MA_SUCCESS) { + if (pConfig->initialSeekPointInPCMFrames > 0) { + ma_resource_manager_data_buffer_seek_to_pcm_frame(pDataBuffer, pConfig->initialSeekPointInPCMFrames); + } + } + + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_buffer* pDataBuffer) +{ + return ma_resource_manager_data_buffer_init_ex_internal(pResourceManager, pConfig, 0, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_buffer_init_ex(pResourceManager, &config, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_buffer_init_ex(pResourceManager, &config, pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_buffer* pExistingDataBuffer, ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_resource_manager_data_source_config config; + + if (pExistingDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + MA_ASSERT(pExistingDataBuffer->pNode != NULL); /* <-- If you've triggered this, you've passed in an invalid existing data buffer. */ + + config = ma_resource_manager_data_source_config_init(); + config.flags = pExistingDataBuffer->flags; + + return ma_resource_manager_data_buffer_init_ex_internal(pResourceManager, &config, pExistingDataBuffer->pNode->hashedName32, pDataBuffer); +} + +static ma_result ma_resource_manager_data_buffer_uninit_internal(ma_resource_manager_data_buffer* pDataBuffer) +{ + MA_ASSERT(pDataBuffer != NULL); + + /* The connector should be uninitialized first. */ + ma_resource_manager_data_buffer_uninit_connector(pDataBuffer->pResourceManager, pDataBuffer); + + /* With the connector uninitialized we can unacquire the node. */ + ma_resource_manager_data_buffer_node_unacquire(pDataBuffer->pResourceManager, pDataBuffer->pNode, NULL, NULL); + + /* The base data source needs to be uninitialized as well. */ + ma_data_source_uninit(&pDataBuffer->ds); + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data_buffer* pDataBuffer) +{ + ma_result result; + + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_result(pDataBuffer) == MA_SUCCESS) { + /* The data buffer can be deleted synchronously. */ + return ma_resource_manager_data_buffer_uninit_internal(pDataBuffer); + } else { + /* + The data buffer needs to be deleted asynchronously because it's still loading. With the status set to MA_UNAVAILABLE, no more pages will + be loaded and the uninitialization should happen fairly quickly. Since the caller owns the data buffer, we need to wait for this event + to get processed before returning. + */ + ma_resource_manager_inline_notification notification; + ma_job job; + + /* + We need to mark the node as unavailable so we don't try reading from it anymore, but also to + let the loading thread know that it needs to abort it's loading procedure. + */ + c89atomic_exchange_i32(&pDataBuffer->result, MA_UNAVAILABLE); + + result = ma_resource_manager_inline_notification_init(pDataBuffer->pResourceManager, ¬ification); + if (result != MA_SUCCESS) { + return result; /* Failed to create the notification. This should rarely, if ever, happen. */ + } + + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER); + job.order = ma_resource_manager_data_buffer_next_execution_order(pDataBuffer); + job.data.resourceManager.freeDataBuffer.pDataBuffer = pDataBuffer; + job.data.resourceManager.freeDataBuffer.pDoneNotification = ¬ification; + job.data.resourceManager.freeDataBuffer.pDoneFence = NULL; + + result = ma_resource_manager_post_job(pDataBuffer->pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_resource_manager_inline_notification_uninit(¬ification); + return result; + } + + ma_resource_manager_inline_notification_wait_and_uninit(¬ification); + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_read_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 framesRead = 0; + ma_bool32 isDecodedBufferBusy = MA_FALSE; + + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + /* + We cannot be using the data buffer after it's been uninitialized. If you trigger this assert it means you're trying to read from the data buffer after + it's been uninitialized or is in the process of uninitializing. + */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + /* If the node is not initialized we need to abort with a busy code. */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + return MA_BUSY; /* Still loading. */ + } + + if (pDataBuffer->seekToCursorOnNextRead) { + pDataBuffer->seekToCursorOnNextRead = MA_FALSE; + + result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pDataBuffer->seekTargetInPCMFrames); + if (result != MA_SUCCESS) { + return result; + } + } + + /* + For decoded buffers (not paged) we need to check beforehand how many frames we have available. We cannot + exceed this amount. We'll read as much as we can, and then return MA_BUSY. + */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_decoded) { + ma_uint64 availableFrames; + + isDecodedBufferBusy = (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY); + + if (ma_resource_manager_data_buffer_get_available_frames(pDataBuffer, &availableFrames) == MA_SUCCESS) { + /* Don't try reading more than the available frame count. */ + if (frameCount > availableFrames) { + frameCount = availableFrames; + + /* + If there's no frames available we want to set the status to MA_AT_END. The logic below + will check if the node is busy, and if so, change it to MA_BUSY. The reason we do this + is because we don't want to call `ma_data_source_read_pcm_frames()` if the frame count + is 0 because that'll result in a situation where it's possible MA_AT_END won't get + returned. + */ + if (frameCount == 0) { + result = MA_AT_END; + } + } else { + isDecodedBufferBusy = MA_FALSE; /* We have enough frames available in the buffer to avoid a MA_BUSY status. */ + } + } + } + + /* Don't attempt to read anything if we've got no frames available. */ + if (frameCount > 0) { + result = ma_data_source_read_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pFramesOut, frameCount, &framesRead); + } + + /* + If we returned MA_AT_END, but the node is still loading, we don't want to return that code or else the caller will interpret the sound + as at the end and terminate decoding. + */ + if (result == MA_AT_END) { + if (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY) { + result = MA_BUSY; + } + } + + if (isDecodedBufferBusy) { + result = MA_BUSY; + } + + if (pFramesRead != NULL) { + *pFramesRead = framesRead; + } + + if (result == MA_SUCCESS && framesRead == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_buffer_seek_to_pcm_frame(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64 frameIndex) +{ + ma_result result; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + /* If we haven't yet got a connector we need to abort. */ + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + pDataBuffer->seekTargetInPCMFrames = frameIndex; + pDataBuffer->seekToCursorOnNextRead = MA_TRUE; + return MA_BUSY; /* Still loading. */ + } + + result = ma_data_source_seek_to_pcm_frame(ma_resource_manager_data_buffer_get_connector(pDataBuffer), frameIndex); + if (result != MA_SUCCESS) { + return result; + } + + pDataBuffer->seekTargetInPCMFrames = ~(ma_uint64)0; /* <-- For identification purposes. */ + pDataBuffer->seekToCursorOnNextRead = MA_FALSE; + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_buffer_get_data_format(ma_resource_manager_data_buffer* pDataBuffer, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_data_source_get_data_format(&pDataBuffer->connector.decoder, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + *pFormat = pDataBuffer->pNode->data.backend.decoded.format; + *pChannels = pDataBuffer->pNode->data.backend.decoded.channels; + *pSampleRate = pDataBuffer->pNode->data.backend.decoded.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pDataBuffer->pNode->data.backend.decoded.channels); + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + *pFormat = pDataBuffer->pNode->data.backend.decodedPaged.data.format; + *pChannels = pDataBuffer->pNode->data.backend.decodedPaged.data.channels; + *pSampleRate = pDataBuffer->pNode->data.backend.decodedPaged.sampleRate; + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, pDataBuffer->pNode->data.backend.decoded.channels); + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_unknown: + { + return MA_BUSY; /* Still loading. */ + }; + + default: + { + /* Unknown supply type. Should never hit this. */ + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pCursor) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + if (pDataBuffer == NULL || pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_decoder_get_cursor_in_pcm_frames(&pDataBuffer->connector.decoder, pCursor); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + return ma_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.buffer, pCursor); + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + return ma_paged_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.pagedBuffer, pCursor); + }; + + case ma_resource_manager_data_supply_type_unknown: + { + return MA_BUSY; + }; + + default: + { + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_data_buffer_get_length_in_pcm_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pLength) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) != MA_UNAVAILABLE); + + if (pDataBuffer == NULL || pLength == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + return MA_BUSY; /* Still loading. */ + } + + return ma_data_source_get_length_in_pcm_frames(ma_resource_manager_data_buffer_get_connector(pDataBuffer), pLength); +} + +MA_API ma_result ma_resource_manager_data_buffer_result(const ma_resource_manager_data_buffer* pDataBuffer) +{ + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + return (ma_result)c89atomic_load_i32((ma_result*)&pDataBuffer->result); /* Need a naughty const-cast here. */ +} + +MA_API ma_result ma_resource_manager_data_buffer_set_looping(ma_resource_manager_data_buffer* pDataBuffer, ma_bool32 isLooping) +{ + return ma_data_source_set_looping(pDataBuffer, isLooping); +} + +MA_API ma_bool32 ma_resource_manager_data_buffer_is_looping(const ma_resource_manager_data_buffer* pDataBuffer) +{ + return ma_data_source_is_looping(pDataBuffer); +} + +MA_API ma_result ma_resource_manager_data_buffer_get_available_frames(ma_resource_manager_data_buffer* pDataBuffer, ma_uint64* pAvailableFrames) +{ + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataBuffer == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode) == ma_resource_manager_data_supply_type_unknown) { + if (ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode) == MA_BUSY) { + return MA_BUSY; + } else { + return MA_INVALID_OPERATION; /* No connector. */ + } + } + + switch (ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode)) + { + case ma_resource_manager_data_supply_type_encoded: + { + return ma_decoder_get_available_frames(&pDataBuffer->connector.decoder, pAvailableFrames); + }; + + case ma_resource_manager_data_supply_type_decoded: + { + return ma_audio_buffer_get_available_frames(&pDataBuffer->connector.buffer, pAvailableFrames); + }; + + case ma_resource_manager_data_supply_type_decoded_paged: + { + ma_uint64 cursor; + ma_paged_audio_buffer_get_cursor_in_pcm_frames(&pDataBuffer->connector.pagedBuffer, &cursor); + + if (pDataBuffer->pNode->data.backend.decodedPaged.decodedFrameCount > cursor) { + *pAvailableFrames = pDataBuffer->pNode->data.backend.decodedPaged.decodedFrameCount - cursor; + } else { + *pAvailableFrames = 0; + } + + return MA_SUCCESS; + }; + + case ma_resource_manager_data_supply_type_unknown: + default: + { + /* Unknown supply type. Should never hit this. */ + return MA_INVALID_ARGS; + } + } +} + +MA_API ma_result ma_resource_manager_register_file(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, pFilePath, NULL, 0, flags, NULL, NULL, NULL, NULL); +} + +MA_API ma_result ma_resource_manager_register_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, NULL, pFilePath, 0, flags, NULL, NULL, NULL, NULL); +} + + +static ma_result ma_resource_manager_register_data(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, ma_resource_manager_data_supply* pExistingData) +{ + return ma_resource_manager_data_buffer_node_acquire(pResourceManager, pName, pNameW, 0, 0, pExistingData, NULL, NULL, NULL); +} + +static ma_result ma_resource_manager_register_decoded_data_internal(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + ma_resource_manager_data_supply data; + data.type = ma_resource_manager_data_supply_type_decoded; + data.backend.decoded.pData = pData; + data.backend.decoded.totalFrameCount = frameCount; + data.backend.decoded.format = format; + data.backend.decoded.channels = channels; + data.backend.decoded.sampleRate = sampleRate; + + return ma_resource_manager_register_data(pResourceManager, pName, pNameW, &data); +} + +MA_API ma_result ma_resource_manager_register_decoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + return ma_resource_manager_register_decoded_data_internal(pResourceManager, pName, NULL, pData, frameCount, format, channels, sampleRate); +} + +MA_API ma_result ma_resource_manager_register_decoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, ma_uint64 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + return ma_resource_manager_register_decoded_data_internal(pResourceManager, NULL, pName, pData, frameCount, format, channels, sampleRate); +} + + +static ma_result ma_resource_manager_register_encoded_data_internal(ma_resource_manager* pResourceManager, const char* pName, const wchar_t* pNameW, const void* pData, size_t sizeInBytes) +{ + ma_resource_manager_data_supply data; + data.type = ma_resource_manager_data_supply_type_encoded; + data.backend.encoded.pData = pData; + data.backend.encoded.sizeInBytes = sizeInBytes; + + return ma_resource_manager_register_data(pResourceManager, pName, pNameW, &data); +} + +MA_API ma_result ma_resource_manager_register_encoded_data(ma_resource_manager* pResourceManager, const char* pName, const void* pData, size_t sizeInBytes) +{ + return ma_resource_manager_register_encoded_data_internal(pResourceManager, pName, NULL, pData, sizeInBytes); +} + +MA_API ma_result ma_resource_manager_register_encoded_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName, const void* pData, size_t sizeInBytes) +{ + return ma_resource_manager_register_encoded_data_internal(pResourceManager, NULL, pName, pData, sizeInBytes); +} + + +MA_API ma_result ma_resource_manager_unregister_file(ma_resource_manager* pResourceManager, const char* pFilePath) +{ + return ma_resource_manager_unregister_data(pResourceManager, pFilePath); +} + +MA_API ma_result ma_resource_manager_unregister_file_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath) +{ + return ma_resource_manager_unregister_data_w(pResourceManager, pFilePath); +} + +MA_API ma_result ma_resource_manager_unregister_data(ma_resource_manager* pResourceManager, const char* pName) +{ + return ma_resource_manager_data_buffer_node_unacquire(pResourceManager, NULL, pName, NULL); +} + +MA_API ma_result ma_resource_manager_unregister_data_w(ma_resource_manager* pResourceManager, const wchar_t* pName) +{ + return ma_resource_manager_data_buffer_node_unacquire(pResourceManager, NULL, NULL, pName); +} + + +static ma_uint32 ma_resource_manager_data_stream_next_execution_order(ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_fetch_add_32(&pDataStream->executionCounter, 1); +} + +static ma_bool32 ma_resource_manager_data_stream_is_decoder_at_end(const ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_load_32((ma_bool32*)&pDataStream->isDecoderAtEnd); +} + +static ma_uint32 ma_resource_manager_data_stream_seek_counter(const ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + return c89atomic_load_32((ma_uint32*)&pDataStream->seekCounter); +} + + +static ma_result ma_resource_manager_data_stream_cb__read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_resource_manager_data_stream_read_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pFramesOut, frameCount, pFramesRead); +} + +static ma_result ma_resource_manager_data_stream_cb__seek_to_pcm_frame(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_resource_manager_data_stream_seek_to_pcm_frame((ma_resource_manager_data_stream*)pDataSource, frameIndex); +} + +static ma_result ma_resource_manager_data_stream_cb__get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + return ma_resource_manager_data_stream_get_data_format((ma_resource_manager_data_stream*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +static ma_result ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + return ma_resource_manager_data_stream_get_cursor_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pCursor); +} + +static ma_result ma_resource_manager_data_stream_cb__get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength) +{ + return ma_resource_manager_data_stream_get_length_in_pcm_frames((ma_resource_manager_data_stream*)pDataSource, pLength); +} + +static ma_result ma_resource_manager_data_stream_cb__set_looping(ma_data_source* pDataSource, ma_bool32 isLooping) +{ + ma_resource_manager_data_stream* pDataStream = (ma_resource_manager_data_stream*)pDataSource; + MA_ASSERT(pDataStream != NULL); + + c89atomic_exchange_32(&pDataStream->isLooping, isLooping); + + return MA_SUCCESS; +} + +static ma_data_source_vtable g_ma_resource_manager_data_stream_vtable = +{ + ma_resource_manager_data_stream_cb__read_pcm_frames, + ma_resource_manager_data_stream_cb__seek_to_pcm_frame, + ma_resource_manager_data_stream_cb__get_data_format, + ma_resource_manager_data_stream_cb__get_cursor_in_pcm_frames, + ma_resource_manager_data_stream_cb__get_length_in_pcm_frames, + ma_resource_manager_data_stream_cb__set_looping, + MA_DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT +}; + +static void ma_resource_manager_data_stream_set_absolute_cursor(ma_resource_manager_data_stream* pDataStream, ma_uint64 absoluteCursor) +{ + /* Loop if possible. */ + if (absoluteCursor > pDataStream->totalLengthInPCMFrames && pDataStream->totalLengthInPCMFrames > 0) { + absoluteCursor = absoluteCursor % pDataStream->totalLengthInPCMFrames; + } + + c89atomic_exchange_64(&pDataStream->absoluteCursor, absoluteCursor); +} + +MA_API ma_result ma_resource_manager_data_stream_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_stream* pDataStream) +{ + ma_result result; + ma_data_source_config dataSourceConfig; + char* pFilePathCopy = NULL; + wchar_t* pFilePathWCopy = NULL; + ma_job job; + ma_bool32 waitBeforeReturning = MA_FALSE; + ma_resource_manager_inline_notification waitNotification; + ma_resource_manager_pipeline_notifications notifications; + + if (pDataStream == NULL) { + if (pConfig != NULL && pConfig->pNotifications != NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(pConfig->pNotifications); + } + + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataStream); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pNotifications != NULL) { + notifications = *pConfig->pNotifications; /* From here on out, `notifications` should be used instead of `pNotifications`. Setting this to NULL to catch any errors at testing time. */ + } else { + MA_ZERO_OBJECT(¬ifications); + } + + dataSourceConfig = ma_data_source_config_init(); + dataSourceConfig.vtable = &g_ma_resource_manager_data_stream_vtable; + + result = ma_data_source_init(&dataSourceConfig, &pDataStream->ds); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return result; + } + + pDataStream->pResourceManager = pResourceManager; + pDataStream->flags = pConfig->flags; + pDataStream->result = MA_BUSY; + + ma_data_source_set_range_in_pcm_frames(pDataStream, pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); + ma_data_source_set_loop_point_in_pcm_frames(pDataStream, pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + ma_data_source_set_looping(pDataStream, pConfig->isLooping); + + if (pResourceManager == NULL || (pConfig->pFilePath == NULL && pConfig->pFilePathW == NULL)) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return MA_INVALID_ARGS; + } + + /* We want all access to the VFS and the internal decoder to happen on the job thread just to keep things easier to manage for the VFS. */ + + /* We need a copy of the file path. We should probably make this more efficient, but for now we'll do a transient memory allocation. */ + if (pConfig->pFilePath != NULL) { + pFilePathCopy = ma_copy_string(pConfig->pFilePath, &pResourceManager->config.allocationCallbacks); + } else { + pFilePathWCopy = ma_copy_string_w(pConfig->pFilePathW, &pResourceManager->config.allocationCallbacks); + } + + if (pFilePathCopy == NULL && pFilePathWCopy == NULL) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + return MA_OUT_OF_MEMORY; + } + + /* + We need to check for the presence of MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC. If it's not set, we need to wait before returning. Otherwise we + can return immediately. Likewise, we'll also check for MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT and do the same. + */ + if ((pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC) == 0 || (pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT) != 0) { + waitBeforeReturning = MA_TRUE; + ma_resource_manager_inline_notification_init(pResourceManager, &waitNotification); + } + + ma_resource_manager_pipeline_notifications_acquire_all_fences(¬ifications); + + /* Set the absolute cursor to our initial seek position so retrieval of the cursor returns a good value. */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, pConfig->initialSeekPointInPCMFrames); + + /* We now have everything we need to post the job. This is the last thing we need to do from here. The rest will be done by the job thread. */ + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_LOAD_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.resourceManager.loadDataStream.pDataStream = pDataStream; + job.data.resourceManager.loadDataStream.pFilePath = pFilePathCopy; + job.data.resourceManager.loadDataStream.pFilePathW = pFilePathWCopy; + job.data.resourceManager.loadDataStream.initialSeekPoint = pConfig->initialSeekPointInPCMFrames; + job.data.resourceManager.loadDataStream.pInitNotification = (waitBeforeReturning == MA_TRUE) ? &waitNotification : notifications.init.pNotification; + job.data.resourceManager.loadDataStream.pInitFence = notifications.init.pFence; + result = ma_resource_manager_post_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + ma_resource_manager_pipeline_notifications_signal_all_notifications(¬ifications); + ma_resource_manager_pipeline_notifications_release_all_fences(¬ifications); + + if (waitBeforeReturning) { + ma_resource_manager_inline_notification_uninit(&waitNotification); + } + + ma_free(pFilePathCopy, &pResourceManager->config.allocationCallbacks); + ma_free(pFilePathWCopy, &pResourceManager->config.allocationCallbacks); + return result; + } + + /* Wait if needed. */ + if (waitBeforeReturning) { + ma_resource_manager_inline_notification_wait_and_uninit(&waitNotification); + + if (notifications.init.pNotification != NULL) { + ma_async_notification_signal(notifications.init.pNotification); + } + + /* NOTE: Do not release pInitFence here. That will be done by the job. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_init(ma_resource_manager* pResourceManager, const char* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_stream_init_ex(pResourceManager, &config, pDataStream); +} + +MA_API ma_result ma_resource_manager_data_stream_init_w(ma_resource_manager* pResourceManager, const wchar_t* pFilePath, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_stream_init_ex(pResourceManager, &config, pDataStream); +} + +MA_API ma_result ma_resource_manager_data_stream_uninit(ma_resource_manager_data_stream* pDataStream) +{ + ma_resource_manager_inline_notification freeEvent; + ma_job job; + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + /* The first thing to do is set the result to unavailable. This will prevent future page decoding. */ + c89atomic_exchange_i32(&pDataStream->result, MA_UNAVAILABLE); + + /* + We need to post a job to ensure we're not in the middle or decoding or anything. Because the object is owned by the caller, we'll need + to wait for it to complete before returning which means we need an event. + */ + ma_resource_manager_inline_notification_init(pDataStream->pResourceManager, &freeEvent); + + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.resourceManager.freeDataStream.pDataStream = pDataStream; + job.data.resourceManager.freeDataStream.pDoneNotification = &freeEvent; + job.data.resourceManager.freeDataStream.pDoneFence = NULL; + ma_resource_manager_post_job(pDataStream->pResourceManager, &job); + + /* We need to wait for the job to finish processing before we return. */ + ma_resource_manager_inline_notification_wait_and_uninit(&freeEvent); + + return MA_SUCCESS; +} + + +static ma_uint32 ma_resource_manager_data_stream_get_page_size_in_frames(ma_resource_manager_data_stream* pDataStream) +{ + MA_ASSERT(pDataStream != NULL); + MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE); + + return MA_RESOURCE_MANAGER_PAGE_SIZE_IN_MILLISECONDS * (pDataStream->decoder.outputSampleRate/1000); +} + +static void* ma_resource_manager_data_stream_get_page_data_pointer(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex, ma_uint32 relativeCursor) +{ + MA_ASSERT(pDataStream != NULL); + MA_ASSERT(pDataStream->isDecoderInitialized == MA_TRUE); + MA_ASSERT(pageIndex == 0 || pageIndex == 1); + + return ma_offset_ptr(pDataStream->pPageData, ((ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * pageIndex) + relativeCursor) * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels)); +} + +static void ma_resource_manager_data_stream_fill_page(ma_resource_manager_data_stream* pDataStream, ma_uint32 pageIndex) +{ + ma_result result = MA_SUCCESS; + ma_uint64 pageSizeInFrames; + ma_uint64 totalFramesReadForThisPage = 0; + void* pPageData = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pageIndex, 0); + + pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream); + + /* The decoder needs to inherit the stream's looping and range state. */ + { + ma_uint64 rangeBeg; + ma_uint64 rangeEnd; + ma_uint64 loopPointBeg; + ma_uint64 loopPointEnd; + + ma_data_source_set_looping(&pDataStream->decoder, ma_resource_manager_data_stream_is_looping(pDataStream)); + + ma_data_source_get_range_in_pcm_frames(pDataStream, &rangeBeg, &rangeEnd); + ma_data_source_set_range_in_pcm_frames(&pDataStream->decoder, rangeBeg, rangeEnd); + + ma_data_source_get_loop_point_in_pcm_frames(pDataStream, &loopPointBeg, &loopPointEnd); + ma_data_source_set_loop_point_in_pcm_frames(&pDataStream->decoder, loopPointBeg, loopPointEnd); + } + + /* Just read straight from the decoder. It will deal with ranges and looping for us. */ + result = ma_data_source_read_pcm_frames(&pDataStream->decoder, pPageData, pageSizeInFrames, &totalFramesReadForThisPage); + if (result == MA_AT_END || totalFramesReadForThisPage < pageSizeInFrames) { + c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_TRUE); + } + + c89atomic_exchange_32(&pDataStream->pageFrameCount[pageIndex], (ma_uint32)totalFramesReadForThisPage); + c89atomic_exchange_32(&pDataStream->isPageValid[pageIndex], MA_TRUE); +} + +static void ma_resource_manager_data_stream_fill_pages(ma_resource_manager_data_stream* pDataStream) +{ + ma_uint32 iPage; + + MA_ASSERT(pDataStream != NULL); + + for (iPage = 0; iPage < 2; iPage += 1) { + ma_resource_manager_data_stream_fill_page(pDataStream, iPage); + } +} + + +static ma_result ma_resource_manager_data_stream_map(ma_resource_manager_data_stream* pDataStream, void** ppFramesOut, ma_uint64* pFrameCount) +{ + ma_uint64 framesAvailable; + ma_uint64 frameCount = 0; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pFrameCount != NULL) { + frameCount = *pFrameCount; + *pFrameCount = 0; + } + if (ppFramesOut != NULL) { + *ppFramesOut = NULL; + } + + if (pDataStream == NULL || ppFramesOut == NULL || pFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */ + if (ma_resource_manager_data_stream_seek_counter(pDataStream) > 0) { + return MA_BUSY; + } + + /* If the page we're on is invalid it means we've caught up to the job thread. */ + if (c89atomic_load_32(&pDataStream->isPageValid[pDataStream->currentPageIndex]) == MA_FALSE) { + framesAvailable = 0; + } else { + /* + The page we're on is valid so we must have some frames available. We need to make sure that we don't overflow into the next page, even if it's valid. The reason is + that the unmap process will only post an update for one page at a time. Keeping mapping tied to page boundaries makes this simpler. + */ + ma_uint32 currentPageFrameCount = c89atomic_load_32(&pDataStream->pageFrameCount[pDataStream->currentPageIndex]); + MA_ASSERT(currentPageFrameCount >= pDataStream->relativeCursor); + + framesAvailable = currentPageFrameCount - pDataStream->relativeCursor; + } + + /* If there's no frames available and the result is set to MA_AT_END we need to return MA_AT_END. */ + if (framesAvailable == 0) { + if (ma_resource_manager_data_stream_is_decoder_at_end(pDataStream)) { + return MA_AT_END; + } else { + return MA_BUSY; /* There are no frames available, but we're not marked as EOF so we might have caught up to the job thread. Need to return MA_BUSY and wait for more data. */ + } + } + + MA_ASSERT(framesAvailable > 0); + + if (frameCount > framesAvailable) { + frameCount = framesAvailable; + } + + *ppFramesOut = ma_resource_manager_data_stream_get_page_data_pointer(pDataStream, pDataStream->currentPageIndex, pDataStream->relativeCursor); + *pFrameCount = frameCount; + + return MA_SUCCESS; +} + +static ma_result ma_resource_manager_data_stream_unmap(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameCount) +{ + ma_uint32 newRelativeCursor; + ma_uint32 pageSizeInFrames; + ma_job job; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* The frame count should always fit inside a 32-bit integer. */ + if (frameCount > 0xFFFFFFFF) { + return MA_INVALID_ARGS; + } + + pageSizeInFrames = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream); + + /* The absolute cursor needs to be updated for ma_resource_manager_data_stream_get_cursor_in_pcm_frames(). */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, c89atomic_load_64(&pDataStream->absoluteCursor) + frameCount); + + /* Here is where we need to check if we need to load a new page, and if so, post a job to load it. */ + newRelativeCursor = pDataStream->relativeCursor + (ma_uint32)frameCount; + + /* If the new cursor has flowed over to the next page we need to mark the old one as invalid and post an event for it. */ + if (newRelativeCursor >= pageSizeInFrames) { + newRelativeCursor -= pageSizeInFrames; + + /* Here is where we post the job start decoding. */ + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.resourceManager.pageDataStream.pDataStream = pDataStream; + job.data.resourceManager.pageDataStream.pageIndex = pDataStream->currentPageIndex; + + /* The page needs to be marked as invalid so that the public API doesn't try reading from it. */ + c89atomic_exchange_32(&pDataStream->isPageValid[pDataStream->currentPageIndex], MA_FALSE); + + /* Before posting the job we need to make sure we set some state. */ + pDataStream->relativeCursor = newRelativeCursor; + pDataStream->currentPageIndex = (pDataStream->currentPageIndex + 1) & 0x01; + return ma_resource_manager_post_job(pDataStream->pResourceManager, &job); + } else { + /* We haven't moved into a new page so we can just move the cursor forward. */ + pDataStream->relativeCursor = newRelativeCursor; + return MA_SUCCESS; + } +} + + +MA_API ma_result ma_resource_manager_data_stream_read_pcm_frames(ma_resource_manager_data_stream* pDataStream, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesProcessed; + ma_format format; + ma_uint32 channels; + + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (frameCount == 0) { + return MA_INVALID_ARGS; + } + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* Don't attempt to read while we're in the middle of seeking. Tell the caller that we're busy. */ + if (ma_resource_manager_data_stream_seek_counter(pDataStream) > 0) { + return MA_BUSY; + } + + ma_resource_manager_data_stream_get_data_format(pDataStream, &format, &channels, NULL, NULL, 0); + + /* Reading is implemented in terms of map/unmap. We need to run this in a loop because mapping is clamped against page boundaries. */ + totalFramesProcessed = 0; + while (totalFramesProcessed < frameCount) { + void* pMappedFrames; + ma_uint64 mappedFrameCount; + + mappedFrameCount = frameCount - totalFramesProcessed; + result = ma_resource_manager_data_stream_map(pDataStream, &pMappedFrames, &mappedFrameCount); + if (result != MA_SUCCESS) { + break; + } + + /* Copy the mapped data to the output buffer if we have one. It's allowed for pFramesOut to be NULL in which case a relative forward seek is performed. */ + if (pFramesOut != NULL) { + ma_copy_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesProcessed, format, channels), pMappedFrames, mappedFrameCount, format, channels); + } + + totalFramesProcessed += mappedFrameCount; + + result = ma_resource_manager_data_stream_unmap(pDataStream, mappedFrameCount); + if (result != MA_SUCCESS) { + break; /* This is really bad - will only get an error here if we failed to post a job to the queue for loading the next page. */ + } + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesProcessed; + } + + if (result == MA_SUCCESS && totalFramesProcessed == 0) { + result = MA_AT_END; + } + + return result; +} + +MA_API ma_result ma_resource_manager_data_stream_seek_to_pcm_frame(ma_resource_manager_data_stream* pDataStream, ma_uint64 frameIndex) +{ + ma_job job; + ma_result streamResult; + + streamResult = ma_resource_manager_data_stream_result(pDataStream); + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(streamResult != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (streamResult != MA_SUCCESS && streamResult != MA_BUSY) { + return MA_INVALID_OPERATION; + } + + /* If we're not already seeking and we're sitting on the same frame, just make this a no-op. */ + if (c89atomic_load_32(&pDataStream->seekCounter) == 0) { + if (c89atomic_load_64(&pDataStream->absoluteCursor) == frameIndex) { + return MA_SUCCESS; + } + } + + + /* Increment the seek counter first to indicate to read_paged_pcm_frames() and map_paged_pcm_frames() that we are in the middle of a seek and MA_BUSY should be returned. */ + c89atomic_fetch_add_32(&pDataStream->seekCounter, 1); + + /* Update the absolute cursor so that ma_resource_manager_data_stream_get_cursor_in_pcm_frames() returns the new position. */ + ma_resource_manager_data_stream_set_absolute_cursor(pDataStream, frameIndex); + + /* + We need to clear our currently loaded pages so that the stream starts playback from the new seek point as soon as possible. These are for the purpose of the public + API and will be ignored by the seek job. The seek job will operate on the assumption that both pages have been marked as invalid and the cursor is at the start of + the first page. + */ + pDataStream->relativeCursor = 0; + pDataStream->currentPageIndex = 0; + c89atomic_exchange_32(&pDataStream->isPageValid[0], MA_FALSE); + c89atomic_exchange_32(&pDataStream->isPageValid[1], MA_FALSE); + + /* Make sure the data stream is not marked as at the end or else if we seek in response to hitting the end, we won't be able to read any more data. */ + c89atomic_exchange_32(&pDataStream->isDecoderAtEnd, MA_FALSE); + + /* + The public API is not allowed to touch the internal decoder so we need to use a job to perform the seek. When seeking, the job thread will assume both pages + are invalid and any content contained within them will be discarded and replaced with newly decoded data. + */ + job = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_SEEK_DATA_STREAM); + job.order = ma_resource_manager_data_stream_next_execution_order(pDataStream); + job.data.resourceManager.seekDataStream.pDataStream = pDataStream; + job.data.resourceManager.seekDataStream.frameIndex = frameIndex; + return ma_resource_manager_post_job(pDataStream->pResourceManager, &job); +} + +MA_API ma_result ma_resource_manager_data_stream_get_data_format(ma_resource_manager_data_stream* pDataStream, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pFormat != NULL) { + *pFormat = ma_format_unknown; + } + + if (pChannels != NULL) { + *pChannels = 0; + } + + if (pSampleRate != NULL) { + *pSampleRate = 0; + } + + if (pChannelMap != NULL) { + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap); + } + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + return MA_INVALID_OPERATION; + } + + /* + We're being a little bit naughty here and accessing the internal decoder from the public API. The output data format is constant, and we've defined this function + such that the application is responsible for ensuring it's not called while uninitializing so it should be safe. + */ + return ma_data_source_get_data_format(&pDataStream->decoder, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); +} + +MA_API ma_result ma_resource_manager_data_stream_get_cursor_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pCursor) +{ + ma_result result; + + if (pCursor == NULL) { + return MA_INVALID_ARGS; + } + + *pCursor = 0; + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + /* + If the stream is in an erroneous state we need to return an invalid operation. We can allow + this to be called when the data stream is in a busy state because the caller may have asked + for an initial seek position and it's convenient to return that as the cursor position. + */ + result = ma_resource_manager_data_stream_result(pDataStream); + if (result != MA_SUCCESS && result != MA_BUSY) { + return MA_INVALID_OPERATION; + } + + *pCursor = c89atomic_load_64(&pDataStream->absoluteCursor); + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_get_length_in_pcm_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pLength) +{ + ma_result streamResult; + + if (pLength == NULL) { + return MA_INVALID_ARGS; + } + + *pLength = 0; + + streamResult = ma_resource_manager_data_stream_result(pDataStream); + + /* We cannot be using the data source after it's been uninitialized. */ + MA_ASSERT(streamResult != MA_UNAVAILABLE); + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + if (streamResult != MA_SUCCESS) { + return streamResult; + } + + /* + We most definitely do not want to be calling ma_decoder_get_length_in_pcm_frames() directly. Instead we want to use a cached value that we + calculated when we initialized it on the job thread. + */ + *pLength = pDataStream->totalLengthInPCMFrames; + if (*pLength == 0) { + return MA_NOT_IMPLEMENTED; /* Some decoders may not have a known length. */ + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_stream_result(const ma_resource_manager_data_stream* pDataStream) +{ + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + return (ma_result)c89atomic_load_i32(&pDataStream->result); +} + +MA_API ma_result ma_resource_manager_data_stream_set_looping(ma_resource_manager_data_stream* pDataStream, ma_bool32 isLooping) +{ + return ma_data_source_set_looping(pDataStream, isLooping); +} + +MA_API ma_bool32 ma_resource_manager_data_stream_is_looping(const ma_resource_manager_data_stream* pDataStream) +{ + if (pDataStream == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32((ma_bool32*)&pDataStream->isLooping); /* Naughty const-cast. Value won't change from here in practice (maybe from another thread). */ +} + +MA_API ma_result ma_resource_manager_data_stream_get_available_frames(ma_resource_manager_data_stream* pDataStream, ma_uint64* pAvailableFrames) +{ + ma_uint32 pageIndex0; + ma_uint32 pageIndex1; + ma_uint32 relativeCursor; + ma_uint64 availableFrames; + + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataStream == NULL) { + return MA_INVALID_ARGS; + } + + pageIndex0 = pDataStream->currentPageIndex; + pageIndex1 = (pDataStream->currentPageIndex + 1) & 0x01; + relativeCursor = pDataStream->relativeCursor; + + availableFrames = 0; + if (c89atomic_load_32(&pDataStream->isPageValid[pageIndex0])) { + availableFrames += c89atomic_load_32(&pDataStream->pageFrameCount[pageIndex0]) - relativeCursor; + if (c89atomic_load_32(&pDataStream->isPageValid[pageIndex1])) { + availableFrames += c89atomic_load_32(&pDataStream->pageFrameCount[pageIndex1]); + } + } + + *pAvailableFrames = availableFrames; + return MA_SUCCESS; +} + + +static ma_result ma_resource_manager_data_source_preinit(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataSource); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + pDataSource->flags = pConfig->flags; + + return MA_SUCCESS; +} + +MA_API ma_result ma_resource_manager_data_source_init_ex(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source_config* pConfig, ma_resource_manager_data_source* pDataSource) +{ + ma_result result; + + result = ma_resource_manager_data_source_preinit(pResourceManager, pConfig, pDataSource); + if (result != MA_SUCCESS) { + return result; + } + + /* The data source itself is just a data stream or a data buffer. */ + if ((pConfig->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_init_ex(pResourceManager, pConfig, &pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_init_ex(pResourceManager, pConfig, &pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_init(ma_resource_manager* pResourceManager, const char* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePath = pName; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_source_init_ex(pResourceManager, &config, pDataSource); +} + +MA_API ma_result ma_resource_manager_data_source_init_w(ma_resource_manager* pResourceManager, const wchar_t* pName, ma_uint32 flags, const ma_resource_manager_pipeline_notifications* pNotifications, ma_resource_manager_data_source* pDataSource) +{ + ma_resource_manager_data_source_config config; + + config = ma_resource_manager_data_source_config_init(); + config.pFilePathW = pName; + config.flags = flags; + config.pNotifications = pNotifications; + + return ma_resource_manager_data_source_init_ex(pResourceManager, &config, pDataSource); +} + +MA_API ma_result ma_resource_manager_data_source_init_copy(ma_resource_manager* pResourceManager, const ma_resource_manager_data_source* pExistingDataSource, ma_resource_manager_data_source* pDataSource) +{ + ma_result result; + ma_resource_manager_data_source_config config; + + if (pExistingDataSource == NULL) { + return MA_INVALID_ARGS; + } + + config = ma_resource_manager_data_source_config_init(); + config.flags = pExistingDataSource->flags; + + result = ma_resource_manager_data_source_preinit(pResourceManager, &config, pDataSource); + if (result != MA_SUCCESS) { + return result; + } + + /* Copying can only be done from data buffers. Streams cannot be copied. */ + if ((pExistingDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return MA_INVALID_OPERATION; + } + + return ma_resource_manager_data_buffer_init_copy(pResourceManager, &pExistingDataSource->backend.buffer, &pDataSource->backend.buffer); +} + +MA_API ma_result ma_resource_manager_data_source_uninit(ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + /* All we need to is uninitialize the underlying data buffer or data stream. */ + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_uninit(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_uninit(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_read_pcm_frames(ma_resource_manager_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_read_pcm_frames(&pDataSource->backend.stream, pFramesOut, frameCount, pFramesRead); + } else { + return ma_resource_manager_data_buffer_read_pcm_frames(&pDataSource->backend.buffer, pFramesOut, frameCount, pFramesRead); + } +} + +MA_API ma_result ma_resource_manager_data_source_seek_to_pcm_frame(ma_resource_manager_data_source* pDataSource, ma_uint64 frameIndex) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_seek_to_pcm_frame(&pDataSource->backend.stream, frameIndex); + } else { + return ma_resource_manager_data_buffer_seek_to_pcm_frame(&pDataSource->backend.buffer, frameIndex); + } +} + +MA_API ma_result ma_resource_manager_data_source_map(ma_resource_manager_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_map(&pDataSource->backend.stream, ppFramesOut, pFrameCount); + } else { + return MA_NOT_IMPLEMENTED; /* Mapping not supported with data buffers. */ + } +} + +MA_API ma_result ma_resource_manager_data_source_unmap(ma_resource_manager_data_source* pDataSource, ma_uint64 frameCount) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_unmap(&pDataSource->backend.stream, frameCount); + } else { + return MA_NOT_IMPLEMENTED; /* Mapping not supported with data buffers. */ + } +} + +MA_API ma_result ma_resource_manager_data_source_get_data_format(ma_resource_manager_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_data_format(&pDataSource->backend.stream, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } else { + return ma_resource_manager_data_buffer_get_data_format(&pDataSource->backend.buffer, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_cursor_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pCursor) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_cursor_in_pcm_frames(&pDataSource->backend.stream, pCursor); + } else { + return ma_resource_manager_data_buffer_get_cursor_in_pcm_frames(&pDataSource->backend.buffer, pCursor); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_length_in_pcm_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pLength) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_length_in_pcm_frames(&pDataSource->backend.stream, pLength); + } else { + return ma_resource_manager_data_buffer_get_length_in_pcm_frames(&pDataSource->backend.buffer, pLength); + } +} + +MA_API ma_result ma_resource_manager_data_source_result(const ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_result(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_result(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_set_looping(ma_resource_manager_data_source* pDataSource, ma_bool32 isLooping) +{ + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_set_looping(&pDataSource->backend.stream, isLooping); + } else { + return ma_resource_manager_data_buffer_set_looping(&pDataSource->backend.buffer, isLooping); + } +} + +MA_API ma_bool32 ma_resource_manager_data_source_is_looping(const ma_resource_manager_data_source* pDataSource) +{ + if (pDataSource == NULL) { + return MA_FALSE; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_is_looping(&pDataSource->backend.stream); + } else { + return ma_resource_manager_data_buffer_is_looping(&pDataSource->backend.buffer); + } +} + +MA_API ma_result ma_resource_manager_data_source_get_available_frames(ma_resource_manager_data_source* pDataSource, ma_uint64* pAvailableFrames) +{ + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pDataSource == NULL) { + return MA_INVALID_ARGS; + } + + if ((pDataSource->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM) != 0) { + return ma_resource_manager_data_stream_get_available_frames(&pDataSource->backend.stream, pAvailableFrames); + } else { + return ma_resource_manager_data_buffer_get_available_frames(&pDataSource->backend.buffer, pAvailableFrames); + } +} + + +MA_API ma_result ma_resource_manager_post_job(ma_resource_manager* pResourceManager, const ma_job* pJob) +{ + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_queue_post(&pResourceManager->jobQueue, pJob); +} + +MA_API ma_result ma_resource_manager_post_job_quit(ma_resource_manager* pResourceManager) +{ + ma_job job = ma_job_init(MA_JOB_TYPE_QUIT); + return ma_resource_manager_post_job(pResourceManager, &job); +} + +MA_API ma_result ma_resource_manager_next_job(ma_resource_manager* pResourceManager, ma_job* pJob) +{ + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_queue_next(&pResourceManager->jobQueue, pJob); +} + + +static ma_result ma_job_process__resource_manager__load_data_buffer_node(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pJob != NULL); + + pResourceManager = (ma_resource_manager*)pJob->data.resourceManager.loadDataBufferNode.pResourceManager; + MA_ASSERT(pResourceManager != NULL); + + pDataBufferNode = (ma_resource_manager_data_buffer_node*)pJob->data.resourceManager.loadDataBufferNode.pDataBufferNode; + MA_ASSERT(pDataBufferNode != NULL); + MA_ASSERT(pDataBufferNode->isDataOwnedByResourceManager == MA_TRUE); /* The data should always be owned by the resource manager. */ + + /* The data buffer is not getting deleted, but we may be getting executed out of order. If so, we need to push the job back onto the queue and return. */ + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Attempting to execute out of order. Probably interleaved with a MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER job. */ + } + + /* First thing we need to do is check whether or not the data buffer is getting deleted. If so we just abort. */ + if (ma_resource_manager_data_buffer_node_result(pDataBufferNode) != MA_BUSY) { + result = ma_resource_manager_data_buffer_node_result(pDataBufferNode); /* The data buffer may be getting deleted before it's even been loaded. */ + goto done; + } + + /* + We're ready to start loading. Essentially what we're doing here is initializing the data supply + of the node. Once this is complete, data buffers can have their connectors initialized which + will allow then to have audio data read from them. + + Note that when the data supply type has been moved away from "unknown", that is when other threads + will determine that the node is available for data delivery and the data buffer connectors can be + initialized. Therefore, it's important that it is set after the data supply has been initialized. + */ + if ((pJob->data.resourceManager.loadDataBufferNode.flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE) != 0) { + /* + Decoding. This is the complex case because we're not going to be doing the entire decoding + process here. Instead it's going to be split of multiple jobs and loaded in pages. The + reason for this is to evenly distribute decoding time across multiple sounds, rather than + having one huge sound hog all the available processing resources. + + The first thing we do is initialize a decoder. This is allocated on the heap and is passed + around to the paging jobs. When the last paging job has completed it's processing, it'll + free the decoder for us. + + This job does not do any actual decoding. It instead just posts a PAGE_DATA_BUFFER_NODE job + which is where the actual decoding work will be done. However, once this job is complete, + the node will be in a state where data buffer connectors can be initialized. + */ + ma_decoder* pDecoder; /* <-- Free'd on the last page decode. */ + ma_job pageDataBufferNodeJob; + + /* Allocate the decoder by initializing a decoded data supply. */ + result = ma_resource_manager_data_buffer_node_init_supply_decoded(pResourceManager, pDataBufferNode, pJob->data.resourceManager.loadDataBufferNode.pFilePath, pJob->data.resourceManager.loadDataBufferNode.pFilePathW, pJob->data.resourceManager.loadDataBufferNode.flags, &pDecoder); + + /* + Don't ever propagate an MA_BUSY result code or else the resource manager will think the + node is just busy decoding rather than in an error state. This should never happen, but + including this logic for safety just in case. + */ + if (result == MA_BUSY) { + result = MA_ERROR; + } + + if (result != MA_SUCCESS) { + if (pJob->data.resourceManager.loadDataBufferNode.pFilePath != NULL) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%s\". %s.\n", pJob->data.resourceManager.loadDataBufferNode.pFilePath, ma_result_description(result)); + } else { + #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(_MSC_VER) + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_WARNING, "Failed to initialize data supply for \"%ls\", %s.\n", pJob->data.resourceManager.loadDataBufferNode.pFilePathW, ma_result_description(result)); + #endif + } + + goto done; + } + + /* + At this point the node's data supply is initialized and other threads can start initializing + their data buffer connectors. However, no data will actually be available until we start to + actually decode it. To do this, we need to post a paging job which is where the decoding + work is done. + + Note that if an error occurred at an earlier point, this section will have been skipped. + */ + pageDataBufferNodeJob = ma_job_init(MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE); + pageDataBufferNodeJob.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pResourceManager = pResourceManager; + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pDataBufferNode = pDataBufferNode; + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pDecoder = pDecoder; + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pDoneNotification = pJob->data.resourceManager.loadDataBufferNode.pDoneNotification; + pageDataBufferNodeJob.data.resourceManager.pageDataBufferNode.pDoneFence = pJob->data.resourceManager.loadDataBufferNode.pDoneFence; + + /* The job has been set up so it can now be posted. */ + result = ma_resource_manager_post_job(pResourceManager, &pageDataBufferNodeJob); + + /* + When we get here, we want to make sure the result code is set to MA_BUSY. The reason for + this is that the result will be copied over to the node's internal result variable. In + this case, since the decoding is still in-progress, we need to make sure the result code + is set to MA_BUSY. + */ + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to post MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE job. %s\n", ma_result_description(result)); + ma_decoder_uninit(pDecoder); + ma_free(pDecoder, &pResourceManager->config.allocationCallbacks); + } else { + result = MA_BUSY; + } + } else { + /* No decoding. This is the simple case. We need only read the file content into memory and we're done. */ + result = ma_resource_manager_data_buffer_node_init_supply_encoded(pResourceManager, pDataBufferNode, pJob->data.resourceManager.loadDataBufferNode.pFilePath, pJob->data.resourceManager.loadDataBufferNode.pFilePathW); + } + + +done: + /* File paths are no longer needed. */ + ma_free(pJob->data.resourceManager.loadDataBufferNode.pFilePath, &pResourceManager->config.allocationCallbacks); + ma_free(pJob->data.resourceManager.loadDataBufferNode.pFilePathW, &pResourceManager->config.allocationCallbacks); + + /* + We need to set the result to at the very end to ensure no other threads try reading the data before we've fully initialized the object. Other threads + are going to be inspecting this variable to determine whether or not they're ready to read data. We can only change the result if it's set to MA_BUSY + because otherwise we may be changing away from an error code which would be bad. An example is if the application creates a data buffer, but then + immediately deletes it before we've got to this point. In this case, pDataBuffer->result will be MA_UNAVAILABLE, and setting it to MA_SUCCESS or any + other error code would cause the buffer to look like it's in a state that it's not. + */ + c89atomic_compare_and_swap_i32(&pDataBufferNode->result, MA_BUSY, result); + + /* At this point initialization is complete and we can signal the notification if any. */ + if (pJob->data.resourceManager.loadDataBufferNode.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataBufferNode.pInitNotification); + } + if (pJob->data.resourceManager.loadDataBufferNode.pInitFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataBufferNode.pInitFence); + } + + /* If we have a success result it means we've fully loaded the buffer. This will happen in the non-decoding case. */ + if (result != MA_BUSY) { + if (pJob->data.resourceManager.loadDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataBufferNode.pDoneNotification); + } + if (pJob->data.resourceManager.loadDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataBufferNode.pDoneFence); + } + } + + /* Increment the node's execution pointer so that the next jobs can be processed. This is how we keep decoding of pages in-order. */ + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return result; +} + +static ma_result ma_job_process__resource_manager__free_data_buffer_node(ma_job* pJob) +{ + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pJob != NULL); + + pResourceManager = (ma_resource_manager*)pJob->data.resourceManager.freeDataBufferNode.pResourceManager; + MA_ASSERT(pResourceManager != NULL); + + pDataBufferNode = (ma_resource_manager_data_buffer_node*)pJob->data.resourceManager.freeDataBufferNode.pDataBufferNode; + MA_ASSERT(pDataBufferNode != NULL); + + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode); + + /* The event needs to be signalled last. */ + if (pJob->data.resourceManager.freeDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.freeDataBufferNode.pDoneNotification); + } + + if (pJob->data.resourceManager.freeDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.freeDataBufferNode.pDoneFence); + } + + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return MA_SUCCESS; +} + +static ma_result ma_job_process__resource_manager__page_data_buffer_node(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer_node* pDataBufferNode; + + MA_ASSERT(pJob != NULL); + + pResourceManager = (ma_resource_manager*)pJob->data.resourceManager.pageDataBufferNode.pResourceManager; + MA_ASSERT(pResourceManager != NULL); + + pDataBufferNode = (ma_resource_manager_data_buffer_node*)pJob->data.resourceManager.pageDataBufferNode.pDataBufferNode; + MA_ASSERT(pDataBufferNode != NULL); + + if (pJob->order != c89atomic_load_32(&pDataBufferNode->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* Don't do any more decoding if the data buffer has started the uninitialization process. */ + result = ma_resource_manager_data_buffer_node_result(pDataBufferNode); + if (result != MA_BUSY) { + goto done; + } + + /* We're ready to decode the next page. */ + result = ma_resource_manager_data_buffer_node_decode_next_page(pResourceManager, pDataBufferNode, (ma_decoder*)pJob->data.resourceManager.pageDataBufferNode.pDecoder); + + /* + If we have a success code by this point, we want to post another job. We're going to set the + result back to MA_BUSY to make it clear that there's still more to load. + */ + if (result == MA_SUCCESS) { + ma_job newJob; + newJob = *pJob; /* Everything is the same as the input job, except the execution order. */ + newJob.order = ma_resource_manager_data_buffer_node_next_execution_order(pDataBufferNode); /* We need a fresh execution order. */ + + result = ma_resource_manager_post_job(pResourceManager, &newJob); + + /* Since the sound isn't yet fully decoded we want the status to be set to busy. */ + if (result == MA_SUCCESS) { + result = MA_BUSY; + } + } + +done: + /* If there's still more to decode the result will be set to MA_BUSY. Otherwise we can free the decoder. */ + if (result != MA_BUSY) { + ma_decoder_uninit((ma_decoder*)pJob->data.resourceManager.pageDataBufferNode.pDecoder); + ma_free(pJob->data.resourceManager.pageDataBufferNode.pDecoder, &pResourceManager->config.allocationCallbacks); + } + + /* If we reached the end we need to treat it as successful. */ + if (result == MA_AT_END) { + result = MA_SUCCESS; + } + + /* Make sure we set the result of node in case some error occurred. */ + c89atomic_compare_and_swap_i32(&pDataBufferNode->result, MA_BUSY, result); + + /* Signal the notification after setting the result in case the notification callback wants to inspect the result code. */ + if (result != MA_BUSY) { + if (pJob->data.resourceManager.pageDataBufferNode.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.pageDataBufferNode.pDoneNotification); + } + + if (pJob->data.resourceManager.pageDataBufferNode.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.pageDataBufferNode.pDoneFence); + } + } + + c89atomic_fetch_add_32(&pDataBufferNode->executionPointer, 1); + return result; +} + + +static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer* pDataBuffer; + ma_resource_manager_data_supply_type dataSupplyType = ma_resource_manager_data_supply_type_unknown; + ma_bool32 isConnectorInitialized = MA_FALSE; + + /* + All we're doing here is checking if the node has finished loading. If not, we just re-post the job + and keep waiting. Otherwise we increment the execution counter and set the buffer's result code. + */ + MA_ASSERT(pJob != NULL); + + pDataBuffer = (ma_resource_manager_data_buffer*)pJob->data.resourceManager.loadDataBuffer.pDataBuffer; + MA_ASSERT(pDataBuffer != NULL); + + pResourceManager = pDataBuffer->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataBuffer->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Attempting to execute out of order. Probably interleaved with a MA_JOB_TYPE_RESOURCE_MANAGER_FREE_DATA_BUFFER job. */ + } + + /* + First thing we need to do is check whether or not the data buffer is getting deleted. If so we + just abort, but making sure we increment the execution pointer. + */ + result = ma_resource_manager_data_buffer_result(pDataBuffer); + if (result != MA_BUSY) { + goto done; /* <-- This will ensure the exucution pointer is incremented. */ + } else { + result = MA_SUCCESS; /* <-- Make sure this is reset. */ + } + + /* Try initializing the connector if we haven't already. */ + isConnectorInitialized = pDataBuffer->isConnectorInitialized; + if (isConnectorInitialized == MA_FALSE) { + dataSupplyType = ma_resource_manager_data_buffer_node_get_data_supply_type(pDataBuffer->pNode); + + if (dataSupplyType != ma_resource_manager_data_supply_type_unknown) { + /* We can now initialize the connector. If this fails, we need to abort. It's very rare for this to fail. */ + ma_resource_manager_data_source_config dataSourceConfig; /* For setting initial looping state and range. */ + dataSourceConfig = ma_resource_manager_data_source_config_init(); + dataSourceConfig.rangeBegInPCMFrames = pJob->data.resourceManager.loadDataBuffer.rangeBegInPCMFrames; + dataSourceConfig.rangeEndInPCMFrames = pJob->data.resourceManager.loadDataBuffer.rangeEndInPCMFrames; + dataSourceConfig.loopPointBegInPCMFrames = pJob->data.resourceManager.loadDataBuffer.loopPointBegInPCMFrames; + dataSourceConfig.loopPointEndInPCMFrames = pJob->data.resourceManager.loadDataBuffer.loopPointEndInPCMFrames; + dataSourceConfig.isLooping = pJob->data.resourceManager.loadDataBuffer.isLooping; + + result = ma_resource_manager_data_buffer_init_connector(pDataBuffer, &dataSourceConfig, pJob->data.resourceManager.loadDataBuffer.pInitNotification, pJob->data.resourceManager.loadDataBuffer.pInitFence); + if (result != MA_SUCCESS) { + ma_log_postf(ma_resource_manager_get_log(pResourceManager), MA_LOG_LEVEL_ERROR, "Failed to initialize connector for data buffer. %s.\n", ma_result_description(result)); + goto done; + } + } else { + /* Don't have a known data supply type. Most likely the data buffer node is still loading, but it could be that an error occurred. */ + } + } else { + /* The connector is already initialized. Nothing to do here. */ + } + + /* + If the data node is still loading, we need to repost the job and *not* increment the execution + pointer (i.e. we need to not fall through to the "done" label). + + There is a hole between here and the where the data connector is initialized where the data + buffer node may have finished initializing. We need to check for this by checking the result of + the data buffer node and whether or not we had an unknown data supply type at the time of + trying to initialize the data connector. + */ + result = ma_resource_manager_data_buffer_node_result(pDataBuffer->pNode); + if (result == MA_BUSY || (result == MA_SUCCESS && isConnectorInitialized == MA_FALSE && dataSupplyType == ma_resource_manager_data_supply_type_unknown)) { + return ma_resource_manager_post_job(pResourceManager, pJob); + } + +done: + /* Only move away from a busy code so that we don't trash any existing error codes. */ + c89atomic_compare_and_swap_i32(&pDataBuffer->result, MA_BUSY, result); + + /* Only signal the other threads after the result has been set just for cleanliness sake. */ + if (pJob->data.resourceManager.loadDataBuffer.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataBuffer.pDoneNotification); + } + if (pJob->data.resourceManager.loadDataBuffer.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataBuffer.pDoneFence); + } + + /* + If at this point the data buffer has not had it's connector initialized, it means the + notification event was never signalled which means we need to signal it here. + */ + if (pDataBuffer->isConnectorInitialized == MA_FALSE && result != MA_SUCCESS) { + if (pJob->data.resourceManager.loadDataBuffer.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataBuffer.pInitNotification); + } + if (pJob->data.resourceManager.loadDataBuffer.pInitFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataBuffer.pInitFence); + } + } + + c89atomic_fetch_add_32(&pDataBuffer->executionPointer, 1); + return result; +} + +static ma_result ma_job_process__resource_manager__free_data_buffer(ma_job* pJob) +{ + ma_resource_manager* pResourceManager; + ma_resource_manager_data_buffer* pDataBuffer; + + MA_ASSERT(pJob != NULL); + + pDataBuffer = (ma_resource_manager_data_buffer*)pJob->data.resourceManager.freeDataBuffer.pDataBuffer; + MA_ASSERT(pDataBuffer != NULL); + + pResourceManager = pDataBuffer->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataBuffer->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + ma_resource_manager_data_buffer_uninit_internal(pDataBuffer); + + /* The event needs to be signalled last. */ + if (pJob->data.resourceManager.freeDataBuffer.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.freeDataBuffer.pDoneNotification); + } + + if (pJob->data.resourceManager.freeDataBuffer.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.freeDataBuffer.pDoneFence); + } + + c89atomic_fetch_add_32(&pDataBuffer->executionPointer, 1); + return MA_SUCCESS; +} + +static ma_result ma_job_process__resource_manager__load_data_stream(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_decoder_config decoderConfig; + ma_uint32 pageBufferSizeInBytes; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pJob != NULL); + + pDataStream = (ma_resource_manager_data_stream*)pJob->data.resourceManager.loadDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + pResourceManager = pDataStream->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + if (ma_resource_manager_data_stream_result(pDataStream) != MA_BUSY) { + result = MA_INVALID_OPERATION; /* Most likely the data stream is being uninitialized. */ + goto done; + } + + /* We need to initialize the decoder first so we can determine the size of the pages. */ + decoderConfig = ma_resource_manager__init_decoder_config(pResourceManager); + + if (pJob->data.resourceManager.loadDataStream.pFilePath != NULL) { + result = ma_decoder_init_vfs(pResourceManager->config.pVFS, pJob->data.resourceManager.loadDataStream.pFilePath, &decoderConfig, &pDataStream->decoder); + } else { + result = ma_decoder_init_vfs_w(pResourceManager->config.pVFS, pJob->data.resourceManager.loadDataStream.pFilePathW, &decoderConfig, &pDataStream->decoder); + } + if (result != MA_SUCCESS) { + goto done; + } + + /* Retrieve the total length of the file before marking the decoder are loaded. */ + if ((pDataStream->flags & MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH) == 0) { + result = ma_decoder_get_length_in_pcm_frames(&pDataStream->decoder, &pDataStream->totalLengthInPCMFrames); + if (result != MA_SUCCESS) { + goto done; /* Failed to retrieve the length. */ + } + } else { + pDataStream->totalLengthInPCMFrames = 0; + } + + /* + Only mark the decoder as initialized when the length of the decoder has been retrieved because that can possibly require a scan over the whole file + and we don't want to have another thread trying to access the decoder while it's scanning. + */ + pDataStream->isDecoderInitialized = MA_TRUE; + + /* We have the decoder so we can now initialize our page buffer. */ + pageBufferSizeInBytes = ma_resource_manager_data_stream_get_page_size_in_frames(pDataStream) * 2 * ma_get_bytes_per_frame(pDataStream->decoder.outputFormat, pDataStream->decoder.outputChannels); + + pDataStream->pPageData = ma_malloc(pageBufferSizeInBytes, &pResourceManager->config.allocationCallbacks); + if (pDataStream->pPageData == NULL) { + ma_decoder_uninit(&pDataStream->decoder); + result = MA_OUT_OF_MEMORY; + goto done; + } + + /* Seek to our initial seek point before filling the initial pages. */ + ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->data.resourceManager.loadDataStream.initialSeekPoint); + + /* We have our decoder and our page buffer, so now we need to fill our pages. */ + ma_resource_manager_data_stream_fill_pages(pDataStream); + + /* And now we're done. We want to make sure the result is MA_SUCCESS. */ + result = MA_SUCCESS; + +done: + ma_free(pJob->data.resourceManager.loadDataStream.pFilePath, &pResourceManager->config.allocationCallbacks); + ma_free(pJob->data.resourceManager.loadDataStream.pFilePathW, &pResourceManager->config.allocationCallbacks); + + /* We can only change the status away from MA_BUSY. If it's set to anything else it means an error has occurred somewhere or the uninitialization process has started (most likely). */ + c89atomic_compare_and_swap_i32(&pDataStream->result, MA_BUSY, result); + + /* Only signal the other threads after the result has been set just for cleanliness sake. */ + if (pJob->data.resourceManager.loadDataStream.pInitNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.loadDataStream.pInitNotification); + } + if (pJob->data.resourceManager.loadDataStream.pInitFence != NULL) { + ma_fence_release(pJob->data.resourceManager.loadDataStream.pInitFence); + } + + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +static ma_result ma_job_process__resource_manager__free_data_stream(ma_job* pJob) +{ + ma_resource_manager* pResourceManager; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pJob != NULL); + + pDataStream = (ma_resource_manager_data_stream*)pJob->data.resourceManager.freeDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + pResourceManager = pDataStream->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* If our status is not MA_UNAVAILABLE we have a bug somewhere. */ + MA_ASSERT(ma_resource_manager_data_stream_result(pDataStream) == MA_UNAVAILABLE); + + if (pDataStream->isDecoderInitialized) { + ma_decoder_uninit(&pDataStream->decoder); + } + + if (pDataStream->pPageData != NULL) { + ma_free(pDataStream->pPageData, &pResourceManager->config.allocationCallbacks); + pDataStream->pPageData = NULL; /* Just in case... */ + } + + ma_data_source_uninit(&pDataStream->ds); + + /* The event needs to be signalled last. */ + if (pJob->data.resourceManager.freeDataStream.pDoneNotification != NULL) { + ma_async_notification_signal(pJob->data.resourceManager.freeDataStream.pDoneNotification); + } + if (pJob->data.resourceManager.freeDataStream.pDoneFence != NULL) { + ma_fence_release(pJob->data.resourceManager.freeDataStream.pDoneFence); + } + + /*c89atomic_fetch_add_32(&pDataStream->executionPointer, 1);*/ + return MA_SUCCESS; +} + +static ma_result ma_job_process__resource_manager__page_data_stream(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pJob != NULL); + + pDataStream = (ma_resource_manager_data_stream*)pJob->data.resourceManager.pageDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + pResourceManager = pDataStream->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* For streams, the status should be MA_SUCCESS. */ + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS) { + result = MA_INVALID_OPERATION; + goto done; + } + + ma_resource_manager_data_stream_fill_page(pDataStream, pJob->data.resourceManager.pageDataStream.pageIndex); + +done: + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob) +{ + ma_result result = MA_SUCCESS; + ma_resource_manager* pResourceManager; + ma_resource_manager_data_stream* pDataStream; + + MA_ASSERT(pJob != NULL); + + pDataStream = (ma_resource_manager_data_stream*)pJob->data.resourceManager.seekDataStream.pDataStream; + MA_ASSERT(pDataStream != NULL); + + pResourceManager = pDataStream->pResourceManager; + + if (pJob->order != c89atomic_load_32(&pDataStream->executionPointer)) { + return ma_resource_manager_post_job(pResourceManager, pJob); /* Out of order. */ + } + + /* For streams the status should be MA_SUCCESS for this to do anything. */ + if (ma_resource_manager_data_stream_result(pDataStream) != MA_SUCCESS || pDataStream->isDecoderInitialized == MA_FALSE) { + result = MA_INVALID_OPERATION; + goto done; + } + + /* + With seeking we just assume both pages are invalid and the relative frame cursor at position 0. This is basically exactly the same as loading, except + instead of initializing the decoder, we seek to a frame. + */ + ma_decoder_seek_to_pcm_frame(&pDataStream->decoder, pJob->data.resourceManager.seekDataStream.frameIndex); + + /* After seeking we'll need to reload the pages. */ + ma_resource_manager_data_stream_fill_pages(pDataStream); + + /* We need to let the public API know that we're done seeking. */ + c89atomic_fetch_sub_32(&pDataStream->seekCounter, 1); + +done: + c89atomic_fetch_add_32(&pDataStream->executionPointer, 1); + return result; +} + +MA_API ma_result ma_resource_manager_process_job(ma_resource_manager* pResourceManager, ma_job* pJob) +{ + if (pResourceManager == NULL || pJob == NULL) { + return MA_INVALID_ARGS; + } + + return ma_job_process(pJob); +} + +MA_API ma_result ma_resource_manager_process_next_job(ma_resource_manager* pResourceManager) +{ + ma_result result; + ma_job job; + + if (pResourceManager == NULL) { + return MA_INVALID_ARGS; + } + + /* This will return MA_CANCELLED if the next job is a quit job. */ + result = ma_resource_manager_next_job(pResourceManager, &job); + if (result != MA_SUCCESS) { + return result; + } + + return ma_job_process(&job); +} +#else +/* We'll get here if the resource manager is being excluded from the build. We need to define the job processing callbacks as no-ops. */ +static ma_result ma_job_process__resource_manager__load_data_buffer_node(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__free_data_buffer_node(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__page_data_buffer_node(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__load_data_buffer(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__free_data_buffer(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__load_data_stream(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__free_data_stream(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__page_data_stream(ma_job* pJob) { return ma_job_process__noop(pJob); } +static ma_result ma_job_process__resource_manager__seek_data_stream(ma_job* pJob) { return ma_job_process__noop(pJob); } +#endif /* MA_NO_RESOURCE_MANAGER */ + + +#ifndef MA_NO_NODE_GRAPH +/* 10ms @ 48K = 480. Must never exceed 65535. */ +#ifndef MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS +#define MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS 480 +#endif + + +static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime); + +MA_API void ma_debug_fill_pcm_frames_with_sine_wave(float* pFramesOut, ma_uint32 frameCount, ma_format format, ma_uint32 channels, ma_uint32 sampleRate) +{ + #ifndef MA_NO_GENERATION + { + ma_waveform_config waveformConfig; + ma_waveform waveform; + + waveformConfig = ma_waveform_config_init(format, channels, sampleRate, ma_waveform_type_sine, 1.0, 400); + ma_waveform_init(&waveformConfig, &waveform); + ma_waveform_read_pcm_frames(&waveform, pFramesOut, frameCount, NULL); + } + #else + { + (void)pFramesOut; + (void)frameCount; + (void)format; + (void)channels; + (void)sampleRate; + #if defined(MA_DEBUG_OUTPUT) + { + #if _MSC_VER + #pragma message ("ma_debug_fill_pcm_frames_with_sine_wave() will do nothing because MA_NO_GENERATION is enabled.") + #endif + } + #endif + } + #endif +} + + + +static ma_result ma_mix_pcm_frames_f32(float* pDst, const float* pSrc, ma_uint64 frameCount, ma_uint32 channels, float volume) +{ + ma_uint64 iSample; + ma_uint64 sampleCount; + + if (pDst == NULL || pSrc == NULL || channels == 0) { + return MA_INVALID_ARGS; + } + + if (volume == 0) { + return MA_SUCCESS; /* No changes if the volume is 0. */ + } + + sampleCount = frameCount * channels; + + if (volume == 1) { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pDst[iSample] += pSrc[iSample]; + } + } else { + for (iSample = 0; iSample < sampleCount; iSample += 1) { + pDst[iSample] += ma_apply_volume_unclipped_f32(pSrc[iSample], volume); + } + } + + return MA_SUCCESS; +} + + +MA_API ma_node_graph_config ma_node_graph_config_init(ma_uint32 channels) +{ + ma_node_graph_config config; + + MA_ZERO_OBJECT(&config); + config.channels = channels; + config.nodeCacheCapInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + + return config; +} + + +static void ma_node_graph_set_is_reading(ma_node_graph* pNodeGraph, ma_bool32 isReading) +{ + MA_ASSERT(pNodeGraph != NULL); + c89atomic_exchange_32(&pNodeGraph->isReading, isReading); +} + +#if 0 +static ma_bool32 ma_node_graph_is_reading(ma_node_graph* pNodeGraph) +{ + MA_ASSERT(pNodeGraph != NULL); + return c89atomic_load_32(&pNodeGraph->isReading); +} +#endif + + +static void ma_node_graph_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_node_graph* pNodeGraph = (ma_node_graph*)pNode; + ma_uint64 framesRead; + + ma_node_graph_read_pcm_frames(pNodeGraph, ppFramesOut[0], *pFrameCountOut, &framesRead); + + *pFrameCountOut = (ma_uint32)framesRead; /* Safe cast. */ + + (void)ppFramesIn; + (void)pFrameCountIn; +} + +static ma_node_vtable g_node_graph_node_vtable = +{ + ma_node_graph_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 0, /* 0 input buses. */ + 1, /* 1 output bus. */ + 0 /* Flags. */ +}; + +static void ma_node_graph_endpoint_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + MA_ASSERT(pNode != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pNode) == 1); + MA_ASSERT(ma_node_get_output_bus_count(pNode) == 1); + + /* Input channel count needs to be the same as the output channel count. */ + MA_ASSERT(ma_node_get_input_channels(pNode, 0) == ma_node_get_output_channels(pNode, 0)); + + /* We don't need to do anything here because it's a passthrough. */ + (void)pNode; + (void)ppFramesIn; + (void)pFrameCountIn; + (void)ppFramesOut; + (void)pFrameCountOut; + +#if 0 + /* The data has already been mixed. We just need to move it to the output buffer. */ + if (ppFramesIn != NULL) { + ma_copy_pcm_frames(ppFramesOut[0], ppFramesIn[0], *pFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNode, 0)); + } +#endif +} + +static ma_node_vtable g_node_graph_endpoint_vtable = +{ + ma_node_graph_endpoint_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* 1 input bus. */ + 1, /* 1 output bus. */ + MA_NODE_FLAG_PASSTHROUGH /* Flags. The endpoint is a passthrough. */ +}; + +MA_API ma_result ma_node_graph_init(const ma_node_graph_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node_graph* pNodeGraph) +{ + ma_result result; + ma_node_config baseConfig; + ma_node_config endpointConfig; + + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNodeGraph); + pNodeGraph->nodeCacheCapInFrames = pConfig->nodeCacheCapInFrames; + if (pNodeGraph->nodeCacheCapInFrames == 0) { + pNodeGraph->nodeCacheCapInFrames = MA_DEFAULT_NODE_CACHE_CAP_IN_FRAMES_PER_BUS; + } + + + /* Base node so we can use the node graph as a node into another graph. */ + baseConfig = ma_node_config_init(); + baseConfig.vtable = &g_node_graph_node_vtable; + baseConfig.pOutputChannels = &pConfig->channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pNodeGraph->base); + if (result != MA_SUCCESS) { + return result; + } + + + /* Endpoint. */ + endpointConfig = ma_node_config_init(); + endpointConfig.vtable = &g_node_graph_endpoint_vtable; + endpointConfig.pInputChannels = &pConfig->channels; + endpointConfig.pOutputChannels = &pConfig->channels; + + result = ma_node_init(pNodeGraph, &endpointConfig, pAllocationCallbacks, &pNodeGraph->endpoint); + if (result != MA_SUCCESS) { + ma_node_uninit(&pNodeGraph->base, pAllocationCallbacks); + return result; + } + + return MA_SUCCESS; +} + +MA_API void ma_node_graph_uninit(ma_node_graph* pNodeGraph, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pNodeGraph == NULL) { + return; + } + + ma_node_uninit(&pNodeGraph->endpoint, pAllocationCallbacks); +} + +MA_API ma_node* ma_node_graph_get_endpoint(ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return NULL; + } + + return &pNodeGraph->endpoint; +} + +MA_API ma_result ma_node_graph_read_pcm_frames(ma_node_graph* pNodeGraph, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_result result = MA_SUCCESS; + ma_uint64 totalFramesRead; + ma_uint32 channels; + + if (pFramesRead != NULL) { + *pFramesRead = 0; /* Safety. */ + } + + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + channels = ma_node_get_output_channels(&pNodeGraph->endpoint, 0); + + + /* We'll be nice and try to do a full read of all frameCount frames. */ + totalFramesRead = 0; + while (totalFramesRead < frameCount) { + ma_uint32 framesJustRead; + ma_uint64 framesToRead = frameCount - totalFramesRead; + + if (framesToRead > 0xFFFFFFFF) { + framesToRead = 0xFFFFFFFF; + } + + ma_node_graph_set_is_reading(pNodeGraph, MA_TRUE); + { + result = ma_node_read_pcm_frames(&pNodeGraph->endpoint, 0, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (ma_uint32)framesToRead, &framesJustRead, ma_node_get_time(&pNodeGraph->endpoint)); + } + ma_node_graph_set_is_reading(pNodeGraph, MA_FALSE); + + totalFramesRead += framesJustRead; + + if (result != MA_SUCCESS) { + break; + } + + /* Abort if we weren't able to read any frames or else we risk getting stuck in a loop. */ + if (framesJustRead == 0) { + break; + } + } + + /* Let's go ahead and silence any leftover frames just for some added safety to ensure the caller doesn't try emitting garbage out of the speakers. */ + if (totalFramesRead < frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, ma_format_f32, channels), (frameCount - totalFramesRead), ma_format_f32, channels); + } + + if (pFramesRead != NULL) { + *pFramesRead = totalFramesRead; + } + + return result; +} + +MA_API ma_uint32 ma_node_graph_get_channels(const ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return 0; + } + + return ma_node_get_output_channels(&pNodeGraph->endpoint, 0); +} + +MA_API ma_uint64 ma_node_graph_get_time(const ma_node_graph* pNodeGraph) +{ + if (pNodeGraph == NULL) { + return 0; + } + + return ma_node_get_time(&pNodeGraph->endpoint); /* Global time is just the local time of the endpoint. */ +} + +MA_API ma_result ma_node_graph_set_time(ma_node_graph* pNodeGraph, ma_uint64 globalTime) +{ + if (pNodeGraph == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_time(&pNodeGraph->endpoint, globalTime); /* Global time is just the local time of the endpoint. */ +} + + +#define MA_NODE_OUTPUT_BUS_FLAG_HAS_READ 0x01 /* Whether or not this bus ready to read more data. Only used on nodes with multiple output buses. */ + +static ma_result ma_node_output_bus_init(ma_node* pNode, ma_uint32 outputBusIndex, ma_uint32 channels, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pOutputBus != NULL); + MA_ASSERT(outputBusIndex < MA_MAX_NODE_BUS_COUNT); + MA_ASSERT(outputBusIndex < ma_node_get_output_bus_count(pNode)); + MA_ASSERT(channels < 256); + + MA_ZERO_OBJECT(pOutputBus); + + if (channels == 0) { + return MA_INVALID_ARGS; + } + + pOutputBus->pNode = pNode; + pOutputBus->outputBusIndex = (ma_uint8)outputBusIndex; + pOutputBus->channels = (ma_uint8)channels; + pOutputBus->flags = MA_NODE_OUTPUT_BUS_FLAG_HAS_READ; /* <-- Important that this flag is set by default. */ + pOutputBus->volume = 1; + + return MA_SUCCESS; +} + +static void ma_node_output_bus_lock(ma_node_output_bus* pOutputBus) +{ + ma_spinlock_lock(&pOutputBus->lock); +} + +static void ma_node_output_bus_unlock(ma_node_output_bus* pOutputBus) +{ + ma_spinlock_unlock(&pOutputBus->lock); +} + + +static ma_uint32 ma_node_output_bus_get_channels(const ma_node_output_bus* pOutputBus) +{ + return pOutputBus->channels; +} + + +static void ma_node_output_bus_set_has_read(ma_node_output_bus* pOutputBus, ma_bool32 hasRead) +{ + if (hasRead) { + c89atomic_fetch_or_32(&pOutputBus->flags, MA_NODE_OUTPUT_BUS_FLAG_HAS_READ); + } else { + c89atomic_fetch_and_32(&pOutputBus->flags, (ma_uint32)~MA_NODE_OUTPUT_BUS_FLAG_HAS_READ); + } +} + +static ma_bool32 ma_node_output_bus_has_read(ma_node_output_bus* pOutputBus) +{ + return (c89atomic_load_32(&pOutputBus->flags) & MA_NODE_OUTPUT_BUS_FLAG_HAS_READ) != 0; +} + + +static void ma_node_output_bus_set_is_attached(ma_node_output_bus* pOutputBus, ma_bool32 isAttached) +{ + c89atomic_exchange_32(&pOutputBus->isAttached, isAttached); +} + +static ma_bool32 ma_node_output_bus_is_attached(ma_node_output_bus* pOutputBus) +{ + return c89atomic_load_32(&pOutputBus->isAttached); +} + + +static ma_result ma_node_output_bus_set_volume(ma_node_output_bus* pOutputBus, float volume) +{ + MA_ASSERT(pOutputBus != NULL); + + if (volume < 0.0f) { + volume = 0.0f; + } + + c89atomic_exchange_f32(&pOutputBus->volume, volume); + + return MA_SUCCESS; +} + +static float ma_node_output_bus_get_volume(const ma_node_output_bus* pOutputBus) +{ + return c89atomic_load_f32((float*)&pOutputBus->volume); +} + + +static ma_result ma_node_input_bus_init(ma_uint32 channels, ma_node_input_bus* pInputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(channels < 256); + + MA_ZERO_OBJECT(pInputBus); + + if (channels == 0) { + return MA_INVALID_ARGS; + } + + pInputBus->channels = (ma_uint8)channels; + + return MA_SUCCESS; +} + +static void ma_node_input_bus_lock(ma_node_input_bus* pInputBus) +{ + ma_spinlock_lock(&pInputBus->lock); +} + +static void ma_node_input_bus_unlock(ma_node_input_bus* pInputBus) +{ + ma_spinlock_unlock(&pInputBus->lock); +} + + +static void ma_node_input_bus_next_begin(ma_node_input_bus* pInputBus) +{ + c89atomic_fetch_add_32(&pInputBus->nextCounter, 1); +} + +static void ma_node_input_bus_next_end(ma_node_input_bus* pInputBus) +{ + c89atomic_fetch_sub_32(&pInputBus->nextCounter, 1); +} + +static ma_uint32 ma_node_input_bus_get_next_counter(ma_node_input_bus* pInputBus) +{ + return c89atomic_load_32(&pInputBus->nextCounter); +} + + +static ma_uint32 ma_node_input_bus_get_channels(const ma_node_input_bus* pInputBus) +{ + return pInputBus->channels; +} + + +static void ma_node_input_bus_detach__no_output_bus_lock(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + /* + Mark the output bus as detached first. This will prevent future iterations on the audio thread + from iterating this output bus. + */ + ma_node_output_bus_set_is_attached(pOutputBus, MA_FALSE); + + /* + We cannot use the output bus lock here since it'll be getting used at a higher level, but we do + still need to use the input bus lock since we'll be updating pointers on two different output + buses. The same rules apply here as the attaching case. Although we're using a lock here, we're + *not* using a lock when iterating over the list in the audio thread. We therefore need to craft + this in a way such that the iteration on the audio thread doesn't break. + + The the first thing to do is swap out the "next" pointer of the previous output bus with the + new "next" output bus. This is the operation that matters for iteration on the audio thread. + After that, the previous pointer on the new "next" pointer needs to be updated, after which + point the linked list will be in a good state. + */ + ma_node_input_bus_lock(pInputBus); + { + ma_node_output_bus* pOldPrev = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pPrev); + ma_node_output_bus* pOldNext = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pNext); + + if (pOldPrev != NULL) { + c89atomic_exchange_ptr(&pOldPrev->pNext, pOldNext); /* <-- This is where the output bus is detached from the list. */ + } + if (pOldNext != NULL) { + c89atomic_exchange_ptr(&pOldNext->pPrev, pOldPrev); /* <-- This is required for detachment. */ + } + } + ma_node_input_bus_unlock(pInputBus); + + /* At this point the output bus is detached and the linked list is completely unaware of it. Reset some data for safety. */ + c89atomic_exchange_ptr(&pOutputBus->pNext, NULL); /* Using atomic exchanges here, mainly for the benefit of analysis tools which don't always recognize spinlocks. */ + c89atomic_exchange_ptr(&pOutputBus->pPrev, NULL); /* As above. */ + pOutputBus->pInputNode = NULL; + pOutputBus->inputNodeInputBusIndex = 0; + + + /* + For thread-safety reasons, we don't want to be returning from this straight away. We need to + wait for the audio thread to finish with the output bus. There's two things we need to wait + for. The first is the part that selects the next output bus in the list, and the other is the + part that reads from the output bus. Basically all we're doing is waiting for the input bus + to stop referencing the output bus. + + We're doing this part last because we want the section above to run while the audio thread + is finishing up with the output bus, just for efficiency reasons. We marked the output bus as + detached right at the top of this function which is going to prevent the audio thread from + iterating the output bus again. + */ + + /* Part 1: Wait for the current iteration to complete. */ + while (ma_node_input_bus_get_next_counter(pInputBus) > 0) { + ma_yield(); + } + + /* Part 2: Wait for any reads to complete. */ + while (c89atomic_load_32(&pOutputBus->refCount) > 0) { + ma_yield(); + } + + /* + At this point we're done detaching and we can be guaranteed that the audio thread is not going + to attempt to reference this output bus again (until attached again). + */ +} + +#if 0 /* Not used at the moment, but leaving here in case I need it later. */ +static void ma_node_input_bus_detach(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + ma_node_output_bus_lock(pOutputBus); + { + ma_node_input_bus_detach__no_output_bus_lock(pInputBus, pOutputBus); + } + ma_node_output_bus_unlock(pOutputBus); +} +#endif + +static void ma_node_input_bus_attach(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus, ma_node* pNewInputNode, ma_uint32 inputNodeInputBusIndex) +{ + MA_ASSERT(pInputBus != NULL); + MA_ASSERT(pOutputBus != NULL); + + ma_node_output_bus_lock(pOutputBus); + { + ma_node_output_bus* pOldInputNode = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pInputNode); + + /* Detach from any existing attachment first if necessary. */ + if (pOldInputNode != NULL) { + ma_node_input_bus_detach__no_output_bus_lock(pInputBus, pOutputBus); + } + + /* + At this point we can be sure the output bus is not attached to anything. The linked list in the + old input bus has been updated so that pOutputBus will not get iterated again. + */ + pOutputBus->pInputNode = pNewInputNode; /* No need for an atomic assignment here because modification of this variable always happens within a lock. */ + pOutputBus->inputNodeInputBusIndex = (ma_uint8)inputNodeInputBusIndex; /* As above. */ + + /* + Now we need to attach the output bus to the linked list. This involves updating two pointers on + two different output buses so I'm going to go ahead and keep this simple and just use a lock. + There are ways to do this without a lock, but it's just too hard to maintain for it's value. + + Although we're locking here, it's important to remember that we're *not* locking when iterating + and reading audio data since that'll be running on the audio thread. As a result we need to be + careful how we craft this so that we don't break iteration. What we're going to do is always + attach the new item so that it becomes the first item in the list. That way, as we're iterating + we won't break any links in the list and iteration will continue safely. The detaching case will + also be crafted in a way as to not break list iteration. It's important to remember to use + atomic exchanges here since no locking is happening on the audio thread during iteration. + */ + ma_node_input_bus_lock(pInputBus); + { + ma_node_output_bus* pNewPrev = &pInputBus->head; + ma_node_output_bus* pNewNext = (ma_node_output_bus*)c89atomic_load_ptr(&pInputBus->head.pNext); + + /* Update the local output bus. */ + c89atomic_exchange_ptr(&pOutputBus->pPrev, pNewPrev); + c89atomic_exchange_ptr(&pOutputBus->pNext, pNewNext); + + /* Update the other output buses to point back to the local output bus. */ + c89atomic_exchange_ptr(&pInputBus->head.pNext, pOutputBus); /* <-- This is where the output bus is actually attached to the input bus. */ + + /* Do the previous pointer last. This is only used for detachment. */ + if (pNewNext != NULL) { + c89atomic_exchange_ptr(&pNewNext->pPrev, pOutputBus); + } + } + ma_node_input_bus_unlock(pInputBus); + + /* + Mark the node as attached last. This is used to controlling whether or the output bus will be + iterated on the audio thread. Mainly required for detachment purposes. + */ + ma_node_output_bus_set_is_attached(pOutputBus, MA_TRUE); + } + ma_node_output_bus_unlock(pOutputBus); +} + +static ma_node_output_bus* ma_node_input_bus_next(ma_node_input_bus* pInputBus, ma_node_output_bus* pOutputBus) +{ + ma_node_output_bus* pNext; + + MA_ASSERT(pInputBus != NULL); + + if (pOutputBus == NULL) { + return NULL; + } + + ma_node_input_bus_next_begin(pInputBus); + { + pNext = pOutputBus; + for (;;) { + pNext = (ma_node_output_bus*)c89atomic_load_ptr(&pNext->pNext); + if (pNext == NULL) { + break; /* Reached the end. */ + } + + if (ma_node_output_bus_is_attached(pNext) == MA_FALSE) { + continue; /* The node is not attached. Keep checking. */ + } + + /* The next node has been selected. */ + break; + } + + /* We need to increment the reference count of the selected node. */ + if (pNext != NULL) { + c89atomic_fetch_add_32(&pNext->refCount, 1); + } + + /* The previous node is no longer being referenced. */ + c89atomic_fetch_sub_32(&pOutputBus->refCount, 1); + } + ma_node_input_bus_next_end(pInputBus); + + return pNext; +} + +static ma_node_output_bus* ma_node_input_bus_first(ma_node_input_bus* pInputBus) +{ + return ma_node_input_bus_next(pInputBus, &pInputBus->head); +} + + + +static ma_result ma_node_input_bus_read_pcm_frames(ma_node* pInputNode, ma_node_input_bus* pInputBus, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime) +{ + ma_result result = MA_SUCCESS; + ma_node_output_bus* pOutputBus; + ma_node_output_bus* pFirst; + ma_uint32 inputChannels; + ma_bool32 doesOutputBufferHaveContent = MA_FALSE; + + /* + This will be called from the audio thread which means we can't be doing any locking. Basically, + this function will not perfom any locking, whereas attaching and detaching will, but crafted in + such a way that we don't need to perform any locking here. The important thing to remember is + to always iterate in a forward direction. + + In order to process any data we need to first read from all input buses. That's where this + function comes in. This iterates over each of the attachments and accumulates/mixes them. We + also convert the channels to the nodes output channel count before mixing. We want to do this + channel conversion so that the caller of this function can invoke the processing callback + without having to do it themselves. + + When we iterate over each of the attachments on the input bus, we need to read as much data as + we can from each of them so that we don't end up with holes between each of the attachments. To + do this, we need to read from each attachment in a loop and read as many frames as we can, up + to `frameCount`. + */ + MA_ASSERT(pInputNode != NULL); + MA_ASSERT(pFramesRead != NULL); /* pFramesRead is critical and must always be specified. On input it's undefined and on output it'll be set to the number of frames actually read. */ + + *pFramesRead = 0; /* Safety. */ + + inputChannels = ma_node_input_bus_get_channels(pInputBus); + + /* + We need to be careful with how we call ma_node_input_bus_first() and ma_node_input_bus_next(). They + are both critical to our lock-free thread-safety system. We can only call ma_node_input_bus_first() + once per iteration, however we have an optimization to checks whether or not it's the first item in + the list. We therefore need to store a pointer to the first item rather than repeatedly calling + ma_node_input_bus_first(). It's safe to keep hold of this pointer, so long as we don't dereference it + after calling ma_node_input_bus_next(), which we won't be. + */ + pFirst = ma_node_input_bus_first(pInputBus); + if (pFirst == NULL) { + return MA_SUCCESS; /* No attachments. Read nothing. */ + } + + for (pOutputBus = pFirst; pOutputBus != NULL; pOutputBus = ma_node_input_bus_next(pInputBus, pOutputBus)) { + ma_uint32 framesProcessed = 0; + ma_bool32 isSilentOutput = MA_FALSE; + + MA_ASSERT(pOutputBus->pNode != NULL); + + isSilentOutput = (((ma_node_base*)pOutputBus->pNode)->vtable->flags & MA_NODE_FLAG_SILENT_OUTPUT) != 0; + + if (pFramesOut != NULL) { + /* Read. */ + float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)]; + ma_uint32 tempCapInFrames = ma_countof(temp) / inputChannels; + + while (framesProcessed < frameCount) { + float* pRunningFramesOut; + ma_uint32 framesToRead; + ma_uint32 framesJustRead; + + framesToRead = frameCount - framesProcessed; + if (framesToRead > tempCapInFrames) { + framesToRead = tempCapInFrames; + } + + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(pFramesOut, framesProcessed, inputChannels); + + if (doesOutputBufferHaveContent == MA_FALSE) { + /* Fast path. First attachment. We just read straight into the output buffer (no mixing required). */ + result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, pRunningFramesOut, framesToRead, &framesJustRead, globalTime + framesProcessed); + } else { + /* Slow path. Not the first attachment. Mixing required. */ + result = ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, temp, framesToRead, &framesJustRead, globalTime + framesProcessed); + if (result == MA_SUCCESS || result == MA_AT_END) { + if (isSilentOutput == MA_FALSE) { /* Don't mix if the node outputs silence. */ + ma_mix_pcm_frames_f32(pRunningFramesOut, temp, framesJustRead, inputChannels, /*volume*/1); + } + } + } + + framesProcessed += framesJustRead; + + /* If we reached the end or otherwise failed to read any data we need to finish up with this output node. */ + if (result != MA_SUCCESS) { + break; + } + + /* If we didn't read anything, abort so we don't get stuck in a loop. */ + if (framesJustRead == 0) { + break; + } + } + + /* If it's the first attachment we didn't do any mixing. Any leftover samples need to be silenced. */ + if (pOutputBus == pFirst && framesProcessed < frameCount) { + ma_silence_pcm_frames(ma_offset_pcm_frames_ptr(pFramesOut, framesProcessed, ma_format_f32, inputChannels), (frameCount - framesProcessed), ma_format_f32, inputChannels); + } + + if (isSilentOutput == MA_FALSE) { + doesOutputBufferHaveContent = MA_TRUE; + } + } else { + /* Seek. */ + ma_node_read_pcm_frames(pOutputBus->pNode, pOutputBus->outputBusIndex, NULL, frameCount, &framesProcessed, globalTime); + } + } + + /* If we didn't output anything, output silence. */ + if (doesOutputBufferHaveContent == MA_FALSE && pFramesOut != NULL) { + ma_silence_pcm_frames(pFramesOut, frameCount, ma_format_f32, inputChannels); + } + + /* In this path we always "process" the entire amount. */ + *pFramesRead = frameCount; + + return result; +} + + +MA_API ma_node_config ma_node_config_init(void) +{ + ma_node_config config; + + MA_ZERO_OBJECT(&config); + config.initialState = ma_node_state_started; /* Nodes are started by default. */ + config.inputBusCount = MA_NODE_BUS_COUNT_UNKNOWN; + config.outputBusCount = MA_NODE_BUS_COUNT_UNKNOWN; + + return config; +} + + + +static ma_result ma_node_detach_full(ma_node* pNode); + +static float* ma_node_get_cached_input_ptr(ma_node* pNode, ma_uint32 inputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + float* pBasePtr; + + MA_ASSERT(pNodeBase != NULL); + + /* Input data is stored at the front of the buffer. */ + pBasePtr = pNodeBase->pCachedData; + for (iInputBus = 0; iInputBus < inputBusIndex; iInputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]); + } + + return pBasePtr; +} + +static float* ma_node_get_cached_output_ptr(ma_node* pNode, ma_uint32 outputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + float* pBasePtr; + + MA_ASSERT(pNodeBase != NULL); + + /* Cached output data starts after the input data. */ + pBasePtr = pNodeBase->pCachedData; + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iInputBus]); + } + + for (iOutputBus = 0; iOutputBus < outputBusIndex; iOutputBus += 1) { + pBasePtr += pNodeBase->cachedDataCapInFramesPerBus * ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iOutputBus]); + } + + return pBasePtr; +} + + +typedef struct +{ + size_t sizeInBytes; + size_t inputBusOffset; + size_t outputBusOffset; + size_t cachedDataOffset; + ma_uint32 inputBusCount; /* So it doesn't have to be calculated twice. */ + ma_uint32 outputBusCount; /* So it doesn't have to be calculated twice. */ +} ma_node_heap_layout; + +static ma_result ma_node_translate_bus_counts(const ma_node_config* pConfig, ma_uint32* pInputBusCount, ma_uint32* pOutputBusCount) +{ + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pInputBusCount != NULL); + MA_ASSERT(pOutputBusCount != NULL); + + /* Bus counts are determined by the vtable, unless they're set to `MA_NODE_BUS_COUNT_UNKNWON`, in which case they're taken from the config. */ + if (pConfig->vtable->inputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { + inputBusCount = pConfig->inputBusCount; + } else { + inputBusCount = pConfig->vtable->inputBusCount; + + if (pConfig->inputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->inputBusCount != pConfig->vtable->inputBusCount) { + return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */ + } + } + + if (pConfig->vtable->outputBusCount == MA_NODE_BUS_COUNT_UNKNOWN) { + outputBusCount = pConfig->outputBusCount; + } else { + outputBusCount = pConfig->vtable->outputBusCount; + + if (pConfig->outputBusCount != MA_NODE_BUS_COUNT_UNKNOWN && pConfig->outputBusCount != pConfig->vtable->outputBusCount) { + return MA_INVALID_ARGS; /* Invalid configuration. You must not specify a conflicting bus count between the node's config and the vtable. */ + } + } + + /* Bus counts must be within limits. */ + if (inputBusCount > MA_MAX_NODE_BUS_COUNT || outputBusCount > MA_MAX_NODE_BUS_COUNT) { + return MA_INVALID_ARGS; + } + + + /* We must have channel counts for each bus. */ + if ((inputBusCount > 0 && pConfig->pInputChannels == NULL) || (outputBusCount > 0 && pConfig->pOutputChannels == NULL)) { + return MA_INVALID_ARGS; /* You must specify channel counts for each input and output bus. */ + } + + + /* Some special rules for passthrough nodes. */ + if ((pConfig->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + if (pConfig->vtable->inputBusCount != 1 || pConfig->vtable->outputBusCount != 1) { + return MA_INVALID_ARGS; /* Passthrough nodes must have exactly 1 input bus and 1 output bus. */ + } + + if (pConfig->pInputChannels[0] != pConfig->pOutputChannels[0]) { + return MA_INVALID_ARGS; /* Passthrough nodes must have the same number of channels between input and output nodes. */ + } + } + + + *pInputBusCount = inputBusCount; + *pOutputBusCount = outputBusCount; + + return MA_SUCCESS; +} + +static ma_result ma_node_get_heap_layout(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, ma_node_heap_layout* pHeapLayout) +{ + ma_result result; + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + + MA_ASSERT(pHeapLayout != NULL); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL || pConfig->vtable == NULL || pConfig->vtable->onProcess == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_node_translate_bus_counts(pConfig, &inputBusCount, &outputBusCount); + if (result != MA_SUCCESS) { + return result; + } + + pHeapLayout->sizeInBytes = 0; + + /* Input buses. */ + if (inputBusCount > MA_MAX_NODE_LOCAL_BUS_COUNT) { + pHeapLayout->inputBusOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(ma_node_input_bus) * inputBusCount); + } else { + pHeapLayout->inputBusOffset = MA_SIZE_MAX; /* MA_SIZE_MAX indicates that no heap allocation is required for the input bus. */ + } + + /* Output buses. */ + if (outputBusCount > MA_MAX_NODE_LOCAL_BUS_COUNT) { + pHeapLayout->outputBusOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(sizeof(ma_node_output_bus) * outputBusCount); + } else { + pHeapLayout->outputBusOffset = MA_SIZE_MAX; + } + + /* + Cached audio data. + + We need to allocate memory for a caching both input and output data. We have an optimization + where no caching is necessary for specific conditions: + + - The node has 0 inputs and 1 output. + + When a node meets the above conditions, no cache is allocated. + + The size choice for this buffer is a little bit finicky. We don't want to be too wasteful by + allocating too much, but at the same time we want it be large enough so that enough frames can + be processed for each call to ma_node_read_pcm_frames() so that it keeps things efficient. For + now I'm going with 10ms @ 48K which is 480 frames per bus. This is configurable at compile + time. It might also be worth investigating whether or not this can be configured at run time. + */ + if (inputBusCount == 0 && outputBusCount == 1) { + /* Fast path. No cache needed. */ + pHeapLayout->cachedDataOffset = MA_SIZE_MAX; + } else { + /* Slow path. Cache needed. */ + size_t cachedDataSizeInBytes = 0; + ma_uint32 iBus; + + for (iBus = 0; iBus < inputBusCount; iBus += 1) { + cachedDataSizeInBytes += pNodeGraph->nodeCacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pInputChannels[iBus]); + } + + for (iBus = 0; iBus < outputBusCount; iBus += 1) { + cachedDataSizeInBytes += pNodeGraph->nodeCacheCapInFrames * ma_get_bytes_per_frame(ma_format_f32, pConfig->pOutputChannels[iBus]); + } + + pHeapLayout->cachedDataOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(cachedDataSizeInBytes); + } + + + /* + Not technically part of the heap, but we can output the input and output bus counts so we can + avoid a redundant call to ma_node_translate_bus_counts(). + */ + pHeapLayout->inputBusCount = inputBusCount; + pHeapLayout->outputBusCount = outputBusCount; + + /* Make sure allocation size is aligned. */ + pHeapLayout->sizeInBytes = ma_align_64(pHeapLayout->sizeInBytes); + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_get_heap_size(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_node_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_node_get_heap_layout(pNodeGraph, pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_init_preallocated(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, void* pHeap, ma_node* pNode) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_result result; + ma_node_heap_layout heapLayout; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNodeBase); + + result = ma_node_get_heap_layout(pNodeGraph, pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + pNodeBase->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pNodeBase->pNodeGraph = pNodeGraph; + pNodeBase->vtable = pConfig->vtable; + pNodeBase->state = pConfig->initialState; + pNodeBase->stateTimes[ma_node_state_started] = 0; + pNodeBase->stateTimes[ma_node_state_stopped] = (ma_uint64)(ma_int64)-1; /* Weird casting for VC6 compatibility. */ + pNodeBase->inputBusCount = heapLayout.inputBusCount; + pNodeBase->outputBusCount = heapLayout.outputBusCount; + + if (heapLayout.inputBusOffset != MA_SIZE_MAX) { + pNodeBase->pInputBuses = (ma_node_input_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset); + } else { + pNodeBase->pInputBuses = pNodeBase->_inputBuses; + } + + if (heapLayout.outputBusOffset != MA_SIZE_MAX) { + pNodeBase->pOutputBuses = (ma_node_output_bus*)ma_offset_ptr(pHeap, heapLayout.inputBusOffset); + } else { + pNodeBase->pOutputBuses = pNodeBase->_outputBuses; + } + + if (heapLayout.cachedDataOffset != MA_SIZE_MAX) { + pNodeBase->pCachedData = (float*)ma_offset_ptr(pHeap, heapLayout.cachedDataOffset); + pNodeBase->cachedDataCapInFramesPerBus = pNodeGraph->nodeCacheCapInFrames; + } else { + pNodeBase->pCachedData = NULL; + } + + + + /* We need to run an initialization step for each input and output bus. */ + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNodeBase); iInputBus += 1) { + result = ma_node_input_bus_init(pConfig->pInputChannels[iInputBus], &pNodeBase->pInputBuses[iInputBus]); + if (result != MA_SUCCESS) { + return result; + } + } + + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) { + result = ma_node_output_bus_init(pNodeBase, iOutputBus, pConfig->pOutputChannels[iOutputBus], &pNodeBase->pOutputBuses[iOutputBus]); + if (result != MA_SUCCESS) { + return result; + } + } + + + /* The cached data needs to be initialized to silence (or a sine wave tone if we're debugging). */ + if (pNodeBase->pCachedData != NULL) { + ma_uint32 iBus; + + #if 1 /* Toggle this between 0 and 1 to turn debugging on or off. 1 = fill with a sine wave for debugging; 0 = fill with silence. */ + /* For safety we'll go ahead and default the buffer to silence. */ + for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { + ma_silence_pcm_frames(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus])); + } + for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { + ma_silence_pcm_frames(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus])); + } + #else + /* For debugging. Default to a sine wave. */ + for (iBus = 0; iBus < ma_node_get_input_bus_count(pNodeBase); iBus += 1) { + ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_input_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[iBus]), 48000); + } + for (iBus = 0; iBus < ma_node_get_output_bus_count(pNodeBase); iBus += 1) { + ma_debug_fill_pcm_frames_with_sine_wave(ma_node_get_cached_output_ptr(pNode, iBus), pNodeBase->cachedDataCapInFramesPerBus, ma_format_f32, ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[iBus]), 48000); + } + #endif + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_init(ma_node_graph* pNodeGraph, const ma_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_node* pNode) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_node_get_heap_size(pNodeGraph, pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_node_init_preallocated(pNodeGraph, pConfig, pHeap, pNode); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + ((ma_node_base*)pNode)->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_node_uninit(ma_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return; + } + + /* + The first thing we need to do is fully detach the node. This will detach all inputs and + outputs. We need to do this first because it will sever the connection with the node graph and + allow us to complete uninitialization without needing to worry about thread-safety with the + audio thread. The detachment process will wait for any local processing of the node to finish. + */ + ma_node_detach_full(pNode); + + /* + At this point the node should be completely unreferenced by the node graph and we can finish up + the uninitialization process without needing to worry about thread-safety. + */ + if (pNodeBase->_ownsHeap) { + ma_free(pNodeBase->_pHeap, pAllocationCallbacks); + } +} + +MA_API ma_node_graph* ma_node_get_node_graph(const ma_node* pNode) +{ + if (pNode == NULL) { + return NULL; + } + + return ((const ma_node_base*)pNode)->pNodeGraph; +} + +MA_API ma_uint32 ma_node_get_input_bus_count(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return ((ma_node_base*)pNode)->inputBusCount; +} + +MA_API ma_uint32 ma_node_get_output_bus_count(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return ((ma_node_base*)pNode)->outputBusCount; +} + + +MA_API ma_uint32 ma_node_get_input_channels(const ma_node* pNode, ma_uint32 inputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNode == NULL) { + return 0; + } + + if (inputBusIndex >= ma_node_get_input_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_input_bus_get_channels(&pNodeBase->pInputBuses[inputBusIndex]); +} + +MA_API ma_uint32 ma_node_get_output_channels(const ma_node* pNode, ma_uint32 outputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNode == NULL) { + return 0; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_output_bus_get_channels(&pNodeBase->pOutputBuses[outputBusIndex]); +} + + +static ma_result ma_node_detach_full(ma_node* pNode) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iInputBus; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + /* + Make sure the node is completely detached first. This will not return until the output bus is + guaranteed to no longer be referenced by the audio thread. + */ + ma_node_detach_all_output_buses(pNode); + + /* + At this point all output buses will have been detached from the graph and we can be guaranteed + that none of it's input nodes will be getting processed by the graph. We can detach these + without needing to worry about the audio thread touching them. + */ + for (iInputBus = 0; iInputBus < ma_node_get_input_bus_count(pNode); iInputBus += 1) { + ma_node_input_bus* pInputBus; + ma_node_output_bus* pOutputBus; + + pInputBus = &pNodeBase->pInputBuses[iInputBus]; + + /* + This is important. We cannot be using ma_node_input_bus_first() or ma_node_input_bus_next(). Those + functions are specifically for the audio thread. We'll instead just manually iterate using standard + linked list logic. We don't need to worry about the audio thread referencing these because the step + above severed the connection to the graph. + */ + for (pOutputBus = (ma_node_output_bus*)c89atomic_load_ptr(&pInputBus->head.pNext); pOutputBus != NULL; pOutputBus = (ma_node_output_bus*)c89atomic_load_ptr(&pOutputBus->pNext)) { + ma_node_detach_output_bus(pOutputBus->pNode, pOutputBus->outputBusIndex); /* This won't do any waiting in practice and should be efficient. */ + } + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_detach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex) +{ + ma_result result = MA_SUCCESS; + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_node_base* pInputNodeBase; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid output bus index. */ + } + + /* We need to lock the output bus because we need to inspect the input node and grab it's input bus. */ + ma_node_output_bus_lock(&pNodeBase->pOutputBuses[outputBusIndex]); + { + pInputNodeBase = (ma_node_base*)pNodeBase->pOutputBuses[outputBusIndex].pInputNode; + if (pInputNodeBase != NULL) { + ma_node_input_bus_detach__no_output_bus_lock(&pInputNodeBase->pInputBuses[pNodeBase->pOutputBuses[outputBusIndex].inputNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex]); + } + } + ma_node_output_bus_unlock(&pNodeBase->pOutputBuses[outputBusIndex]); + + return result; +} + +MA_API ma_result ma_node_detach_all_output_buses(ma_node* pNode) +{ + ma_uint32 iOutputBus; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNode); iOutputBus += 1) { + ma_node_detach_output_bus(pNode, iOutputBus); + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_attach_output_bus(ma_node* pNode, ma_uint32 outputBusIndex, ma_node* pOtherNode, ma_uint32 otherNodeInputBusIndex) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_node_base* pOtherNodeBase = (ma_node_base*)pOtherNode; + + if (pNodeBase == NULL || pOtherNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (pNodeBase == pOtherNodeBase) { + return MA_INVALID_OPERATION; /* Cannot attach a node to itself. */ + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode) || otherNodeInputBusIndex >= ma_node_get_input_bus_count(pOtherNode)) { + return MA_INVALID_OPERATION; /* Invalid bus index. */ + } + + /* The output channel count of the output node must be the same as the input channel count of the input node. */ + if (ma_node_get_output_channels(pNode, outputBusIndex) != ma_node_get_input_channels(pOtherNode, otherNodeInputBusIndex)) { + return MA_INVALID_OPERATION; /* Channel count is incompatible. */ + } + + /* This will deal with detaching if the output bus is already attached to something. */ + ma_node_input_bus_attach(&pOtherNodeBase->pInputBuses[otherNodeInputBusIndex], &pNodeBase->pOutputBuses[outputBusIndex], pOtherNode, otherNodeInputBusIndex); + + return MA_SUCCESS; +} + +MA_API ma_result ma_node_set_output_bus_volume(ma_node* pNode, ma_uint32 outputBusIndex, float volume) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return MA_INVALID_ARGS; /* Invalid bus index. */ + } + + return ma_node_output_bus_set_volume(&pNodeBase->pOutputBuses[outputBusIndex], volume); +} + +MA_API float ma_node_get_output_bus_volume(const ma_node* pNode, ma_uint32 outputBusIndex) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return 0; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNode)) { + return 0; /* Invalid bus index. */ + } + + return ma_node_output_bus_get_volume(&pNodeBase->pOutputBuses[outputBusIndex]); +} + +MA_API ma_result ma_node_set_state(ma_node* pNode, ma_node_state state) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_i32(&pNodeBase->state, state); + + return MA_SUCCESS; +} + +MA_API ma_node_state ma_node_get_state(const ma_node* pNode) +{ + const ma_node_base* pNodeBase = (const ma_node_base*)pNode; + + if (pNodeBase == NULL) { + return ma_node_state_stopped; + } + + return (ma_node_state)c89atomic_load_i32(&pNodeBase->state); +} + +MA_API ma_result ma_node_set_state_time(ma_node* pNode, ma_node_state state, ma_uint64 globalTime) +{ + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + /* Validation check for safety since we'll be using this as an index into stateTimes[]. */ + if (state != ma_node_state_started && state != ma_node_state_stopped) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_64(&((ma_node_base*)pNode)->stateTimes[state], globalTime); + + return MA_SUCCESS; +} + +MA_API ma_uint64 ma_node_get_state_time(const ma_node* pNode, ma_node_state state) +{ + if (pNode == NULL) { + return 0; + } + + /* Validation check for safety since we'll be using this as an index into stateTimes[]. */ + if (state != ma_node_state_started && state != ma_node_state_stopped) { + return 0; + } + + return c89atomic_load_64(&((ma_node_base*)pNode)->stateTimes[state]); +} + +MA_API ma_node_state ma_node_get_state_by_time(const ma_node* pNode, ma_uint64 globalTime) +{ + if (pNode == NULL) { + return ma_node_state_stopped; + } + + return ma_node_get_state_by_time_range(pNode, globalTime, globalTime); +} + +MA_API ma_node_state ma_node_get_state_by_time_range(const ma_node* pNode, ma_uint64 globalTimeBeg, ma_uint64 globalTimeEnd) +{ + ma_node_state state; + + if (pNode == NULL) { + return ma_node_state_stopped; + } + + state = ma_node_get_state(pNode); + + /* An explicitly stopped node is always stopped. */ + if (state == ma_node_state_stopped) { + return ma_node_state_stopped; + } + + /* + Getting here means the node is marked as started, but it may still not be truly started due to + it's start time not having been reached yet. Also, the stop time may have also been reached in + which case it'll be considered stopped. + */ + if (ma_node_get_state_time(pNode, ma_node_state_started) > globalTimeBeg) { + return ma_node_state_stopped; /* Start time has not yet been reached. */ + } + + if (ma_node_get_state_time(pNode, ma_node_state_stopped) <= globalTimeEnd) { + return ma_node_state_stopped; /* Stop time has been reached. */ + } + + /* Getting here means the node is marked as started and is within it's start/stop times. */ + return ma_node_state_started; +} + +MA_API ma_uint64 ma_node_get_time(const ma_node* pNode) +{ + if (pNode == NULL) { + return 0; + } + + return c89atomic_load_64(&((ma_node_base*)pNode)->localTime); +} + +MA_API ma_result ma_node_set_time(ma_node* pNode, ma_uint64 localTime) +{ + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + c89atomic_exchange_64(&((ma_node_base*)pNode)->localTime, localTime); + + return MA_SUCCESS; +} + + + +static void ma_node_process_pcm_frames_internal(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + + MA_ASSERT(pNode != NULL); + + if (pNodeBase->vtable->onProcess) { + pNodeBase->vtable->onProcess(pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); + } +} + +static ma_result ma_node_read_pcm_frames(ma_node* pNode, ma_uint32 outputBusIndex, float* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead, ma_uint64 globalTime) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_result result = MA_SUCCESS; + ma_uint32 iInputBus; + ma_uint32 iOutputBus; + ma_uint32 inputBusCount; + ma_uint32 outputBusCount; + ma_uint32 totalFramesRead = 0; + float* ppFramesIn[MA_MAX_NODE_BUS_COUNT]; + float* ppFramesOut[MA_MAX_NODE_BUS_COUNT]; + ma_uint64 globalTimeBeg; + ma_uint64 globalTimeEnd; + ma_uint64 startTime; + ma_uint64 stopTime; + ma_uint32 timeOffsetBeg; + ma_uint32 timeOffsetEnd; + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + + /* + pFramesRead is mandatory. It must be used to determine how many frames were read. It's normal and + expected that the number of frames read may be different to that requested. Therefore, the caller + must look at this value to correctly determine how many frames were read. + */ + MA_ASSERT(pFramesRead != NULL); /* <-- If you've triggered this assert, you're using this function wrong. You *must* use this variable and inspect it after the call returns. */ + if (pFramesRead == NULL) { + return MA_INVALID_ARGS; + } + + *pFramesRead = 0; /* Safety. */ + + if (pNodeBase == NULL) { + return MA_INVALID_ARGS; + } + + if (outputBusIndex >= ma_node_get_output_bus_count(pNodeBase)) { + return MA_INVALID_ARGS; /* Invalid output bus index. */ + } + + /* Don't do anything if we're in a stopped state. */ + if (ma_node_get_state_by_time_range(pNode, globalTime, globalTime + frameCount) != ma_node_state_started) { + return MA_SUCCESS; /* We're in a stopped state. This is not an error - we just need to not read anything. */ + } + + + globalTimeBeg = globalTime; + globalTimeEnd = globalTime + frameCount; + startTime = ma_node_get_state_time(pNode, ma_node_state_started); + stopTime = ma_node_get_state_time(pNode, ma_node_state_stopped); + + /* + At this point we know that we are inside our start/stop times. However, we may need to adjust + our frame count and output pointer to accomodate since we could be straddling the time period + that this function is getting called for. + + It's possible (and likely) that the start time does not line up with the output buffer. We + therefore need to offset it by a number of frames to accomodate. The same thing applies for + the stop time. + */ + timeOffsetBeg = (globalTimeBeg < startTime) ? (ma_uint32)(globalTimeEnd - startTime) : 0; + timeOffsetEnd = (globalTimeEnd > stopTime) ? (ma_uint32)(globalTimeEnd - stopTime) : 0; + + /* Trim based on the start offset. We need to silence the start of the buffer. */ + if (timeOffsetBeg > 0) { + ma_silence_pcm_frames(pFramesOut, timeOffsetBeg, ma_format_f32, ma_node_get_output_channels(pNode, outputBusIndex)); + pFramesOut += timeOffsetBeg * ma_node_get_output_channels(pNode, outputBusIndex); + frameCount -= timeOffsetBeg; + } + + /* Trim based on the end offset. We don't need to silence the tail section because we'll just have a reduced value written to pFramesRead. */ + if (timeOffsetEnd > 0) { + frameCount -= timeOffsetEnd; + } + + + /* We run on different paths depending on the bus counts. */ + inputBusCount = ma_node_get_input_bus_count(pNode); + outputBusCount = ma_node_get_output_bus_count(pNode); + + /* + Run a simplified path when there are no inputs and one output. In this case there's nothing to + actually read and we can go straight to output. This is a very common scenario because the vast + majority of data source nodes will use this setup so this optimization I think is worthwhile. + */ + if (inputBusCount == 0 && outputBusCount == 1) { + /* Fast path. No need to read from input and no need for any caching. */ + frameCountIn = 0; + frameCountOut = frameCount; /* Just read as much as we can. The callback will return what was actually read. */ + + ppFramesOut[0] = pFramesOut; + ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut); + totalFramesRead = frameCountOut; + } else { + /* Slow path. Need to read input data. */ + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_PASSTHROUGH) != 0) { + /* + Fast path. We're running a passthrough. We need to read directly into the output buffer, but + still fire the callback so that event handling and trigger nodes can do their thing. Since + it's a passthrough there's no need for any kind of caching logic. + */ + MA_ASSERT(outputBusCount == inputBusCount); + MA_ASSERT(outputBusCount == 1); + MA_ASSERT(outputBusIndex == 0); + + /* We just read directly from input bus to output buffer, and then afterwards fire the callback. */ + ppFramesOut[0] = pFramesOut; + ppFramesIn[0] = ppFramesOut[0]; + + result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[0], ppFramesIn[0], frameCount, &totalFramesRead, globalTime); + if (result == MA_SUCCESS) { + /* Even though it's a passthrough, we still need to fire the callback. */ + frameCountIn = totalFramesRead; + frameCountOut = totalFramesRead; + + if (totalFramesRead > 0) { + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + } + + /* + A passthrough should never have modified the input and output frame counts. If you're + triggering these assers you need to fix your processing callback. + */ + MA_ASSERT(frameCountIn == totalFramesRead); + MA_ASSERT(frameCountOut == totalFramesRead); + } + } else { + /* Slow path. Need to do caching. */ + ma_uint32 framesToProcessIn; + ma_uint32 framesToProcessOut; + ma_bool32 consumeNullInput = MA_FALSE; + + /* + We use frameCount as a basis for the number of frames to read since that's what's being + requested, however we still need to clamp it to whatever can fit in the cache. + + This will also be used as the basis for determining how many input frames to read. This is + not ideal because it can result in too many input frames being read which introduces latency. + To solve this, nodes can implement an optional callback called onGetRequiredInputFrameCount + which is used as hint to miniaudio as to how many input frames it needs to read at a time. This + callback is completely optional, and if it's not set, miniaudio will assume `frameCount`. + + This function will be called multiple times for each period of time, once for each output node. + We cannot read from each input node each time this function is called. Instead we need to check + whether or not this is first output bus to be read from for this time period, and if so, read + from our input data. + + To determine whether or not we're ready to read data, we check a flag. There will be one flag + for each output. When the flag is set, it means data has been read previously and that we're + ready to advance time forward for our input nodes by reading fresh data. + */ + framesToProcessOut = frameCount; + if (framesToProcessOut > pNodeBase->cachedDataCapInFramesPerBus) { + framesToProcessOut = pNodeBase->cachedDataCapInFramesPerBus; + } + + framesToProcessIn = frameCount; + if (pNodeBase->vtable->onGetRequiredInputFrameCount) { + pNodeBase->vtable->onGetRequiredInputFrameCount(pNode, framesToProcessOut, &framesToProcessIn); /* <-- It does not matter if this fails. */ + } + if (framesToProcessIn > pNodeBase->cachedDataCapInFramesPerBus) { + framesToProcessIn = pNodeBase->cachedDataCapInFramesPerBus; + } + + + MA_ASSERT(framesToProcessIn <= 0xFFFF); + MA_ASSERT(framesToProcessOut <= 0xFFFF); + + if (ma_node_output_bus_has_read(&pNodeBase->pOutputBuses[outputBusIndex])) { + /* Getting here means we need to do another round of processing. */ + pNodeBase->cachedFrameCountOut = 0; + + for (;;) { + frameCountOut = 0; + + /* + We need to prepare our output frame pointers for processing. In the same iteration we need + to mark every output bus as unread so that future calls to this function for different buses + for the current time period don't pull in data when they should instead be reading from cache. + */ + for (iOutputBus = 0; iOutputBus < outputBusCount; iOutputBus += 1) { + ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[iOutputBus], MA_FALSE); /* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */ + ppFramesOut[iOutputBus] = ma_node_get_cached_output_ptr(pNode, iOutputBus); + } + + /* We only need to read from input buses if there isn't already some data in the cache. */ + if (pNodeBase->cachedFrameCountIn == 0) { + ma_uint32 maxFramesReadIn = 0; + + /* Here is where we pull in data from the input buses. This is what will trigger an advance in time. */ + for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) { + ma_uint32 framesRead; + + /* The first thing to do is get the offset within our bulk allocation to store this input data. */ + ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus); + + /* Once we've determined our destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */ + result = ma_node_input_bus_read_pcm_frames(pNodeBase, &pNodeBase->pInputBuses[iInputBus], ppFramesIn[iInputBus], framesToProcessIn, &framesRead, globalTime); + if (result != MA_SUCCESS) { + /* It doesn't really matter if we fail because we'll just fill with silence. */ + framesRead = 0; /* Just for safety, but I don't think it's really needed. */ + } + + /* TODO: Minor optimization opportunity here. If no frames were read and the buffer is already filled with silence, no need to re-silence it. */ + /* Any leftover frames need to silenced for safety. */ + if (framesRead < framesToProcessIn) { + ma_silence_pcm_frames(ppFramesIn[iInputBus] + (framesRead * ma_node_get_input_channels(pNodeBase, iInputBus)), (framesToProcessIn - framesRead), ma_format_f32, ma_node_get_input_channels(pNodeBase, iInputBus)); + } + + maxFramesReadIn = ma_max(maxFramesReadIn, framesRead); + } + + /* This was a fresh load of input data so reset our consumption counter. */ + pNodeBase->consumedFrameCountIn = 0; + + /* + We don't want to keep processing if there's nothing to process, so set the number of cached + input frames to the maximum number we read from each attachment (the lesser will be padded + with silence). If we didn't read anything, this will be set to 0 and the entire buffer will + have been assigned to silence. This being equal to 0 is an important property for us because + it allows us to detect when NULL can be passed into the processing callback for the input + buffer for the purpose of continuous processing. + */ + pNodeBase->cachedFrameCountIn = (ma_uint16)maxFramesReadIn; + } else { + /* We don't need to read anything, but we do need to prepare our input frame pointers. */ + for (iInputBus = 0; iInputBus < inputBusCount; iInputBus += 1) { + ppFramesIn[iInputBus] = ma_node_get_cached_input_ptr(pNode, iInputBus) + (pNodeBase->consumedFrameCountIn * ma_node_get_input_channels(pNodeBase, iInputBus)); + } + } + + /* + At this point we have our input data so now we need to do some processing. Sneaky little + optimization here - we can set the pointer to the output buffer for this output bus so + that the final copy into the output buffer is done directly by onProcess(). + */ + if (pFramesOut != NULL) { + ppFramesOut[outputBusIndex] = ma_offset_pcm_frames_ptr_f32(pFramesOut, pNodeBase->cachedFrameCountOut, ma_node_get_output_channels(pNode, outputBusIndex)); + } + + + /* Give the processing function the entire capacity of the output buffer. */ + frameCountOut = (framesToProcessOut - pNodeBase->cachedFrameCountOut); + + /* + We need to treat nodes with continuous processing a little differently. For these ones, + we always want to fire the callback with the requested number of frames, regardless of + pNodeBase->cachedFrameCountIn, which could be 0. Also, we want to check if we can pass + in NULL for the input buffer to the callback. + */ + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_CONTINUOUS_PROCESSING) != 0) { + /* We're using continuous processing. Make sure we specify the whole frame count at all times. */ + frameCountIn = framesToProcessIn; /* Give the processing function as much input data as we've got in the buffer, including any silenced padding from short reads. */ + + if ((pNodeBase->vtable->flags & MA_NODE_FLAG_ALLOW_NULL_INPUT) != 0 && pNodeBase->consumedFrameCountIn == 0 && pNodeBase->cachedFrameCountIn == 0) { + consumeNullInput = MA_TRUE; + } else { + consumeNullInput = MA_FALSE; + } + + /* + Since we're using continuous processing we're always passing in a full frame count + regardless of how much input data was read. If this is greater than what we read as + input, we'll end up with an underflow. We instead need to make sure our cached frame + count is set to the number of frames we'll be passing to the data callback. Not + doing this will result in an underflow when we "consume" the cached data later on. + + Note that this check needs to be done after the "consumeNullInput" check above because + we use the property of cachedFrameCountIn being 0 to determine whether or not we + should be passing in a null pointer to the processing callback for when the node is + configured with MA_NODE_FLAG_ALLOW_NULL_INPUT. + */ + if (pNodeBase->cachedFrameCountIn < (ma_uint16)frameCountIn) { + pNodeBase->cachedFrameCountIn = (ma_uint16)frameCountIn; + } + } else { + frameCountIn = pNodeBase->cachedFrameCountIn; /* Give the processing function as much valid input data as we've got. */ + consumeNullInput = MA_FALSE; + } + + /* + Process data slightly differently depending on whether or not we're consuming NULL + input (checked just above). + */ + if (consumeNullInput) { + ma_node_process_pcm_frames_internal(pNode, NULL, &frameCountIn, ppFramesOut, &frameCountOut); + } else { + /* + We want to skip processing if there's no input data, but we can only do that safely if + we know that there is no chance of any output frames being produced. If continuous + processing is being used, this won't be a problem because the input frame count will + always be non-0. However, if continuous processing is *not* enabled and input and output + data is processed at different rates, we still need to process that last input frame + because there could be a few excess output frames needing to be produced from cached + data. The `MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES` flag is used as the indicator for + determining whether or not we need to process the node even when there are no input + frames available right now. + */ + if (frameCountIn > 0 || (pNodeBase->vtable->flags & MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES) != 0) { + ma_node_process_pcm_frames_internal(pNode, (const float**)ppFramesIn, &frameCountIn, ppFramesOut, &frameCountOut); /* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */ + } else { + frameCountOut = 0; /* No data was processed. */ + } + } + + /* + Thanks to our sneaky optimization above we don't need to do any data copying directly into + the output buffer - the onProcess() callback just did that for us. We do, however, need to + apply the number of input and output frames that were processed. Note that due to continuous + processing above, we need to do explicit checks here. If we just consumed a NULL input + buffer it means that no actual input data was processed from the internal buffers and we + don't want to be modifying any counters. + */ + if (consumeNullInput == MA_FALSE) { + pNodeBase->consumedFrameCountIn += (ma_uint16)frameCountIn; + pNodeBase->cachedFrameCountIn -= (ma_uint16)frameCountIn; + } + + /* The cached output frame count is always equal to what we just read. */ + pNodeBase->cachedFrameCountOut += (ma_uint16)frameCountOut; + + /* If we couldn't process any data, we're done. The loop needs to be terminated here or else we'll get stuck in a loop. */ + if (pNodeBase->cachedFrameCountOut == framesToProcessOut || (frameCountOut == 0 && frameCountIn == 0)) { + break; + } + } + } else { + /* + We're not needing to read anything from the input buffer so just read directly from our + already-processed data. + */ + if (pFramesOut != NULL) { + ma_copy_pcm_frames(pFramesOut, ma_node_get_cached_output_ptr(pNodeBase, outputBusIndex), pNodeBase->cachedFrameCountOut, ma_format_f32, ma_node_get_output_channels(pNodeBase, outputBusIndex)); + } + } + + /* The number of frames read is always equal to the number of cached output frames. */ + totalFramesRead = pNodeBase->cachedFrameCountOut; + + /* Now that we've read the data, make sure our read flag is set. */ + ma_node_output_bus_set_has_read(&pNodeBase->pOutputBuses[outputBusIndex], MA_TRUE); + } + } + + /* Apply volume, if necessary. */ + ma_apply_volume_factor_f32(pFramesOut, totalFramesRead * ma_node_get_output_channels(pNodeBase, outputBusIndex), ma_node_output_bus_get_volume(&pNodeBase->pOutputBuses[outputBusIndex])); + + /* Advance our local time forward. */ + c89atomic_fetch_add_64(&pNodeBase->localTime, (ma_uint64)totalFramesRead); + + *pFramesRead = totalFramesRead + timeOffsetBeg; /* Must include the silenced section at the start of the buffer. */ + return result; +} + + + + +/* Data source node. */ +MA_API ma_data_source_node_config ma_data_source_node_config_init(ma_data_source* pDataSource) +{ + ma_data_source_node_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_node_config_init(); + config.pDataSource = pDataSource; + + return config; +} + + +static void ma_data_source_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_data_source_node* pDataSourceNode = (ma_data_source_node*)pNode; + ma_format format; + ma_uint32 channels; + ma_uint32 frameCount; + ma_uint64 framesRead = 0; + + MA_ASSERT(pDataSourceNode != NULL); + MA_ASSERT(pDataSourceNode->pDataSource != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pDataSourceNode) == 0); + MA_ASSERT(ma_node_get_output_bus_count(pDataSourceNode) == 1); + + /* We don't want to read from ppFramesIn at all. Instead we read from the data source. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + frameCount = *pFrameCountOut; + + /* miniaudio should never be calling this with a frame count of zero. */ + MA_ASSERT(frameCount > 0); + + if (ma_data_source_get_data_format(pDataSourceNode->pDataSource, &format, &channels, NULL, NULL, 0) == MA_SUCCESS) { /* <-- Don't care about sample rate here. */ + /* The node graph system requires samples be in floating point format. This is checked in ma_data_source_node_init(). */ + MA_ASSERT(format == ma_format_f32); + (void)format; /* Just to silence some static analysis tools. */ + + ma_data_source_read_pcm_frames(pDataSourceNode->pDataSource, ppFramesOut[0], frameCount, &framesRead); + } + + *pFrameCountOut = (ma_uint32)framesRead; +} + +static ma_node_vtable g_ma_data_source_node_vtable = +{ + ma_data_source_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 0, /* 0 input buses. */ + 1, /* 1 output bus. */ + 0 +}; + +MA_API ma_result ma_data_source_node_init(ma_node_graph* pNodeGraph, const ma_data_source_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_data_source_node* pDataSourceNode) +{ + ma_result result; + ma_format format; /* For validating the format, which must be ma_format_f32. */ + ma_uint32 channels; /* For specifying the channel count of the output bus. */ + ma_node_config baseConfig; + + if (pDataSourceNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDataSourceNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + result = ma_data_source_get_data_format(pConfig->pDataSource, &format, &channels, NULL, NULL, 0); /* Don't care about sample rate. This will check pDataSource for NULL. */ + if (result != MA_SUCCESS) { + return result; + } + + MA_ASSERT(format == ma_format_f32); /* <-- If you've triggered this it means your data source is not outputting floating-point samples. You must configure your data source to use ma_format_f32. */ + if (format != ma_format_f32) { + return MA_INVALID_ARGS; /* Invalid format. */ + } + + /* The channel count is defined by the data source. If the caller has manually changed the channels we just ignore it. */ + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_data_source_node_vtable; /* Explicitly set the vtable here to prevent callers from setting it incorrectly. */ + + /* + The channel count is defined by the data source. It is invalid for the caller to manually set + the channel counts in the config. `ma_data_source_node_config_init()` will have defaulted the + channel count pointer to NULL which is how it must remain. If you trigger any of these asserts + it means you're explicitly setting the channel count. Instead, configure the output channel + count of your data source to be the necessary channel count. + */ + if (baseConfig.pOutputChannels != NULL) { + return MA_INVALID_ARGS; + } + + baseConfig.pOutputChannels = &channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDataSourceNode->base); + if (result != MA_SUCCESS) { + return result; + } + + pDataSourceNode->pDataSource = pConfig->pDataSource; + + return MA_SUCCESS; +} + +MA_API void ma_data_source_node_uninit(ma_data_source_node* pDataSourceNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_uninit(&pDataSourceNode->base, pAllocationCallbacks); +} + +MA_API ma_result ma_data_source_node_set_looping(ma_data_source_node* pDataSourceNode, ma_bool32 isLooping) +{ + if (pDataSourceNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_data_source_set_looping(pDataSourceNode->pDataSource, isLooping); +} + +MA_API ma_bool32 ma_data_source_node_is_looping(ma_data_source_node* pDataSourceNode) +{ + if (pDataSourceNode == NULL) { + return MA_FALSE; + } + + return ma_data_source_is_looping(pDataSourceNode->pDataSource); +} + + + +/* Splitter Node. */ +MA_API ma_splitter_node_config ma_splitter_node_config_init(ma_uint32 channels) +{ + ma_splitter_node_config config; + + MA_ZERO_OBJECT(&config); + config.nodeConfig = ma_node_config_init(); + config.channels = channels; + + return config; +} + + +static void ma_splitter_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_node_base* pNodeBase = (ma_node_base*)pNode; + ma_uint32 iOutputBus; + ma_uint32 channels; + + MA_ASSERT(pNodeBase != NULL); + MA_ASSERT(ma_node_get_input_bus_count(pNodeBase) == 1); + MA_ASSERT(ma_node_get_output_bus_count(pNodeBase) >= 2); + + /* We don't need to consider the input frame count - it'll be the same as the output frame count and we process everything. */ + (void)pFrameCountIn; + + /* NOTE: This assumes the same number of channels for all inputs and outputs. This was checked in ma_splitter_node_init(). */ + channels = ma_node_get_input_channels(pNodeBase, 0); + + /* Splitting is just copying the first input bus and copying it over to each output bus. */ + for (iOutputBus = 0; iOutputBus < ma_node_get_output_bus_count(pNodeBase); iOutputBus += 1) { + ma_copy_pcm_frames(ppFramesOut[iOutputBus], ppFramesIn[0], *pFrameCountOut, ma_format_f32, channels); + } +} + +static ma_node_vtable g_ma_splitter_node_vtable = +{ + ma_splitter_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* 1 input bus. */ + 2, /* 2 output buses. */ + 0 +}; + +MA_API ma_result ma_splitter_node_init(ma_node_graph* pNodeGraph, const ma_splitter_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_splitter_node* pSplitterNode) +{ + ma_result result; + ma_node_config baseConfig; + ma_uint32 pInputChannels[1]; + ma_uint32 pOutputChannels[2]; + + if (pSplitterNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSplitterNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* Splitters require the same number of channels between inputs and outputs. */ + pInputChannels[0] = pConfig->channels; + pOutputChannels[0] = pConfig->channels; + pOutputChannels[1] = pConfig->channels; + + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_splitter_node_vtable; + baseConfig.pInputChannels = pInputChannels; + baseConfig.pOutputChannels = pOutputChannels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pSplitterNode->base); + if (result != MA_SUCCESS) { + return result; /* Failed to initialize the base node. */ + } + + return MA_SUCCESS; +} + +MA_API void ma_splitter_node_uninit(ma_splitter_node* pSplitterNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_node_uninit(pSplitterNode, pAllocationCallbacks); +} + + +/* +Biquad Node +*/ +MA_API ma_biquad_node_config ma_biquad_node_config_init(ma_uint32 channels, float b0, float b1, float b2, float a0, float a1, float a2) +{ + ma_biquad_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.biquad = ma_biquad_config_init(ma_format_f32, channels, b0, b1, b2, a0, a1, a2); + + return config; +} + +static void ma_biquad_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_biquad_process_pcm_frames(&pLPFNode->biquad, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_biquad_node_vtable = +{ + ma_biquad_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_biquad_node_init(ma_node_graph* pNodeGraph, const ma_biquad_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_biquad_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->biquad.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_biquad_init(&pConfig->biquad, pAllocationCallbacks, &pNode->biquad); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_biquad_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->biquad.channels; + baseNodeConfig.pOutputChannels = &pConfig->biquad.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_biquad_node_reinit(const ma_biquad_config* pConfig, ma_biquad_node* pNode) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + MA_ASSERT(pNode != NULL); + + return ma_biquad_reinit(pConfig, &pLPFNode->biquad); +} + +MA_API void ma_biquad_node_uninit(ma_biquad_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_biquad_node* pLPFNode = (ma_biquad_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_biquad_uninit(&pLPFNode->biquad, pAllocationCallbacks); +} + + + +/* +Low Pass Filter Node +*/ +MA_API ma_lpf_node_config ma_lpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_lpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.lpf = ma_lpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_lpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_lpf_process_pcm_frames(&pLPFNode->lpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_lpf_node_vtable = +{ + ma_lpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_lpf_node_init(ma_node_graph* pNodeGraph, const ma_lpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_lpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->lpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_lpf_init(&pConfig->lpf, pAllocationCallbacks, &pNode->lpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_lpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->lpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->lpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_lpf_node_reinit(const ma_lpf_config* pConfig, ma_lpf_node* pNode) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_lpf_reinit(pConfig, &pLPFNode->lpf); +} + +MA_API void ma_lpf_node_uninit(ma_lpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_lpf_node* pLPFNode = (ma_lpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_lpf_uninit(&pLPFNode->lpf, pAllocationCallbacks); +} + + + +/* +High Pass Filter Node +*/ +MA_API ma_hpf_node_config ma_hpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_hpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.hpf = ma_hpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_hpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_hpf_process_pcm_frames(&pHPFNode->hpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_hpf_node_vtable = +{ + ma_hpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_hpf_node_init(ma_node_graph* pNodeGraph, const ma_hpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->hpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_hpf_init(&pConfig->hpf, pAllocationCallbacks, &pNode->hpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_hpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->hpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->hpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_hpf_node_reinit(const ma_hpf_config* pConfig, ma_hpf_node* pNode) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_hpf_reinit(pConfig, &pHPFNode->hpf); +} + +MA_API void ma_hpf_node_uninit(ma_hpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_hpf_node* pHPFNode = (ma_hpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_hpf_uninit(&pHPFNode->hpf, pAllocationCallbacks); +} + + + + +/* +Band Pass Filter Node +*/ +MA_API ma_bpf_node_config ma_bpf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, ma_uint32 order) +{ + ma_bpf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.bpf = ma_bpf_config_init(ma_format_f32, channels, sampleRate, cutoffFrequency, order); + + return config; +} + +static void ma_bpf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_bpf_process_pcm_frames(&pBPFNode->bpf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_bpf_node_vtable = +{ + ma_bpf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_bpf_node_init(ma_node_graph* pNodeGraph, const ma_bpf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_bpf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->bpf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_bpf_init(&pConfig->bpf, pAllocationCallbacks, &pNode->bpf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_bpf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->bpf.channels; + baseNodeConfig.pOutputChannels = &pConfig->bpf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_bpf_node_reinit(const ma_bpf_config* pConfig, ma_bpf_node* pNode) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_bpf_reinit(pConfig, &pBPFNode->bpf); +} + +MA_API void ma_bpf_node_uninit(ma_bpf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_bpf_node* pBPFNode = (ma_bpf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_bpf_uninit(&pBPFNode->bpf, pAllocationCallbacks); +} + + + +/* +Notching Filter Node +*/ +MA_API ma_notch_node_config ma_notch_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double q, double frequency) +{ + ma_notch_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.notch = ma_notch2_config_init(ma_format_f32, channels, sampleRate, q, frequency); + + return config; +} + +static void ma_notch_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_notch_node* pBPFNode = (ma_notch_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_notch2_process_pcm_frames(&pBPFNode->notch, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_notch_node_vtable = +{ + ma_notch_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_notch_node_init(ma_node_graph* pNodeGraph, const ma_notch_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_notch_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->notch.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_notch2_init(&pConfig->notch, pAllocationCallbacks, &pNode->notch); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_notch_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->notch.channels; + baseNodeConfig.pOutputChannels = &pConfig->notch.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_notch_node_reinit(const ma_notch_config* pConfig, ma_notch_node* pNode) +{ + ma_notch_node* pNotchNode = (ma_notch_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_notch2_reinit(pConfig, &pNotchNode->notch); +} + +MA_API void ma_notch_node_uninit(ma_notch_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_notch_node* pNotchNode = (ma_notch_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_notch2_uninit(&pNotchNode->notch, pAllocationCallbacks); +} + + + +/* +Peaking Filter Node +*/ +MA_API ma_peak_node_config ma_peak_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_peak_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.peak = ma_peak2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_peak_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_peak_node* pBPFNode = (ma_peak_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_peak2_process_pcm_frames(&pBPFNode->peak, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_peak_node_vtable = +{ + ma_peak_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_peak_node_init(ma_node_graph* pNodeGraph, const ma_peak_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_peak_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->peak.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_peak2_init(&pConfig->peak, pAllocationCallbacks, &pNode->peak); + if (result != MA_SUCCESS) { + ma_node_uninit(pNode, pAllocationCallbacks); + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_peak_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->peak.channels; + baseNodeConfig.pOutputChannels = &pConfig->peak.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_peak_node_reinit(const ma_peak_config* pConfig, ma_peak_node* pNode) +{ + ma_peak_node* pPeakNode = (ma_peak_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_peak2_reinit(pConfig, &pPeakNode->peak); +} + +MA_API void ma_peak_node_uninit(ma_peak_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_peak_node* pPeakNode = (ma_peak_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_peak2_uninit(&pPeakNode->peak, pAllocationCallbacks); +} + + + +/* +Low Shelf Filter Node +*/ +MA_API ma_loshelf_node_config ma_loshelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_loshelf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.loshelf = ma_loshelf2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_loshelf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_loshelf_node* pBPFNode = (ma_loshelf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_loshelf2_process_pcm_frames(&pBPFNode->loshelf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_loshelf_node_vtable = +{ + ma_loshelf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_loshelf_node_init(ma_node_graph* pNodeGraph, const ma_loshelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_loshelf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->loshelf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_loshelf2_init(&pConfig->loshelf, pAllocationCallbacks, &pNode->loshelf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_loshelf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->loshelf.channels; + baseNodeConfig.pOutputChannels = &pConfig->loshelf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_loshelf_node_reinit(const ma_loshelf_config* pConfig, ma_loshelf_node* pNode) +{ + ma_loshelf_node* pLoshelfNode = (ma_loshelf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_loshelf2_reinit(pConfig, &pLoshelfNode->loshelf); +} + +MA_API void ma_loshelf_node_uninit(ma_loshelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_loshelf_node* pLoshelfNode = (ma_loshelf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_loshelf2_uninit(&pLoshelfNode->loshelf, pAllocationCallbacks); +} + + + +/* +High Shelf Filter Node +*/ +MA_API ma_hishelf_node_config ma_hishelf_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, double gainDB, double q, double frequency) +{ + ma_hishelf_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.hishelf = ma_hishelf2_config_init(ma_format_f32, channels, sampleRate, gainDB, q, frequency); + + return config; +} + +static void ma_hishelf_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_hishelf_node* pBPFNode = (ma_hishelf_node*)pNode; + + MA_ASSERT(pNode != NULL); + (void)pFrameCountIn; + + ma_hishelf2_process_pcm_frames(&pBPFNode->hishelf, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_hishelf_node_vtable = +{ + ma_hishelf_node_process_pcm_frames, + NULL, /* onGetRequiredInputFrameCount */ + 1, /* One input. */ + 1, /* One output. */ + 0 /* Default flags. */ +}; + +MA_API ma_result ma_hishelf_node_init(ma_node_graph* pNodeGraph, const ma_hishelf_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_hishelf_node* pNode) +{ + ma_result result; + ma_node_config baseNodeConfig; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pNode); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->hishelf.format != ma_format_f32) { + return MA_INVALID_ARGS; /* The format must be f32. */ + } + + result = ma_hishelf2_init(&pConfig->hishelf, pAllocationCallbacks, &pNode->hishelf); + if (result != MA_SUCCESS) { + return result; + } + + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_hishelf_node_vtable; + baseNodeConfig.pInputChannels = &pConfig->hishelf.channels; + baseNodeConfig.pOutputChannels = &pConfig->hishelf.channels; + + result = ma_node_init(pNodeGraph, &baseNodeConfig, pAllocationCallbacks, pNode); + if (result != MA_SUCCESS) { + return result; + } + + return result; +} + +MA_API ma_result ma_hishelf_node_reinit(const ma_hishelf_config* pConfig, ma_hishelf_node* pNode) +{ + ma_hishelf_node* pHishelfNode = (ma_hishelf_node*)pNode; + + if (pNode == NULL) { + return MA_INVALID_ARGS; + } + + return ma_hishelf2_reinit(pConfig, &pHishelfNode->hishelf); +} + +MA_API void ma_hishelf_node_uninit(ma_hishelf_node* pNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + ma_hishelf_node* pHishelfNode = (ma_hishelf_node*)pNode; + + if (pNode == NULL) { + return; + } + + ma_node_uninit(pNode, pAllocationCallbacks); + ma_hishelf2_uninit(&pHishelfNode->hishelf, pAllocationCallbacks); +} + + + + +MA_API ma_delay_node_config ma_delay_node_config_init(ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 delayInFrames, float decay) +{ + ma_delay_node_config config; + + config.nodeConfig = ma_node_config_init(); + config.delay = ma_delay_config_init(channels, sampleRate, delayInFrames, decay); + + return config; +} + + +static void ma_delay_node_process_pcm_frames(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_delay_node* pDelayNode = (ma_delay_node*)pNode; + + (void)pFrameCountIn; + + ma_delay_process_pcm_frames(&pDelayNode->delay, ppFramesOut[0], ppFramesIn[0], *pFrameCountOut); +} + +static ma_node_vtable g_ma_delay_node_vtable = +{ + ma_delay_node_process_pcm_frames, + NULL, + 1, /* 1 input channels. */ + 1, /* 1 output channel. */ + MA_NODE_FLAG_CONTINUOUS_PROCESSING /* Delay requires continuous processing to ensure the tail get's processed. */ +}; + +MA_API ma_result ma_delay_node_init(ma_node_graph* pNodeGraph, const ma_delay_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_delay_node* pDelayNode) +{ + ma_result result; + ma_node_config baseConfig; + + if (pDelayNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pDelayNode); + + result = ma_delay_init(&pConfig->delay, pAllocationCallbacks, &pDelayNode->delay); + if (result != MA_SUCCESS) { + return result; + } + + baseConfig = pConfig->nodeConfig; + baseConfig.vtable = &g_ma_delay_node_vtable; + baseConfig.pInputChannels = &pConfig->delay.channels; + baseConfig.pOutputChannels = &pConfig->delay.channels; + + result = ma_node_init(pNodeGraph, &baseConfig, pAllocationCallbacks, &pDelayNode->baseNode); + if (result != MA_SUCCESS) { + ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks); + return result; + } + + return result; +} + +MA_API void ma_delay_node_uninit(ma_delay_node* pDelayNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + if (pDelayNode == NULL) { + return; + } + + /* The base node is always uninitialized first. */ + ma_node_uninit(pDelayNode, pAllocationCallbacks); + ma_delay_uninit(&pDelayNode->delay, pAllocationCallbacks); +} + +MA_API void ma_delay_node_set_wet(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_wet(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_wet(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_wet(&pDelayNode->delay); +} + +MA_API void ma_delay_node_set_dry(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_dry(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_dry(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_dry(&pDelayNode->delay); +} + +MA_API void ma_delay_node_set_decay(ma_delay_node* pDelayNode, float value) +{ + if (pDelayNode == NULL) { + return; + } + + ma_delay_set_decay(&pDelayNode->delay, value); +} + +MA_API float ma_delay_node_get_decay(const ma_delay_node* pDelayNode) +{ + if (pDelayNode == NULL) { + return 0; + } + + return ma_delay_get_decay(&pDelayNode->delay); +} +#endif /* MA_NO_NODE_GRAPH */ + + +#if !defined(MA_NO_ENGINE) && !defined(MA_NO_NODE_GRAPH) +/************************************************************************************************************************************************************** + +Engine + +**************************************************************************************************************************************************************/ +#define MA_SEEK_TARGET_NONE (~(ma_uint64)0) + +MA_API ma_engine_node_config ma_engine_node_config_init(ma_engine* pEngine, ma_engine_node_type type, ma_uint32 flags) +{ + ma_engine_node_config config; + + MA_ZERO_OBJECT(&config); + config.pEngine = pEngine; + config.type = type; + config.isPitchDisabled = (flags & MA_SOUND_FLAG_NO_PITCH) != 0; + config.isSpatializationDisabled = (flags & MA_SOUND_FLAG_NO_SPATIALIZATION) != 0; + + return config; +} + + +static void ma_engine_node_update_pitch_if_required(ma_engine_node* pEngineNode) +{ + ma_bool32 isUpdateRequired = MA_FALSE; + float newPitch; + + MA_ASSERT(pEngineNode != NULL); + + newPitch = c89atomic_load_explicit_f32(&pEngineNode->pitch, c89atomic_memory_order_acquire); + + if (pEngineNode->oldPitch != newPitch) { + pEngineNode->oldPitch = newPitch; + isUpdateRequired = MA_TRUE; + } + + if (pEngineNode->oldDopplerPitch != pEngineNode->spatializer.dopplerPitch) { + pEngineNode->oldDopplerPitch = pEngineNode->spatializer.dopplerPitch; + isUpdateRequired = MA_TRUE; + } + + if (isUpdateRequired) { + float basePitch = (float)pEngineNode->sampleRate / ma_engine_get_sample_rate(pEngineNode->pEngine); + ma_linear_resampler_set_rate_ratio(&pEngineNode->resampler, basePitch * pEngineNode->oldPitch * pEngineNode->oldDopplerPitch); + } +} + +static ma_bool32 ma_engine_node_is_pitching_enabled(const ma_engine_node* pEngineNode) +{ + MA_ASSERT(pEngineNode != NULL); + + /* Don't try to be clever by skiping resampling in the pitch=1 case or else you'll glitch when moving away from 1. */ + return !c89atomic_load_explicit_32(&pEngineNode->isPitchDisabled, c89atomic_memory_order_acquire); +} + +static ma_bool32 ma_engine_node_is_spatialization_enabled(const ma_engine_node* pEngineNode) +{ + MA_ASSERT(pEngineNode != NULL); + + return !c89atomic_load_explicit_32(&pEngineNode->isSpatializationDisabled, c89atomic_memory_order_acquire); +} + +static ma_uint64 ma_engine_node_get_required_input_frame_count(const ma_engine_node* pEngineNode, ma_uint64 outputFrameCount) +{ + ma_uint64 inputFrameCount = 0; + + if (ma_engine_node_is_pitching_enabled(pEngineNode)) { + ma_result result = ma_linear_resampler_get_required_input_frame_count(&pEngineNode->resampler, outputFrameCount, &inputFrameCount); + if (result != MA_SUCCESS) { + inputFrameCount = 0; + } + } else { + inputFrameCount = outputFrameCount; /* No resampling, so 1:1. */ + } + + return inputFrameCount; +} + +static void ma_engine_node_process_pcm_frames__general(ma_engine_node* pEngineNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + ma_uint32 totalFramesProcessedIn; + ma_uint32 totalFramesProcessedOut; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + ma_bool32 isPitchingEnabled; + ma_bool32 isFadingEnabled; + ma_bool32 isSpatializationEnabled; + ma_bool32 isPanningEnabled; + + frameCountIn = *pFrameCountIn; + frameCountOut = *pFrameCountOut; + + channelsIn = ma_spatializer_get_input_channels(&pEngineNode->spatializer); + channelsOut = ma_spatializer_get_output_channels(&pEngineNode->spatializer); + + totalFramesProcessedIn = 0; + totalFramesProcessedOut = 0; + + isPitchingEnabled = ma_engine_node_is_pitching_enabled(pEngineNode); + isFadingEnabled = pEngineNode->fader.volumeBeg != 1 || pEngineNode->fader.volumeEnd != 1; + isSpatializationEnabled = ma_engine_node_is_spatialization_enabled(pEngineNode); + isPanningEnabled = pEngineNode->panner.pan != 0 && channelsOut != 1; + + /* Keep going while we've still got data available for processing. */ + while (totalFramesProcessedOut < frameCountOut) { + /* + We need to process in a specific order. We always do resampling first because it's likely + we're going to be increasing the channel count after spatialization. Also, I want to do + fading based on the output sample rate. + + We'll first read into a buffer from the resampler. Then we'll do all processing that + operates on the on the input channel count. We'll then get the spatializer to output to + the output buffer and then do all effects from that point directly in the output buffer + in-place. + + Note that we're always running the resampler. If we try to be clever and skip resampling + when the pitch is 1, we'll get a glitch when we move away from 1, back to 1, and then + away from 1 again. We'll want to implement any pitch=1 optimizations in the resampler + itself. + + There's a small optimization here that we'll utilize since it might be a fairly common + case. When the input and output channel counts are the same, we'll read straight into the + output buffer from the resampler and do everything in-place. + */ + const float* pRunningFramesIn; + float* pRunningFramesOut; + float* pWorkingBuffer; /* This is the buffer that we'll be processing frames in. This is in input channels. */ + float temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE / sizeof(float)]; + ma_uint32 tempCapInFrames = ma_countof(temp) / channelsIn; + ma_uint32 framesAvailableIn; + ma_uint32 framesAvailableOut; + ma_uint32 framesJustProcessedIn; + ma_uint32 framesJustProcessedOut; + ma_bool32 isWorkingBufferValid = MA_FALSE; + + framesAvailableIn = frameCountIn - totalFramesProcessedIn; + framesAvailableOut = frameCountOut - totalFramesProcessedOut; + + pRunningFramesIn = ma_offset_pcm_frames_const_ptr_f32(ppFramesIn[0], totalFramesProcessedIn, channelsIn); + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesProcessedOut, channelsOut); + + if (channelsIn == channelsOut) { + /* Fast path. Channel counts are the same. No need for an intermediary input buffer. */ + pWorkingBuffer = pRunningFramesOut; + } else { + /* Slow path. Channel counts are different. Need to use an intermediary input buffer. */ + pWorkingBuffer = temp; + if (framesAvailableOut > tempCapInFrames) { + framesAvailableOut = tempCapInFrames; + } + } + + /* First is resampler. */ + if (isPitchingEnabled) { + ma_uint64 resampleFrameCountIn = framesAvailableIn; + ma_uint64 resampleFrameCountOut = framesAvailableOut; + + ma_linear_resampler_process_pcm_frames(&pEngineNode->resampler, pRunningFramesIn, &resampleFrameCountIn, pWorkingBuffer, &resampleFrameCountOut); + isWorkingBufferValid = MA_TRUE; + + framesJustProcessedIn = (ma_uint32)resampleFrameCountIn; + framesJustProcessedOut = (ma_uint32)resampleFrameCountOut; + } else { + framesJustProcessedIn = ma_min(framesAvailableIn, framesAvailableOut); + framesJustProcessedOut = framesJustProcessedIn; /* When no resampling is being performed, the number of output frames is the same as input frames. */ + } + + /* Fading. */ + if (isFadingEnabled) { + if (isWorkingBufferValid) { + ma_fader_process_pcm_frames(&pEngineNode->fader, pWorkingBuffer, pWorkingBuffer, framesJustProcessedOut); /* In-place processing. */ + } else { + ma_fader_process_pcm_frames(&pEngineNode->fader, pWorkingBuffer, pRunningFramesIn, framesJustProcessedOut); + isWorkingBufferValid = MA_TRUE; + } + } + + /* + If at this point we still haven't actually done anything with the working buffer we need + to just read straight from the input buffer. + */ + if (isWorkingBufferValid == MA_FALSE) { + pWorkingBuffer = (float*)pRunningFramesIn; /* Naughty const cast, but it's safe at this point because we won't ever be writing to it from this point out. */ + } + + /* Spatialization. */ + if (isSpatializationEnabled) { + ma_uint32 iListener; + + /* + When determining the listener to use, we first check to see if the sound is pinned to a + specific listener. If so, we use that. Otherwise we just use the closest listener. + */ + if (pEngineNode->pinnedListenerIndex != MA_LISTENER_INDEX_CLOSEST && pEngineNode->pinnedListenerIndex < ma_engine_get_listener_count(pEngineNode->pEngine)) { + iListener = pEngineNode->pinnedListenerIndex; + } else { + iListener = ma_engine_find_closest_listener(pEngineNode->pEngine, pEngineNode->spatializer.position.x, pEngineNode->spatializer.position.y, pEngineNode->spatializer.position.z); + } + + ma_spatializer_process_pcm_frames(&pEngineNode->spatializer, &pEngineNode->pEngine->listeners[iListener], pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut); + } else { + /* No spatialization, but we still need to do channel conversion. */ + if (channelsIn == channelsOut) { + /* No channel conversion required. Just copy straight to the output buffer. */ + ma_copy_pcm_frames(pRunningFramesOut, pWorkingBuffer, framesJustProcessedOut, ma_format_f32, channelsOut); + } else { + /* Channel conversion required. TODO: Add support for channel maps here. */ + ma_channel_map_apply_f32(pRunningFramesOut, NULL, channelsOut, pWorkingBuffer, NULL, channelsIn, framesJustProcessedOut, ma_channel_mix_mode_simple, pEngineNode->pEngine->monoExpansionMode); + } + } + + /* At this point we can guarantee that the output buffer contains valid data. We can process everything in place now. */ + + /* Panning. */ + if (isPanningEnabled) { + ma_panner_process_pcm_frames(&pEngineNode->panner, pRunningFramesOut, pRunningFramesOut, framesJustProcessedOut); /* In-place processing. */ + } + + /* We're done for this chunk. */ + totalFramesProcessedIn += framesJustProcessedIn; + totalFramesProcessedOut += framesJustProcessedOut; + + /* If we didn't process any output frames this iteration it means we've either run out of input data, or run out of room in the output buffer. */ + if (framesJustProcessedOut == 0) { + break; + } + } + + /* At this point we're done processing. */ + *pFrameCountIn = totalFramesProcessedIn; + *pFrameCountOut = totalFramesProcessedOut; +} + +static void ma_engine_node_process_pcm_frames__sound(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + /* For sounds, we need to first read from the data source. Then we need to apply the engine effects (pan, pitch, fades, etc.). */ + ma_result result = MA_SUCCESS; + ma_sound* pSound = (ma_sound*)pNode; + ma_uint32 frameCount = *pFrameCountOut; + ma_uint32 totalFramesRead = 0; + ma_format dataSourceFormat; + ma_uint32 dataSourceChannels; + ma_uint8 temp[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 tempCapInFrames; + ma_uint64 seekTarget; + + /* This is a data source node which means no input buses. */ + (void)ppFramesIn; + (void)pFrameCountIn; + + /* If we're marked at the end we need to stop the sound and do nothing. */ + if (ma_sound_at_end(pSound)) { + ma_sound_stop(pSound); + *pFrameCountOut = 0; + return; + } + + /* If we're seeking, do so now before reading. */ + seekTarget = c89atomic_load_64(&pSound->seekTarget); + if (seekTarget != MA_SEEK_TARGET_NONE) { + ma_data_source_seek_to_pcm_frame(pSound->pDataSource, seekTarget); + + /* Any time-dependant effects need to have their times updated. */ + ma_node_set_time(pSound, seekTarget); + + c89atomic_exchange_64(&pSound->seekTarget, MA_SEEK_TARGET_NONE); + } + + /* + We want to update the pitch once. For sounds, this can be either at the start or at the end. If + we don't force this to only ever be updating once, we could end up in a situation where + retrieving the required input frame count ends up being different to what we actually retrieve. + What could happen is that the required input frame count is calculated, the pitch is update, + and then this processing function is called resulting in a different number of input frames + being processed. Do not call this in ma_engine_node_process_pcm_frames__general() or else + you'll hit the aforementioned bug. + */ + ma_engine_node_update_pitch_if_required(&pSound->engineNode); + + /* + For the convenience of the caller, we're doing to allow data sources to use non-floating-point formats and channel counts that differ + from the main engine. + */ + result = ma_data_source_get_data_format(pSound->pDataSource, &dataSourceFormat, &dataSourceChannels, NULL, NULL, 0); + if (result == MA_SUCCESS) { + tempCapInFrames = sizeof(temp) / ma_get_bytes_per_frame(dataSourceFormat, dataSourceChannels); + + /* Keep reading until we've read as much as was requested or we reach the end of the data source. */ + while (totalFramesRead < frameCount) { + ma_uint32 framesRemaining = frameCount - totalFramesRead; + ma_uint32 framesToRead; + ma_uint64 framesJustRead; + ma_uint32 frameCountIn; + ma_uint32 frameCountOut; + const float* pRunningFramesIn; + float* pRunningFramesOut; + + /* + The first thing we need to do is read into the temporary buffer. We can calculate exactly + how many input frames we'll need after resampling. + */ + framesToRead = (ma_uint32)ma_engine_node_get_required_input_frame_count(&pSound->engineNode, framesRemaining); + if (framesToRead > tempCapInFrames) { + framesToRead = tempCapInFrames; + } + + result = ma_data_source_read_pcm_frames(pSound->pDataSource, temp, framesToRead, &framesJustRead); + + /* If we reached the end of the sound we'll want to mark it as at the end and stop it. This should never be returned for looping sounds. */ + if (result == MA_AT_END) { + c89atomic_exchange_32(&pSound->atEnd, MA_TRUE); /* This will be set to false in ma_sound_start(). */ + } + + pRunningFramesOut = ma_offset_pcm_frames_ptr_f32(ppFramesOut[0], totalFramesRead, ma_engine_get_channels(ma_sound_get_engine(pSound))); + + frameCountIn = (ma_uint32)framesJustRead; + frameCountOut = framesRemaining; + + /* Convert if necessary. */ + if (dataSourceFormat == ma_format_f32) { + /* Fast path. No data conversion necessary. */ + pRunningFramesIn = (float*)temp; + ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut); + } else { + /* Slow path. Need to do sample format conversion to f32. If we give the f32 buffer the same count as the first temp buffer, we're guaranteed it'll be large enough. */ + float tempf32[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* Do not do `MA_DATA_CONVERTER_STACK_BUFFER_SIZE/sizeof(float)` here like we've done in other places. */ + ma_convert_pcm_frames_format(tempf32, ma_format_f32, temp, dataSourceFormat, framesJustRead, dataSourceChannels, ma_dither_mode_none); + + /* Now that we have our samples in f32 format we can process like normal. */ + pRunningFramesIn = tempf32; + ma_engine_node_process_pcm_frames__general(&pSound->engineNode, &pRunningFramesIn, &frameCountIn, &pRunningFramesOut, &frameCountOut); + } + + /* We should have processed all of our input frames since we calculated the required number of input frames at the top. */ + MA_ASSERT(frameCountIn == framesJustRead); + totalFramesRead += (ma_uint32)frameCountOut; /* Safe cast. */ + + if (result != MA_SUCCESS || ma_sound_at_end(pSound)) { + break; /* Might have reached the end. */ + } + } + } + + *pFrameCountOut = totalFramesRead; +} + +static void ma_engine_node_process_pcm_frames__group(ma_node* pNode, const float** ppFramesIn, ma_uint32* pFrameCountIn, float** ppFramesOut, ma_uint32* pFrameCountOut) +{ + /* + Make sure the pitch is updated before trying to read anything. It's important that this is done + only once and not in ma_engine_node_process_pcm_frames__general(). The reason for this is that + ma_engine_node_process_pcm_frames__general() will call ma_engine_node_get_required_input_frame_count(), + and if another thread modifies the pitch just after that call it can result in a glitch due to + the input rate changing. + */ + ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode); + + /* For groups, the input data has already been read and we just need to apply the effect. */ + ma_engine_node_process_pcm_frames__general((ma_engine_node*)pNode, ppFramesIn, pFrameCountIn, ppFramesOut, pFrameCountOut); +} + +static ma_result ma_engine_node_get_required_input_frame_count__group(ma_node* pNode, ma_uint32 outputFrameCount, ma_uint32* pInputFrameCount) +{ + ma_uint64 inputFrameCount; + + MA_ASSERT(pInputFrameCount != NULL); + + /* Our pitch will affect this calculation. We need to update it. */ + ma_engine_node_update_pitch_if_required((ma_engine_node*)pNode); + + inputFrameCount = ma_engine_node_get_required_input_frame_count((ma_engine_node*)pNode, outputFrameCount); + if (inputFrameCount > 0xFFFFFFFF) { + inputFrameCount = 0xFFFFFFFF; /* Will never happen because miniaudio will only ever process in relatively small chunks. */ + } + + *pInputFrameCount = (ma_uint32)inputFrameCount; + + return MA_SUCCESS; +} + + +static ma_node_vtable g_ma_engine_node_vtable__sound = +{ + ma_engine_node_process_pcm_frames__sound, + NULL, /* onGetRequiredInputFrameCount */ + 0, /* Sounds are data source nodes which means they have zero inputs (their input is drawn from the data source itself). */ + 1, /* Sounds have one output bus. */ + 0 /* Default flags. */ +}; + +static ma_node_vtable g_ma_engine_node_vtable__group = +{ + ma_engine_node_process_pcm_frames__group, + ma_engine_node_get_required_input_frame_count__group, + 1, /* Groups have one input bus. */ + 1, /* Groups have one output bus. */ + MA_NODE_FLAG_DIFFERENT_PROCESSING_RATES /* The engine node does resampling so should let miniaudio know about it. */ +}; + + + +static ma_node_config ma_engine_node_base_node_config_init(const ma_engine_node_config* pConfig) +{ + ma_node_config baseNodeConfig; + + if (pConfig->type == ma_engine_node_type_sound) { + /* Sound. */ + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_engine_node_vtable__sound; + baseNodeConfig.initialState = ma_node_state_stopped; /* Sounds are stopped by default. */ + } else { + /* Group. */ + baseNodeConfig = ma_node_config_init(); + baseNodeConfig.vtable = &g_ma_engine_node_vtable__group; + baseNodeConfig.initialState = ma_node_state_started; /* Groups are started by default. */ + } + + return baseNodeConfig; +} + +static ma_spatializer_config ma_engine_node_spatializer_config_init(const ma_node_config* pBaseNodeConfig) +{ + return ma_spatializer_config_init(pBaseNodeConfig->pInputChannels[0], pBaseNodeConfig->pOutputChannels[0]); +} + +typedef struct +{ + size_t sizeInBytes; + size_t baseNodeOffset; + size_t resamplerOffset; + size_t spatializerOffset; +} ma_engine_node_heap_layout; + +static ma_result ma_engine_node_get_heap_layout(const ma_engine_node_config* pConfig, ma_engine_node_heap_layout* pHeapLayout) +{ + ma_result result; + size_t tempHeapSize; + ma_node_config baseNodeConfig; + ma_linear_resampler_config resamplerConfig; + ma_spatializer_config spatializerConfig; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + + MA_ASSERT(pHeapLayout); + + MA_ZERO_OBJECT(pHeapLayout); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + if (pConfig->pEngine == NULL) { + return MA_INVALID_ARGS; /* An engine must be specified. */ + } + + pHeapLayout->sizeInBytes = 0; + + channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); + channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); + + + /* Base node. */ + baseNodeConfig = ma_engine_node_base_node_config_init(pConfig); + baseNodeConfig.pInputChannels = &channelsIn; + baseNodeConfig.pOutputChannels = &channelsOut; + + result = ma_node_get_heap_size(ma_engine_get_node_graph(pConfig->pEngine), &baseNodeConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the base node. */ + } + + pHeapLayout->baseNodeOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + /* Resmapler. */ + resamplerConfig = ma_linear_resampler_config_init(ma_format_f32, channelsIn, 1, 1); /* Input and output sample rates don't affect the calculation of the heap size. */ + resamplerConfig.lpfOrder = 0; + + result = ma_linear_resampler_get_heap_size(&resamplerConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the resampler. */ + } + + pHeapLayout->resamplerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + /* Spatializer. */ + spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); + + result = ma_spatializer_get_heap_size(&spatializerConfig, &tempHeapSize); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the size of the heap for the spatializer. */ + } + + pHeapLayout->spatializerOffset = pHeapLayout->sizeInBytes; + pHeapLayout->sizeInBytes += ma_align_64(tempHeapSize); + + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_node_get_heap_size(const ma_engine_node_config* pConfig, size_t* pHeapSizeInBytes) +{ + ma_result result; + ma_engine_node_heap_layout heapLayout; + + if (pHeapSizeInBytes == NULL) { + return MA_INVALID_ARGS; + } + + *pHeapSizeInBytes = 0; + + result = ma_engine_node_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + *pHeapSizeInBytes = heapLayout.sizeInBytes; + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_node_init_preallocated(const ma_engine_node_config* pConfig, void* pHeap, ma_engine_node* pEngineNode) +{ + ma_result result; + ma_engine_node_heap_layout heapLayout; + ma_node_config baseNodeConfig; + ma_linear_resampler_config resamplerConfig; + ma_fader_config faderConfig; + ma_spatializer_config spatializerConfig; + ma_panner_config pannerConfig; + ma_uint32 channelsIn; + ma_uint32 channelsOut; + + if (pEngineNode == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pEngineNode); + + result = ma_engine_node_get_heap_layout(pConfig, &heapLayout); + if (result != MA_SUCCESS) { + return result; + } + + if (pConfig->pinnedListenerIndex != MA_LISTENER_INDEX_CLOSEST && pConfig->pinnedListenerIndex >= ma_engine_get_listener_count(pConfig->pEngine)) { + return MA_INVALID_ARGS; /* Invalid listener. */ + } + + pEngineNode->_pHeap = pHeap; + MA_ZERO_MEMORY(pHeap, heapLayout.sizeInBytes); + + pEngineNode->pEngine = pConfig->pEngine; + pEngineNode->sampleRate = (pConfig->sampleRate > 0) ? pConfig->sampleRate : ma_engine_get_sample_rate(pEngineNode->pEngine); + pEngineNode->pitch = 1; + pEngineNode->oldPitch = 1; + pEngineNode->oldDopplerPitch = 1; + pEngineNode->isPitchDisabled = pConfig->isPitchDisabled; + pEngineNode->isSpatializationDisabled = pConfig->isSpatializationDisabled; + pEngineNode->pinnedListenerIndex = pConfig->pinnedListenerIndex; + + + channelsIn = (pConfig->channelsIn != 0) ? pConfig->channelsIn : ma_engine_get_channels(pConfig->pEngine); + channelsOut = (pConfig->channelsOut != 0) ? pConfig->channelsOut : ma_engine_get_channels(pConfig->pEngine); + + + /* Base node. */ + baseNodeConfig = ma_engine_node_base_node_config_init(pConfig); + baseNodeConfig.pInputChannels = &channelsIn; + baseNodeConfig.pOutputChannels = &channelsOut; + + result = ma_node_init_preallocated(&pConfig->pEngine->nodeGraph, &baseNodeConfig, ma_offset_ptr(pHeap, heapLayout.baseNodeOffset), &pEngineNode->baseNode); + if (result != MA_SUCCESS) { + goto error0; + } + + + /* + We can now initialize the effects we need in order to implement the engine node. There's a + defined order of operations here, mainly centered around when we convert our channels from the + data source's native channel count to the engine's channel count. As a rule, we want to do as + much computation as possible before spatialization because there's a chance that will increase + the channel count, thereby increasing the amount of work needing to be done to process. + */ + + /* We'll always do resampling first. */ + resamplerConfig = ma_linear_resampler_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], pEngineNode->sampleRate, ma_engine_get_sample_rate(pEngineNode->pEngine)); + resamplerConfig.lpfOrder = 0; /* <-- Need to disable low-pass filtering for pitch shifting for now because there's cases where the biquads are becoming unstable. Need to figure out a better fix for this. */ + + result = ma_linear_resampler_init_preallocated(&resamplerConfig, ma_offset_ptr(pHeap, heapLayout.resamplerOffset), &pEngineNode->resampler); + if (result != MA_SUCCESS) { + goto error1; + } + + + /* After resampling will come the fader. */ + faderConfig = ma_fader_config_init(ma_format_f32, baseNodeConfig.pInputChannels[0], ma_engine_get_sample_rate(pEngineNode->pEngine)); + + result = ma_fader_init(&faderConfig, &pEngineNode->fader); + if (result != MA_SUCCESS) { + goto error2; + } + + + /* + Spatialization comes next. We spatialize based ont he node's output channel count. It's up the caller to + ensure channels counts link up correctly in the node graph. + */ + spatializerConfig = ma_engine_node_spatializer_config_init(&baseNodeConfig); + spatializerConfig.gainSmoothTimeInFrames = pEngineNode->pEngine->gainSmoothTimeInFrames; + + result = ma_spatializer_init_preallocated(&spatializerConfig, ma_offset_ptr(pHeap, heapLayout.spatializerOffset), &pEngineNode->spatializer); + if (result != MA_SUCCESS) { + goto error2; + } + + + /* + After spatialization comes panning. We need to do this after spatialization because otherwise we wouldn't + be able to pan mono sounds. + */ + pannerConfig = ma_panner_config_init(ma_format_f32, baseNodeConfig.pOutputChannels[0]); + + result = ma_panner_init(&pannerConfig, &pEngineNode->panner); + if (result != MA_SUCCESS) { + goto error3; + } + + return MA_SUCCESS; + + /* No need for allocation callbacks here because we use a preallocated heap. */ +error3: ma_spatializer_uninit(&pEngineNode->spatializer, NULL); +error2: ma_linear_resampler_uninit(&pEngineNode->resampler, NULL); +error1: ma_node_uninit(&pEngineNode->baseNode, NULL); +error0: return result; +} + +MA_API ma_result ma_engine_node_init(const ma_engine_node_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_engine_node* pEngineNode) +{ + ma_result result; + size_t heapSizeInBytes; + void* pHeap; + + result = ma_engine_node_get_heap_size(pConfig, &heapSizeInBytes); + if (result != MA_SUCCESS) { + return result; + } + + if (heapSizeInBytes > 0) { + pHeap = ma_malloc(heapSizeInBytes, pAllocationCallbacks); + if (pHeap == NULL) { + return MA_OUT_OF_MEMORY; + } + } else { + pHeap = NULL; + } + + result = ma_engine_node_init_preallocated(pConfig, pHeap, pEngineNode); + if (result != MA_SUCCESS) { + ma_free(pHeap, pAllocationCallbacks); + return result; + } + + pEngineNode->_ownsHeap = MA_TRUE; + return MA_SUCCESS; +} + +MA_API void ma_engine_node_uninit(ma_engine_node* pEngineNode, const ma_allocation_callbacks* pAllocationCallbacks) +{ + /* + The base node always needs to be uninitialized first to ensure it's detached from the graph completely before we + destroy anything that might be in the middle of being used by the processing function. + */ + ma_node_uninit(&pEngineNode->baseNode, pAllocationCallbacks); + + /* Now that the node has been uninitialized we can safely uninitialize the rest. */ + ma_spatializer_uninit(&pEngineNode->spatializer, pAllocationCallbacks); + ma_linear_resampler_uninit(&pEngineNode->resampler, pAllocationCallbacks); + + /* Free the heap last. */ + if (pEngineNode->_ownsHeap) { + ma_free(pEngineNode->_pHeap, pAllocationCallbacks); + } +} + + +MA_API ma_sound_config ma_sound_config_init(void) +{ + ma_sound_config config; + + MA_ZERO_OBJECT(&config); + config.rangeEndInPCMFrames = ~((ma_uint64)0); + config.loopPointEndInPCMFrames = ~((ma_uint64)0); + + return config; +} + +MA_API ma_sound_group_config ma_sound_group_config_init(void) +{ + ma_sound_group_config config; + + MA_ZERO_OBJECT(&config); + + return config; +} + + +MA_API ma_engine_config ma_engine_config_init(void) +{ + ma_engine_config config; + + MA_ZERO_OBJECT(&config); + config.listenerCount = 1; /* Always want at least one listener. */ + config.monoExpansionMode = ma_mono_expansion_mode_default; + + return config; +} + + +#if !defined(MA_NO_DEVICE_IO) +static void ma_engine_data_callback_internal(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) +{ + ma_engine* pEngine = (ma_engine*)pDevice->pUserData; + + (void)pFramesIn; + + /* + Experiment: Try processing a resource manager job if we're on the Emscripten build. + + This serves two purposes: + + 1) It ensures jobs are actually processed at some point since we cannot guarantee that the + caller is doing the right thing and calling ma_resource_manager_process_next_job(); and + + 2) It's an attempt at working around an issue where processing jobs on the Emscripten main + loop doesn't work as well as it should. When trying to load sounds without the `DECODE` + flag or with the `ASYNC` flag, the sound data is just not able to be loaded in time + before the callback is processed. I think it's got something to do with the single- + threaded nature of Web, but I'm not entirely sure. + */ + #if !defined(MA_NO_RESOURCE_MANAGER) && defined(MA_EMSCRIPTEN) + { + if (pEngine->pResourceManager != NULL) { + if ((pEngine->pResourceManager->config.flags & MA_RESOURCE_MANAGER_FLAG_NO_THREADING) != 0) { + ma_resource_manager_process_next_job(pEngine->pResourceManager); + } + } + } + #endif + + ma_engine_read_pcm_frames(pEngine, pFramesOut, frameCount, NULL); +} +#endif + +MA_API ma_result ma_engine_init(const ma_engine_config* pConfig, ma_engine* pEngine) +{ + ma_result result; + ma_node_graph_config nodeGraphConfig; + ma_engine_config engineConfig; + ma_spatializer_listener_config listenerConfig; + ma_uint32 iListener; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pEngine); + + /* The config is allowed to be NULL in which case we use defaults for everything. */ + if (pConfig != NULL) { + engineConfig = *pConfig; + } else { + engineConfig = ma_engine_config_init(); + } + + pEngine->monoExpansionMode = engineConfig.monoExpansionMode; + ma_allocation_callbacks_init_copy(&pEngine->allocationCallbacks, &engineConfig.allocationCallbacks); + + #if !defined(MA_NO_RESOURCE_MANAGER) + { + pEngine->pResourceManager = engineConfig.pResourceManager; + } + #endif + + #if !defined(MA_NO_DEVICE_IO) + { + pEngine->pDevice = engineConfig.pDevice; + + /* If we don't have a device, we need one. */ + if (pEngine->pDevice == NULL && engineConfig.noDevice == MA_FALSE) { + ma_device_config deviceConfig; + + pEngine->pDevice = (ma_device*)ma_malloc(sizeof(*pEngine->pDevice), &pEngine->allocationCallbacks); + if (pEngine->pDevice == NULL) { + return MA_OUT_OF_MEMORY; + } + + deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.pDeviceID = engineConfig.pPlaybackDeviceID; + deviceConfig.playback.format = ma_format_f32; + deviceConfig.playback.channels = engineConfig.channels; + deviceConfig.sampleRate = engineConfig.sampleRate; + deviceConfig.dataCallback = ma_engine_data_callback_internal; + deviceConfig.pUserData = pEngine; + deviceConfig.periodSizeInFrames = engineConfig.periodSizeInFrames; + deviceConfig.periodSizeInMilliseconds = engineConfig.periodSizeInMilliseconds; + deviceConfig.noPreSilencedOutputBuffer = MA_TRUE; /* We'll always be outputting to every frame in the callback so there's no need for a pre-silenced buffer. */ + deviceConfig.noClip = MA_TRUE; /* The engine will do clipping itself. */ + + if (engineConfig.pContext == NULL) { + ma_context_config contextConfig = ma_context_config_init(); + contextConfig.allocationCallbacks = pEngine->allocationCallbacks; + contextConfig.pLog = engineConfig.pLog; + + /* If the engine config does not specify a log, use the resource manager's if we have one. */ + #ifndef MA_NO_RESOURCE_MANAGER + { + if (contextConfig.pLog == NULL && engineConfig.pResourceManager != NULL) { + contextConfig.pLog = ma_resource_manager_get_log(engineConfig.pResourceManager); + } + } + #endif + + result = ma_device_init_ex(NULL, 0, &contextConfig, &deviceConfig, pEngine->pDevice); + } else { + result = ma_device_init(engineConfig.pContext, &deviceConfig, pEngine->pDevice); + } + + if (result != MA_SUCCESS) { + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + pEngine->pDevice = NULL; + return result; + } + + pEngine->ownsDevice = MA_TRUE; + } + + /* Update the channel count and sample rate of the engine config so we can reference it below. */ + if (pEngine->pDevice != NULL) { + engineConfig.channels = pEngine->pDevice->playback.channels; + engineConfig.sampleRate = pEngine->pDevice->sampleRate; + } + } + #endif + + if (engineConfig.channels == 0 || engineConfig.sampleRate == 0) { + return MA_INVALID_ARGS; + } + + pEngine->sampleRate = engineConfig.sampleRate; + + /* The engine always uses either the log that was passed into the config, or the context's log is available. */ + if (engineConfig.pLog != NULL) { + pEngine->pLog = engineConfig.pLog; + } else { + #if !defined(MA_NO_DEVICE_IO) + { + pEngine->pLog = ma_device_get_log(pEngine->pDevice); + } + #else + { + pEngine->pLog = NULL; + } + #endif + } + + + /* The engine is a node graph. This needs to be initialized after we have the device so we can can determine the channel count. */ + nodeGraphConfig = ma_node_graph_config_init(engineConfig.channels); + nodeGraphConfig.nodeCacheCapInFrames = (engineConfig.periodSizeInFrames > 0xFFFF) ? 0xFFFF : (ma_uint16)engineConfig.periodSizeInFrames; + + result = ma_node_graph_init(&nodeGraphConfig, &pEngine->allocationCallbacks, &pEngine->nodeGraph); + if (result != MA_SUCCESS) { + goto on_error_1; + } + + + /* We need at least one listener. */ + if (engineConfig.listenerCount == 0) { + engineConfig.listenerCount = 1; + } + + if (engineConfig.listenerCount > MA_ENGINE_MAX_LISTENERS) { + result = MA_INVALID_ARGS; /* Too many listeners. */ + goto on_error_1; + } + + for (iListener = 0; iListener < engineConfig.listenerCount; iListener += 1) { + listenerConfig = ma_spatializer_listener_config_init(ma_node_graph_get_channels(&pEngine->nodeGraph)); + + /* + If we're using a device, use the device's channel map for the listener. Otherwise just use + miniaudio's default channel map. + */ + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + /* + Temporarily disabled. There is a subtle bug here where front-left and front-right + will be used by the device's channel map, but this is not what we want to use for + spatialization. Instead we want to use side-left and side-right. I need to figure + out a better solution for this. For now, disabling the user of device channel maps. + */ + /*listenerConfig.pChannelMapOut = pEngine->pDevice->playback.channelMap;*/ + } + } + #endif + + result = ma_spatializer_listener_init(&listenerConfig, &pEngine->allocationCallbacks, &pEngine->listeners[iListener]); /* TODO: Change this to a pre-allocated heap. */ + if (result != MA_SUCCESS) { + goto on_error_2; + } + + pEngine->listenerCount += 1; + } + + + /* Gain smoothing for spatialized sounds. */ + pEngine->gainSmoothTimeInFrames = engineConfig.gainSmoothTimeInFrames; + if (pEngine->gainSmoothTimeInFrames == 0) { + ma_uint32 gainSmoothTimeInMilliseconds = engineConfig.gainSmoothTimeInMilliseconds; + if (gainSmoothTimeInMilliseconds == 0) { + gainSmoothTimeInMilliseconds = 8; + } + + pEngine->gainSmoothTimeInFrames = (gainSmoothTimeInMilliseconds * ma_engine_get_sample_rate(pEngine)) / 1000; /* 8ms by default. */ + } + + + /* We need a resource manager. */ + #ifndef MA_NO_RESOURCE_MANAGER + { + if (pEngine->pResourceManager == NULL) { + ma_resource_manager_config resourceManagerConfig; + + pEngine->pResourceManager = (ma_resource_manager*)ma_malloc(sizeof(*pEngine->pResourceManager), &pEngine->allocationCallbacks); + if (pEngine->pResourceManager == NULL) { + result = MA_OUT_OF_MEMORY; + goto on_error_2; + } + + resourceManagerConfig = ma_resource_manager_config_init(); + resourceManagerConfig.pLog = pEngine->pLog; /* Always use the engine's log for internally-managed resource managers. */ + resourceManagerConfig.decodedFormat = ma_format_f32; + resourceManagerConfig.decodedChannels = 0; /* Leave the decoded channel count as 0 so we can get good spatialization. */ + resourceManagerConfig.decodedSampleRate = ma_engine_get_sample_rate(pEngine); + ma_allocation_callbacks_init_copy(&resourceManagerConfig.allocationCallbacks, &pEngine->allocationCallbacks); + resourceManagerConfig.pVFS = engineConfig.pResourceManagerVFS; + + /* The Emscripten build cannot use threads. */ + #if defined(MA_EMSCRIPTEN) + { + resourceManagerConfig.jobThreadCount = 0; + resourceManagerConfig.flags |= MA_RESOURCE_MANAGER_FLAG_NO_THREADING; + } + #endif + + result = ma_resource_manager_init(&resourceManagerConfig, pEngine->pResourceManager); + if (result != MA_SUCCESS) { + goto on_error_3; + } + + pEngine->ownsResourceManager = MA_TRUE; + } + } + #endif + + /* Setup some stuff for inlined sounds. That is sounds played with ma_engine_play_sound(). */ + pEngine->inlinedSoundLock = 0; + pEngine->pInlinedSoundHead = NULL; + + /* Start the engine if required. This should always be the last step. */ + #if !defined(MA_NO_DEVICE_IO) + { + if (engineConfig.noAutoStart == MA_FALSE && pEngine->pDevice != NULL) { + result = ma_engine_start(pEngine); + if (result != MA_SUCCESS) { + goto on_error_4; /* Failed to start the engine. */ + } + } + } + #endif + + return MA_SUCCESS; + +#if !defined(MA_NO_DEVICE_IO) +on_error_4: +#endif +#if !defined(MA_NO_RESOURCE_MANAGER) +on_error_3: + if (pEngine->ownsResourceManager) { + ma_free(pEngine->pResourceManager, &pEngine->allocationCallbacks); + } +#endif /* MA_NO_RESOURCE_MANAGER */ +on_error_2: + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + ma_spatializer_listener_uninit(&pEngine->listeners[iListener], &pEngine->allocationCallbacks); + } + + ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks); +on_error_1: + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->ownsDevice) { + ma_device_uninit(pEngine->pDevice); + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + } + } + #endif + + return result; +} + +MA_API void ma_engine_uninit(ma_engine* pEngine) +{ + ma_uint32 iListener; + + if (pEngine == NULL) { + return; + } + + /* The device must be uninitialized before the node graph to ensure the audio thread doesn't try accessing it. */ + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->ownsDevice) { + ma_device_uninit(pEngine->pDevice); + ma_free(pEngine->pDevice, &pEngine->allocationCallbacks); + } else { + if (pEngine->pDevice != NULL) { + ma_device_stop(pEngine->pDevice); + } + } + } + #endif + + /* + All inlined sounds need to be deleted. I'm going to use a lock here just to future proof in case + I want to do some kind of garbage collection later on. + */ + ma_spinlock_lock(&pEngine->inlinedSoundLock); + { + for (;;) { + ma_sound_inlined* pSoundToDelete = pEngine->pInlinedSoundHead; + if (pSoundToDelete == NULL) { + break; /* Done. */ + } + + pEngine->pInlinedSoundHead = pSoundToDelete->pNext; + + ma_sound_uninit(&pSoundToDelete->sound); + ma_free(pSoundToDelete, &pEngine->allocationCallbacks); + } + } + ma_spinlock_unlock(&pEngine->inlinedSoundLock); + + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + ma_spatializer_listener_uninit(&pEngine->listeners[iListener], &pEngine->allocationCallbacks); + } + + /* Make sure the node graph is uninitialized after the audio thread has been shutdown to prevent accessing of the node graph after being uninitialized. */ + ma_node_graph_uninit(&pEngine->nodeGraph, &pEngine->allocationCallbacks); + + /* Uninitialize the resource manager last to ensure we don't have a thread still trying to access it. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pEngine->ownsResourceManager) { + ma_resource_manager_uninit(pEngine->pResourceManager); + ma_free(pEngine->pResourceManager, &pEngine->allocationCallbacks); + } +#endif +} + +MA_API ma_result ma_engine_read_pcm_frames(ma_engine* pEngine, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + return ma_node_graph_read_pcm_frames(&pEngine->nodeGraph, pFramesOut, frameCount, pFramesRead); +} + +MA_API ma_node_graph* ma_engine_get_node_graph(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + return &pEngine->nodeGraph; +} + +#if !defined(MA_NO_RESOURCE_MANAGER) +MA_API ma_resource_manager* ma_engine_get_resource_manager(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + #if !defined(MA_NO_RESOURCE_MANAGER) + { + return pEngine->pResourceManager; + } + #else + { + return NULL; + } + #endif +} +#endif + +MA_API ma_device* ma_engine_get_device(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + #if !defined(MA_NO_DEVICE_IO) + { + return pEngine->pDevice; + } + #else + { + return NULL; + } + #endif +} + +MA_API ma_log* ma_engine_get_log(ma_engine* pEngine) +{ + if (pEngine == NULL) { + return NULL; + } + + if (pEngine->pLog != NULL) { + return pEngine->pLog; + } else { + #if !defined(MA_NO_DEVICE_IO) + { + return ma_device_get_log(ma_engine_get_device(pEngine)); + } + #else + { + return NULL; + } + #endif + } +} + +MA_API ma_node* ma_engine_get_endpoint(ma_engine* pEngine) +{ + return ma_node_graph_get_endpoint(&pEngine->nodeGraph); +} + +MA_API ma_uint64 ma_engine_get_time(const ma_engine* pEngine) +{ + return ma_node_graph_get_time(&pEngine->nodeGraph); +} + +MA_API ma_result ma_engine_set_time(ma_engine* pEngine, ma_uint64 globalTime) +{ + return ma_node_graph_set_time(&pEngine->nodeGraph, globalTime); +} + +MA_API ma_uint32 ma_engine_get_channels(const ma_engine* pEngine) +{ + return ma_node_graph_get_channels(&pEngine->nodeGraph); +} + +MA_API ma_uint32 ma_engine_get_sample_rate(const ma_engine* pEngine) +{ + if (pEngine == NULL) { + return 0; + } + + return pEngine->sampleRate; +} + + +MA_API ma_result ma_engine_start(ma_engine* pEngine) +{ + ma_result result; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + result = ma_device_start(pEngine->pDevice); + } else { + result = MA_INVALID_OPERATION; /* The engine is running without a device which means there's no real notion of "starting" the engine. */ + } + } + #else + { + result = MA_INVALID_OPERATION; /* Device IO is disabled, so there's no real notion of "starting" the engine. */ + } + #endif + + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_stop(ma_engine* pEngine) +{ + ma_result result; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + #if !defined(MA_NO_DEVICE_IO) + { + if (pEngine->pDevice != NULL) { + result = ma_device_stop(pEngine->pDevice); + } else { + result = MA_INVALID_OPERATION; /* The engine is running without a device which means there's no real notion of "stopping" the engine. */ + } + } + #else + { + result = MA_INVALID_OPERATION; /* Device IO is disabled, so there's no real notion of "stopping" the engine. */ + } + #endif + + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} + +MA_API ma_result ma_engine_set_volume(ma_engine* pEngine, float volume) +{ + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_output_bus_volume(ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0, volume); +} + +MA_API ma_result ma_engine_set_gain_db(ma_engine* pEngine, float gainDB) +{ + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return ma_node_set_output_bus_volume(ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0, ma_volume_db_to_linear(gainDB)); +} + + +MA_API ma_uint32 ma_engine_get_listener_count(const ma_engine* pEngine) +{ + if (pEngine == NULL) { + return 0; + } + + return pEngine->listenerCount; +} + +MA_API ma_uint32 ma_engine_find_closest_listener(const ma_engine* pEngine, float absolutePosX, float absolutePosY, float absolutePosZ) +{ + ma_uint32 iListener; + ma_uint32 iListenerClosest; + float closestLen2 = MA_FLT_MAX; + + if (pEngine == NULL || pEngine->listenerCount == 1) { + return 0; + } + + iListenerClosest = 0; + for (iListener = 0; iListener < pEngine->listenerCount; iListener += 1) { + if (ma_engine_listener_is_enabled(pEngine, iListener)) { + float len2 = ma_vec3f_len2(ma_vec3f_sub(pEngine->listeners[iListener].position, ma_vec3f_init_3f(absolutePosX, absolutePosY, absolutePosZ))); + if (closestLen2 > len2) { + closestLen2 = len2; + iListenerClosest = iListener; + } + } + } + + MA_ASSERT(iListenerClosest < 255); + return iListenerClosest; +} + +MA_API void ma_engine_listener_set_position(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_position(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_position(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_listener_get_position(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_direction(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_direction(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_direction(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, -1); + } + + return ma_spatializer_listener_get_direction(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_velocity(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_velocity(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_velocity(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_listener_get_velocity(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_cone(ma_engine* pEngine, ma_uint32 listenerIndex, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_cone(&pEngine->listeners[listenerIndex], innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_engine_listener_get_cone(const ma_engine* pEngine, ma_uint32 listenerIndex, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = 0; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = 0; + } + + if (pOuterGain != NULL) { + *pOuterGain = 0; + } + + ma_spatializer_listener_get_cone(&pEngine->listeners[listenerIndex], pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_engine_listener_set_world_up(ma_engine* pEngine, ma_uint32 listenerIndex, float x, float y, float z) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_world_up(&pEngine->listeners[listenerIndex], x, y, z); +} + +MA_API ma_vec3f ma_engine_listener_get_world_up(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return ma_vec3f_init_3f(0, 1, 0); + } + + return ma_spatializer_listener_get_world_up(&pEngine->listeners[listenerIndex]); +} + +MA_API void ma_engine_listener_set_enabled(ma_engine* pEngine, ma_uint32 listenerIndex, ma_bool32 isEnabled) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return; + } + + ma_spatializer_listener_set_enabled(&pEngine->listeners[listenerIndex], isEnabled); +} + +MA_API ma_bool32 ma_engine_listener_is_enabled(const ma_engine* pEngine, ma_uint32 listenerIndex) +{ + if (pEngine == NULL || listenerIndex >= pEngine->listenerCount) { + return MA_FALSE; + } + + return ma_spatializer_listener_is_enabled(&pEngine->listeners[listenerIndex]); +} + + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_engine_play_sound_ex(ma_engine* pEngine, const char* pFilePath, ma_node* pNode, ma_uint32 nodeInputBusIndex) +{ + ma_result result = MA_SUCCESS; + ma_sound_inlined* pSound = NULL; + ma_sound_inlined* pNextSound = NULL; + + if (pEngine == NULL || pFilePath == NULL) { + return MA_INVALID_ARGS; + } + + /* Attach to the endpoint node if nothing is specicied. */ + if (pNode == NULL) { + pNode = ma_node_graph_get_endpoint(&pEngine->nodeGraph); + nodeInputBusIndex = 0; + } + + /* + We want to check if we can recycle an already-allocated inlined sound. Since this is just a + helper I'm not *too* concerned about performance here and I'm happy to use a lock to keep + the implementation simple. Maybe this can be optimized later if there's enough demand, but + if this function is being used it probably means the caller doesn't really care too much. + + What we do is check the atEnd flag. When this is true, we can recycle the sound. Otherwise + we just keep iterating. If we reach the end without finding a sound to recycle we just + allocate a new one. This doesn't scale well for a massive number of sounds being played + simultaneously as we don't ever actually free the sound objects. Some kind of garbage + collection routine might be valuable for this which I'll think about. + */ + ma_spinlock_lock(&pEngine->inlinedSoundLock); + { + ma_uint32 soundFlags = 0; + + for (pNextSound = pEngine->pInlinedSoundHead; pNextSound != NULL; pNextSound = pNextSound->pNext) { + if (ma_sound_at_end(&pNextSound->sound)) { + /* + The sound is at the end which means it's available for recycling. All we need to do + is uninitialize it and reinitialize it. All we're doing is recycling memory. + */ + pSound = pNextSound; + c89atomic_fetch_sub_32(&pEngine->inlinedSoundCount, 1); + break; + } + } + + if (pSound != NULL) { + /* + We actually want to detach the sound from the list here. The reason is because we want the sound + to be in a consistent state at the non-recycled case to simplify the logic below. + */ + if (pEngine->pInlinedSoundHead == pSound) { + pEngine->pInlinedSoundHead = pSound->pNext; + } + + if (pSound->pPrev != NULL) { + pSound->pPrev->pNext = pSound->pNext; + } + if (pSound->pNext != NULL) { + pSound->pNext->pPrev = pSound->pPrev; + } + + /* Now the previous sound needs to be uninitialized. */ + ma_sound_uninit(&pNextSound->sound); + } else { + /* No sound available for recycling. Allocate one now. */ + pSound = (ma_sound_inlined*)ma_malloc(sizeof(*pSound), &pEngine->allocationCallbacks); + } + + if (pSound != NULL) { /* Safety check for the allocation above. */ + /* + At this point we should have memory allocated for the inlined sound. We just need + to initialize it like a normal sound now. + */ + soundFlags |= MA_SOUND_FLAG_ASYNC; /* For inlined sounds we don't want to be sitting around waiting for stuff to load so force an async load. */ + soundFlags |= MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT; /* We want specific control over where the sound is attached in the graph. We'll attach it manually just before playing the sound. */ + soundFlags |= MA_SOUND_FLAG_NO_PITCH; /* Pitching isn't usable with inlined sounds, so disable it to save on speed. */ + soundFlags |= MA_SOUND_FLAG_NO_SPATIALIZATION; /* Not currently doing spatialization with inlined sounds, but this might actually change later. For now disable spatialization. Will be removed if we ever add support for spatialization here. */ + + result = ma_sound_init_from_file(pEngine, pFilePath, soundFlags, NULL, NULL, &pSound->sound); + if (result == MA_SUCCESS) { + /* Now attach the sound to the graph. */ + result = ma_node_attach_output_bus(pSound, 0, pNode, nodeInputBusIndex); + if (result == MA_SUCCESS) { + /* At this point the sound should be loaded and we can go ahead and add it to the list. The new item becomes the new head. */ + pSound->pNext = pEngine->pInlinedSoundHead; + pSound->pPrev = NULL; + + pEngine->pInlinedSoundHead = pSound; /* <-- This is what attaches the sound to the list. */ + if (pSound->pNext != NULL) { + pSound->pNext->pPrev = pSound; + } + } else { + ma_free(pSound, &pEngine->allocationCallbacks); + } + } else { + ma_free(pSound, &pEngine->allocationCallbacks); + } + } else { + result = MA_OUT_OF_MEMORY; + } + } + ma_spinlock_unlock(&pEngine->inlinedSoundLock); + + if (result != MA_SUCCESS) { + return result; + } + + /* Finally we can start playing the sound. */ + result = ma_sound_start(&pSound->sound); + if (result != MA_SUCCESS) { + /* Failed to start the sound. We need to mark it for recycling and return an error. */ + c89atomic_exchange_32(&pSound->sound.atEnd, MA_TRUE); + return result; + } + + c89atomic_fetch_add_32(&pEngine->inlinedSoundCount, 1); + return result; +} + +MA_API ma_result ma_engine_play_sound(ma_engine* pEngine, const char* pFilePath, ma_sound_group* pGroup) +{ + return ma_engine_play_sound_ex(pEngine, pFilePath, pGroup, 0); +} +#endif + + +static ma_result ma_sound_preinit(ma_engine* pEngine, ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pSound); + pSound->seekTarget = MA_SEEK_TARGET_NONE; + + if (pEngine == NULL) { + return MA_INVALID_ARGS; + } + + return MA_SUCCESS; +} + +static ma_result ma_sound_init_from_data_source_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result; + ma_engine_node_config engineNodeConfig; + ma_engine_node_type type; /* Will be set to ma_engine_node_type_group if no data source is specified. */ + + /* Do not clear pSound to zero here - that's done at a higher level with ma_sound_preinit(). */ + MA_ASSERT(pEngine != NULL); + MA_ASSERT(pSound != NULL); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + pSound->pDataSource = pConfig->pDataSource; + + if (pConfig->pDataSource != NULL) { + type = ma_engine_node_type_sound; + } else { + type = ma_engine_node_type_group; + } + + /* + Sounds are engine nodes. Before we can initialize this we need to determine the channel count. + If we can't do this we need to abort. It's up to the caller to ensure they're using a data + source that provides this information upfront. + */ + engineNodeConfig = ma_engine_node_config_init(pEngine, type, pConfig->flags); + engineNodeConfig.channelsIn = pConfig->channelsIn; + engineNodeConfig.channelsOut = pConfig->channelsOut; + + /* If we're loading from a data source the input channel count needs to be the data source's native channel count. */ + if (pConfig->pDataSource != NULL) { + result = ma_data_source_get_data_format(pConfig->pDataSource, NULL, &engineNodeConfig.channelsIn, &engineNodeConfig.sampleRate, NULL, 0); + if (result != MA_SUCCESS) { + return result; /* Failed to retrieve the channel count. */ + } + + if (engineNodeConfig.channelsIn == 0) { + return MA_INVALID_OPERATION; /* Invalid channel count. */ + } + + if (engineNodeConfig.channelsOut == MA_SOUND_SOURCE_CHANNEL_COUNT) { + engineNodeConfig.channelsOut = engineNodeConfig.channelsIn; + } + } + + + /* Getting here means we should have a valid channel count and we can initialize the engine node. */ + result = ma_engine_node_init(&engineNodeConfig, &pEngine->allocationCallbacks, &pSound->engineNode); + if (result != MA_SUCCESS) { + return result; + } + + /* If no attachment is specified, attach the sound straight to the endpoint. */ + if (pConfig->pInitialAttachment == NULL) { + /* No group. Attach straight to the endpoint by default, unless the caller has requested that do not. */ + if ((pConfig->flags & MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT) == 0) { + result = ma_node_attach_output_bus(pSound, 0, ma_node_graph_get_endpoint(&pEngine->nodeGraph), 0); + } + } else { + /* An attachment is specified. Attach to it by default. The sound has only a single output bus, and the config will specify which input bus to attach to. */ + result = ma_node_attach_output_bus(pSound, 0, pConfig->pInitialAttachment, pConfig->initialAttachmentInputBusIndex); + } + + if (result != MA_SUCCESS) { + ma_engine_node_uninit(&pSound->engineNode, &pEngine->allocationCallbacks); + return result; + } + + + /* Apply initial range and looping state to the data source if applicable. */ + if (pConfig->rangeBegInPCMFrames != 0 || pConfig->rangeEndInPCMFrames != ~((ma_uint64)0)) { + ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->rangeBegInPCMFrames, pConfig->rangeEndInPCMFrames); + } + + if (pConfig->loopPointBegInPCMFrames != 0 || pConfig->loopPointEndInPCMFrames != ~((ma_uint64)0)) { + ma_data_source_set_range_in_pcm_frames(ma_sound_get_data_source(pSound), pConfig->loopPointBegInPCMFrames, pConfig->loopPointEndInPCMFrames); + } + + ma_sound_set_looping(pSound, pConfig->isLooping); + + return MA_SUCCESS; +} + +#ifndef MA_NO_RESOURCE_MANAGER +MA_API ma_result ma_sound_init_from_file_internal(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result = MA_SUCCESS; + ma_uint32 flags; + ma_sound_config config; + ma_resource_manager_pipeline_notifications notifications; + + /* + The engine requires knowledge of the channel count of the underlying data source before it can + initialize the sound. Therefore, we need to make the resource manager wait until initialization + of the underlying data source to be initialized so we can get access to the channel count. To + do this, the MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT is forced. + + Because we're initializing the data source before the sound, there's a chance the notification + will get triggered before this function returns. This is OK, so long as the caller is aware of + it and can avoid accessing the sound from within the notification. + */ + flags = pConfig->flags | MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT; + + pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); + if (pSound->pResourceManagerDataSource == NULL) { + return MA_OUT_OF_MEMORY; + } + + notifications = ma_resource_manager_pipeline_notifications_init(); + notifications.done.pFence = pConfig->pDoneFence; + + /* + We must wrap everything around the fence if one was specified. This ensures ma_fence_wait() does + not return prematurely before the sound has finished initializing. + */ + if (notifications.done.pFence) { ma_fence_acquire(notifications.done.pFence); } + { + ma_resource_manager_data_source_config resourceManagerDataSourceConfig = ma_resource_manager_data_source_config_init(); + resourceManagerDataSourceConfig.pFilePath = pConfig->pFilePath; + resourceManagerDataSourceConfig.pFilePathW = pConfig->pFilePathW; + resourceManagerDataSourceConfig.flags = flags; + resourceManagerDataSourceConfig.pNotifications = ¬ifications; + resourceManagerDataSourceConfig.initialSeekPointInPCMFrames = pConfig->initialSeekPointInPCMFrames; + resourceManagerDataSourceConfig.rangeBegInPCMFrames = pConfig->rangeBegInPCMFrames; + resourceManagerDataSourceConfig.rangeEndInPCMFrames = pConfig->rangeEndInPCMFrames; + resourceManagerDataSourceConfig.loopPointBegInPCMFrames = pConfig->loopPointBegInPCMFrames; + resourceManagerDataSourceConfig.loopPointEndInPCMFrames = pConfig->loopPointEndInPCMFrames; + resourceManagerDataSourceConfig.isLooping = pConfig->isLooping; + + result = ma_resource_manager_data_source_init_ex(pEngine->pResourceManager, &resourceManagerDataSourceConfig, pSound->pResourceManagerDataSource); + if (result != MA_SUCCESS) { + goto done; + } + + pSound->ownsDataSource = MA_TRUE; /* <-- Important. Not setting this will result in the resource manager data source never getting uninitialized. */ + + /* We need to use a slightly customized version of the config so we'll need to make a copy. */ + config = *pConfig; + config.pFilePath = NULL; + config.pFilePathW = NULL; + config.pDataSource = pSound->pResourceManagerDataSource; + + result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound); + if (result != MA_SUCCESS) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + MA_ZERO_OBJECT(pSound); + goto done; + } + } +done: + if (notifications.done.pFence) { ma_fence_release(notifications.done.pFence); } + return result; +} + +MA_API ma_result ma_sound_init_from_file(ma_engine* pEngine, const char* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pFilePath = pFilePath; + config.flags = flags; + config.pInitialAttachment = pGroup; + config.pDoneFence = pDoneFence; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_from_file_w(ma_engine* pEngine, const wchar_t* pFilePath, ma_uint32 flags, ma_sound_group* pGroup, ma_fence* pDoneFence, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pFilePathW = pFilePath; + config.flags = flags; + config.pInitialAttachment = pGroup; + config.pDoneFence = pDoneFence; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_copy(ma_engine* pEngine, const ma_sound* pExistingSound, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound) +{ + ma_result result; + ma_sound_config config; + + result = ma_sound_preinit(pEngine, pSound); + if (result != MA_SUCCESS) { + return result; + } + + if (pExistingSound == NULL) { + return MA_INVALID_ARGS; + } + + /* Cloning only works for data buffers (not streams) that are loaded from the resource manager. */ + if (pExistingSound->pResourceManagerDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + /* + We need to make a clone of the data source. If the data source is not a data buffer (i.e. a stream) + the this will fail. + */ + pSound->pResourceManagerDataSource = (ma_resource_manager_data_source*)ma_malloc(sizeof(*pSound->pResourceManagerDataSource), &pEngine->allocationCallbacks); + if (pSound->pResourceManagerDataSource == NULL) { + return MA_OUT_OF_MEMORY; + } + + result = ma_resource_manager_data_source_init_copy(pEngine->pResourceManager, pExistingSound->pResourceManagerDataSource, pSound->pResourceManagerDataSource); + if (result != MA_SUCCESS) { + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + return result; + } + + config = ma_sound_config_init(); + config.pDataSource = pSound->pResourceManagerDataSource; + config.flags = flags; + config.pInitialAttachment = pGroup; + + result = ma_sound_init_from_data_source_internal(pEngine, &config, pSound); + if (result != MA_SUCCESS) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pEngine->allocationCallbacks); + MA_ZERO_OBJECT(pSound); + return result; + } + + return MA_SUCCESS; +} +#endif + +MA_API ma_result ma_sound_init_from_data_source(ma_engine* pEngine, ma_data_source* pDataSource, ma_uint32 flags, ma_sound_group* pGroup, ma_sound* pSound) +{ + ma_sound_config config = ma_sound_config_init(); + config.pDataSource = pDataSource; + config.flags = flags; + config.pInitialAttachment = pGroup; + return ma_sound_init_ex(pEngine, &config, pSound); +} + +MA_API ma_result ma_sound_init_ex(ma_engine* pEngine, const ma_sound_config* pConfig, ma_sound* pSound) +{ + ma_result result; + + result = ma_sound_preinit(pEngine, pSound); + if (result != MA_SUCCESS) { + return result; + } + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* We need to load the sound differently depending on whether or not we're loading from a file. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pConfig->pFilePath != NULL || pConfig->pFilePathW != NULL) { + return ma_sound_init_from_file_internal(pEngine, pConfig, pSound); + } else +#endif + { + /* + Getting here means we're not loading from a file. We may be loading from an already-initialized + data source, or none at all. If we aren't specifying any data source, we'll be initializing the + the equivalent to a group. ma_data_source_init_from_data_source_internal() will deal with this + for us, so no special treatment required here. + */ + return ma_sound_init_from_data_source_internal(pEngine, pConfig, pSound); + } +} + +MA_API void ma_sound_uninit(ma_sound* pSound) +{ + if (pSound == NULL) { + return; + } + + /* + Always uninitialize the node first. This ensures it's detached from the graph and does not return until it has done + so which makes thread safety beyond this point trivial. + */ + ma_engine_node_uninit(&pSound->engineNode, &pSound->engineNode.pEngine->allocationCallbacks); + + /* Once the sound is detached from the group we can guarantee that it won't be referenced by the mixer thread which means it's safe for us to destroy the data source. */ +#ifndef MA_NO_RESOURCE_MANAGER + if (pSound->ownsDataSource) { + ma_resource_manager_data_source_uninit(pSound->pResourceManagerDataSource); + ma_free(pSound->pResourceManagerDataSource, &pSound->engineNode.pEngine->allocationCallbacks); + pSound->pDataSource = NULL; + } +#else + MA_ASSERT(pSound->ownsDataSource == MA_FALSE); +#endif +} + +MA_API ma_engine* ma_sound_get_engine(const ma_sound* pSound) +{ + if (pSound == NULL) { + return NULL; + } + + return pSound->engineNode.pEngine; +} + +MA_API ma_data_source* ma_sound_get_data_source(const ma_sound* pSound) +{ + if (pSound == NULL) { + return NULL; + } + + return pSound->pDataSource; +} + +MA_API ma_result ma_sound_start(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* If the sound is already playing, do nothing. */ + if (ma_sound_is_playing(pSound)) { + return MA_SUCCESS; + } + + /* If the sound is at the end it means we want to start from the start again. */ + if (ma_sound_at_end(pSound)) { + ma_result result = ma_data_source_seek_to_pcm_frame(pSound->pDataSource, 0); + if (result != MA_SUCCESS && result != MA_NOT_IMPLEMENTED) { + return result; /* Failed to seek back to the start. */ + } + + /* Make sure we clear the end indicator. */ + c89atomic_exchange_32(&pSound->atEnd, MA_FALSE); + } + + /* Make sure the sound is started. If there's a start delay, the sound won't actually start until the start time is reached. */ + ma_node_set_state(pSound, ma_node_state_started); + + return MA_SUCCESS; +} + +MA_API ma_result ma_sound_stop(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* This will stop the sound immediately. Use ma_sound_set_stop_time() to stop the sound at a specific time. */ + ma_node_set_state(pSound, ma_node_state_stopped); + + return MA_SUCCESS; +} + +MA_API void ma_sound_set_volume(ma_sound* pSound, float volume) +{ + if (pSound == NULL) { + return; + } + + /* The volume is controlled via the output bus. */ + ma_node_set_output_bus_volume(pSound, 0, volume); +} + +MA_API float ma_sound_get_volume(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_node_get_output_bus_volume(pSound, 0); +} + +MA_API void ma_sound_set_pan(ma_sound* pSound, float pan) +{ + if (pSound == NULL) { + return; + } + + ma_panner_set_pan(&pSound->engineNode.panner, pan); +} + +MA_API float ma_sound_get_pan(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_panner_get_pan(&pSound->engineNode.panner); +} + +MA_API void ma_sound_set_pan_mode(ma_sound* pSound, ma_pan_mode panMode) +{ + if (pSound == NULL) { + return; + } + + ma_panner_set_mode(&pSound->engineNode.panner, panMode); +} + +MA_API ma_pan_mode ma_sound_get_pan_mode(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_pan_mode_balance; + } + + return ma_panner_get_mode(&pSound->engineNode.panner); +} + +MA_API void ma_sound_set_pitch(ma_sound* pSound, float pitch) +{ + if (pSound == NULL) { + return; + } + + if (pitch <= 0) { + return; + } + + c89atomic_exchange_explicit_f32(&pSound->engineNode.pitch, pitch, c89atomic_memory_order_release); +} + +MA_API float ma_sound_get_pitch(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return c89atomic_load_f32(&pSound->engineNode.pitch); /* Naughty const-cast for this. */ +} + +MA_API void ma_sound_set_spatialization_enabled(ma_sound* pSound, ma_bool32 enabled) +{ + if (pSound == NULL) { + return; + } + + c89atomic_exchange_explicit_32(&pSound->engineNode.isSpatializationDisabled, !enabled, c89atomic_memory_order_release); +} + +MA_API ma_bool32 ma_sound_is_spatialization_enabled(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + return ma_engine_node_is_spatialization_enabled(&pSound->engineNode); +} + +MA_API void ma_sound_set_pinned_listener_index(ma_sound* pSound, ma_uint32 listenerIndex) +{ + if (pSound == NULL || listenerIndex >= ma_engine_get_listener_count(ma_sound_get_engine(pSound))) { + return; + } + + c89atomic_exchange_explicit_32(&pSound->engineNode.pinnedListenerIndex, listenerIndex, c89atomic_memory_order_release); +} + +MA_API ma_uint32 ma_sound_get_pinned_listener_index(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_LISTENER_INDEX_CLOSEST; + } + + return c89atomic_load_explicit_32(&pSound->engineNode.pinnedListenerIndex, c89atomic_memory_order_acquire); +} + +MA_API ma_uint32 ma_sound_get_listener_index(const ma_sound* pSound) +{ + ma_uint32 listenerIndex; + + if (pSound == NULL) { + return 0; + } + + listenerIndex = ma_sound_get_pinned_listener_index(pSound); + if (listenerIndex == MA_LISTENER_INDEX_CLOSEST) { + ma_vec3f position = ma_sound_get_position(pSound); + return ma_engine_find_closest_listener(ma_sound_get_engine(pSound), position.x, position.y, position.z); + } + + return listenerIndex; +} + +MA_API ma_vec3f ma_sound_get_direction_to_listener(const ma_sound* pSound) +{ + ma_vec3f relativePos; + ma_engine* pEngine; + + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + pEngine = ma_sound_get_engine(pSound); + if (pEngine == NULL) { + return ma_vec3f_init_3f(0, 0, -1); + } + + ma_spatializer_get_relative_position_and_direction(&pSound->engineNode.spatializer, &pEngine->listeners[ma_sound_get_listener_index(pSound)], &relativePos, NULL); + + return ma_vec3f_normalize(ma_vec3f_neg(relativePos)); +} + +MA_API void ma_sound_set_position(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_position(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_position(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_position(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_direction(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_direction(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_direction(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_direction(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_velocity(ma_sound* pSound, float x, float y, float z) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_velocity(&pSound->engineNode.spatializer, x, y, z); +} + +MA_API ma_vec3f ma_sound_get_velocity(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_vec3f_init_3f(0, 0, 0); + } + + return ma_spatializer_get_velocity(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_attenuation_model(ma_sound* pSound, ma_attenuation_model attenuationModel) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_attenuation_model(&pSound->engineNode.spatializer, attenuationModel); +} + +MA_API ma_attenuation_model ma_sound_get_attenuation_model(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_attenuation_model_none; + } + + return ma_spatializer_get_attenuation_model(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_positioning(ma_sound* pSound, ma_positioning positioning) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_positioning(&pSound->engineNode.spatializer, positioning); +} + +MA_API ma_positioning ma_sound_get_positioning(const ma_sound* pSound) +{ + if (pSound == NULL) { + return ma_positioning_absolute; + } + + return ma_spatializer_get_positioning(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_rolloff(ma_sound* pSound, float rolloff) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_rolloff(&pSound->engineNode.spatializer, rolloff); +} + +MA_API float ma_sound_get_rolloff(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_rolloff(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_min_gain(ma_sound* pSound, float minGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_min_gain(&pSound->engineNode.spatializer, minGain); +} + +MA_API float ma_sound_get_min_gain(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_min_gain(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_max_gain(ma_sound* pSound, float maxGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_max_gain(&pSound->engineNode.spatializer, maxGain); +} + +MA_API float ma_sound_get_max_gain(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_max_gain(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_min_distance(ma_sound* pSound, float minDistance) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_min_distance(&pSound->engineNode.spatializer, minDistance); +} + +MA_API float ma_sound_get_min_distance(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_min_distance(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_max_distance(ma_sound* pSound, float maxDistance) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_max_distance(&pSound->engineNode.spatializer, maxDistance); +} + +MA_API float ma_sound_get_max_distance(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_max_distance(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_cone(ma_sound* pSound, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_cone(&pSound->engineNode.spatializer, innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_sound_get_cone(const ma_sound* pSound, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + if (pInnerAngleInRadians != NULL) { + *pInnerAngleInRadians = 0; + } + + if (pOuterAngleInRadians != NULL) { + *pOuterAngleInRadians = 0; + } + + if (pOuterGain != NULL) { + *pOuterGain = 0; + } + + ma_spatializer_get_cone(&pSound->engineNode.spatializer, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_sound_set_doppler_factor(ma_sound* pSound, float dopplerFactor) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_doppler_factor(&pSound->engineNode.spatializer, dopplerFactor); +} + +MA_API float ma_sound_get_doppler_factor(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_spatializer_get_doppler_factor(&pSound->engineNode.spatializer); +} + +MA_API void ma_sound_set_directional_attenuation_factor(ma_sound* pSound, float directionalAttenuationFactor) +{ + if (pSound == NULL) { + return; + } + + ma_spatializer_set_directional_attenuation_factor(&pSound->engineNode.spatializer, directionalAttenuationFactor); +} + +MA_API float ma_sound_get_directional_attenuation_factor(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 1; + } + + return ma_spatializer_get_directional_attenuation_factor(&pSound->engineNode.spatializer); +} + + +MA_API void ma_sound_set_fade_in_pcm_frames(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_fader_set_fade(&pSound->engineNode.fader, volumeBeg, volumeEnd, fadeLengthInFrames); +} + +MA_API void ma_sound_set_fade_in_milliseconds(ma_sound* pSound, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_fade_in_pcm_frames(pSound, volumeBeg, volumeEnd, (fadeLengthInMilliseconds * pSound->engineNode.fader.config.sampleRate) / 1000); +} + +MA_API float ma_sound_get_current_fade_volume(ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + return ma_fader_get_current_volume(&pSound->engineNode.fader); +} + +MA_API void ma_sound_set_start_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_node_set_state_time(pSound, ma_node_state_started, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_set_start_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_start_time_in_pcm_frames(pSound, absoluteGlobalTimeInMilliseconds * ma_engine_get_sample_rate(ma_sound_get_engine(pSound)) / 1000); +} + +MA_API void ma_sound_set_stop_time_in_pcm_frames(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInFrames) +{ + if (pSound == NULL) { + return; + } + + ma_node_set_state_time(pSound, ma_node_state_stopped, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_set_stop_time_in_milliseconds(ma_sound* pSound, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + if (pSound == NULL) { + return; + } + + ma_sound_set_stop_time_in_pcm_frames(pSound, absoluteGlobalTimeInMilliseconds * ma_engine_get_sample_rate(ma_sound_get_engine(pSound)) / 1000); +} + +MA_API ma_bool32 ma_sound_is_playing(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + return ma_node_get_state_by_time(pSound, ma_engine_get_time(ma_sound_get_engine(pSound))) == ma_node_state_started; +} + +MA_API ma_uint64 ma_sound_get_time_in_pcm_frames(const ma_sound* pSound) +{ + if (pSound == NULL) { + return 0; + } + + return ma_node_get_time(pSound); +} + +MA_API void ma_sound_set_looping(ma_sound* pSound, ma_bool32 isLooping) +{ + if (pSound == NULL) { + return; + } + + /* Looping is only a valid concept if the sound is backed by a data source. */ + if (pSound->pDataSource == NULL) { + return; + } + + /* The looping state needs to be applied to the data source in order for any looping to actually happen. */ + ma_data_source_set_looping(pSound->pDataSource, isLooping); +} + +MA_API ma_bool32 ma_sound_is_looping(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + /* There is no notion of looping for sounds that are not backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_FALSE; + } + + return ma_data_source_is_looping(pSound->pDataSource); +} + +MA_API ma_bool32 ma_sound_at_end(const ma_sound* pSound) +{ + if (pSound == NULL) { + return MA_FALSE; + } + + /* There is no notion of an end of a sound if it's not backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_FALSE; + } + + return c89atomic_load_32(&pSound->atEnd); +} + +MA_API ma_result ma_sound_seek_to_pcm_frame(ma_sound* pSound, ma_uint64 frameIndex) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* Seeking is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + /* We can't be seeking while reading at the same time. We just set the seek target and get the mixing thread to do the actual seek. */ + c89atomic_exchange_64(&pSound->seekTarget, frameIndex); + + return MA_SUCCESS; +} + +MA_API ma_result ma_sound_get_data_format(ma_sound* pSound, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The data format is retrieved directly from the data source if the sound is backed by one. Otherwise we pull it from the node. */ + if (pSound->pDataSource == NULL) { + ma_uint32 channels; + + if (pFormat != NULL) { + *pFormat = ma_format_f32; + } + + channels = ma_node_get_input_channels(&pSound->engineNode, 0); + if (pChannels != NULL) { + *pChannels = channels; + } + + if (pSampleRate != NULL) { + *pSampleRate = pSound->engineNode.resampler.config.sampleRateIn; + } + + if (pChannelMap != NULL) { + ma_channel_map_init_standard(ma_standard_channel_map_default, pChannelMap, channelMapCap, channels); + } + + return MA_SUCCESS; + } else { + return ma_data_source_get_data_format(pSound->pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap); + } +} + +MA_API ma_result ma_sound_get_cursor_in_pcm_frames(ma_sound* pSound, ma_uint64* pCursor) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a cursor is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_cursor_in_pcm_frames(pSound->pDataSource, pCursor); +} + +MA_API ma_result ma_sound_get_length_in_pcm_frames(ma_sound* pSound, ma_uint64* pLength) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a sound length is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_length_in_pcm_frames(pSound->pDataSource, pLength); +} + +MA_API ma_result ma_sound_get_cursor_in_seconds(ma_sound* pSound, float* pCursor) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a cursor is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_cursor_in_seconds(pSound->pDataSource, pCursor); +} + +MA_API ma_result ma_sound_get_length_in_seconds(ma_sound* pSound, float* pLength) +{ + if (pSound == NULL) { + return MA_INVALID_ARGS; + } + + /* The notion of a sound length is only valid for sounds that are backed by a data source. */ + if (pSound->pDataSource == NULL) { + return MA_INVALID_OPERATION; + } + + return ma_data_source_get_length_in_seconds(pSound->pDataSource, pLength); +} + + +MA_API ma_result ma_sound_group_init(ma_engine* pEngine, ma_uint32 flags, ma_sound_group* pParentGroup, ma_sound_group* pGroup) +{ + ma_sound_group_config config = ma_sound_group_config_init(); + config.flags = flags; + config.pInitialAttachment = pParentGroup; + return ma_sound_group_init_ex(pEngine, &config, pGroup); +} + +MA_API ma_result ma_sound_group_init_ex(ma_engine* pEngine, const ma_sound_group_config* pConfig, ma_sound_group* pGroup) +{ + ma_sound_config soundConfig; + + if (pGroup == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pGroup); + + if (pConfig == NULL) { + return MA_INVALID_ARGS; + } + + /* A sound group is just a sound without a data source. */ + soundConfig = *pConfig; + soundConfig.pFilePath = NULL; + soundConfig.pFilePathW = NULL; + soundConfig.pDataSource = NULL; + + /* + Groups need to have spatialization disabled by default because I think it'll be pretty rare + that programs will want to spatialize groups (but not unheard of). Certainly it feels like + disabling this by default feels like the right option. Spatialization can be enabled with a + call to ma_sound_group_set_spatialization_enabled(). + */ + soundConfig.flags |= MA_SOUND_FLAG_NO_SPATIALIZATION; + + return ma_sound_init_ex(pEngine, &soundConfig, pGroup); +} + +MA_API void ma_sound_group_uninit(ma_sound_group* pGroup) +{ + ma_sound_uninit(pGroup); +} + +MA_API ma_engine* ma_sound_group_get_engine(const ma_sound_group* pGroup) +{ + return ma_sound_get_engine(pGroup); +} + +MA_API ma_result ma_sound_group_start(ma_sound_group* pGroup) +{ + return ma_sound_start(pGroup); +} + +MA_API ma_result ma_sound_group_stop(ma_sound_group* pGroup) +{ + return ma_sound_stop(pGroup); +} + +MA_API void ma_sound_group_set_volume(ma_sound_group* pGroup, float volume) +{ + ma_sound_set_volume(pGroup, volume); +} + +MA_API float ma_sound_group_get_volume(const ma_sound_group* pGroup) +{ + return ma_sound_get_volume(pGroup); +} + +MA_API void ma_sound_group_set_pan(ma_sound_group* pGroup, float pan) +{ + ma_sound_set_pan(pGroup, pan); +} + +MA_API float ma_sound_group_get_pan(const ma_sound_group* pGroup) +{ + return ma_sound_get_pan(pGroup); +} + +MA_API void ma_sound_group_set_pan_mode(ma_sound_group* pGroup, ma_pan_mode panMode) +{ + ma_sound_set_pan_mode(pGroup, panMode); +} + +MA_API ma_pan_mode ma_sound_group_get_pan_mode(const ma_sound_group* pGroup) +{ + return ma_sound_get_pan_mode(pGroup); +} + +MA_API void ma_sound_group_set_pitch(ma_sound_group* pGroup, float pitch) +{ + ma_sound_set_pitch(pGroup, pitch); +} + +MA_API float ma_sound_group_get_pitch(const ma_sound_group* pGroup) +{ + return ma_sound_get_pitch(pGroup); +} + +MA_API void ma_sound_group_set_spatialization_enabled(ma_sound_group* pGroup, ma_bool32 enabled) +{ + ma_sound_set_spatialization_enabled(pGroup, enabled); +} + +MA_API ma_bool32 ma_sound_group_is_spatialization_enabled(const ma_sound_group* pGroup) +{ + return ma_sound_is_spatialization_enabled(pGroup); +} + +MA_API void ma_sound_group_set_pinned_listener_index(ma_sound_group* pGroup, ma_uint32 listenerIndex) +{ + ma_sound_set_pinned_listener_index(pGroup, listenerIndex); +} + +MA_API ma_uint32 ma_sound_group_get_pinned_listener_index(const ma_sound_group* pGroup) +{ + return ma_sound_get_pinned_listener_index(pGroup); +} + +MA_API ma_uint32 ma_sound_group_get_listener_index(const ma_sound_group* pGroup) +{ + return ma_sound_get_listener_index(pGroup); +} + +MA_API ma_vec3f ma_sound_group_get_direction_to_listener(const ma_sound_group* pGroup) +{ + return ma_sound_get_direction_to_listener(pGroup); +} + +MA_API void ma_sound_group_set_position(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_position(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_position(const ma_sound_group* pGroup) +{ + return ma_sound_get_position(pGroup); +} + +MA_API void ma_sound_group_set_direction(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_direction(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_direction(const ma_sound_group* pGroup) +{ + return ma_sound_get_direction(pGroup); +} + +MA_API void ma_sound_group_set_velocity(ma_sound_group* pGroup, float x, float y, float z) +{ + ma_sound_set_velocity(pGroup, x, y, z); +} + +MA_API ma_vec3f ma_sound_group_get_velocity(const ma_sound_group* pGroup) +{ + return ma_sound_get_velocity(pGroup); +} + +MA_API void ma_sound_group_set_attenuation_model(ma_sound_group* pGroup, ma_attenuation_model attenuationModel) +{ + ma_sound_set_attenuation_model(pGroup, attenuationModel); +} + +MA_API ma_attenuation_model ma_sound_group_get_attenuation_model(const ma_sound_group* pGroup) +{ + return ma_sound_get_attenuation_model(pGroup); +} + +MA_API void ma_sound_group_set_positioning(ma_sound_group* pGroup, ma_positioning positioning) +{ + ma_sound_set_positioning(pGroup, positioning); +} + +MA_API ma_positioning ma_sound_group_get_positioning(const ma_sound_group* pGroup) +{ + return ma_sound_get_positioning(pGroup); +} + +MA_API void ma_sound_group_set_rolloff(ma_sound_group* pGroup, float rolloff) +{ + ma_sound_set_rolloff(pGroup, rolloff); +} + +MA_API float ma_sound_group_get_rolloff(const ma_sound_group* pGroup) +{ + return ma_sound_get_rolloff(pGroup); +} + +MA_API void ma_sound_group_set_min_gain(ma_sound_group* pGroup, float minGain) +{ + ma_sound_set_min_gain(pGroup, minGain); +} + +MA_API float ma_sound_group_get_min_gain(const ma_sound_group* pGroup) +{ + return ma_sound_get_min_gain(pGroup); +} + +MA_API void ma_sound_group_set_max_gain(ma_sound_group* pGroup, float maxGain) +{ + ma_sound_set_max_gain(pGroup, maxGain); +} + +MA_API float ma_sound_group_get_max_gain(const ma_sound_group* pGroup) +{ + return ma_sound_get_max_gain(pGroup); +} + +MA_API void ma_sound_group_set_min_distance(ma_sound_group* pGroup, float minDistance) +{ + ma_sound_set_min_distance(pGroup, minDistance); +} + +MA_API float ma_sound_group_get_min_distance(const ma_sound_group* pGroup) +{ + return ma_sound_get_min_distance(pGroup); +} + +MA_API void ma_sound_group_set_max_distance(ma_sound_group* pGroup, float maxDistance) +{ + ma_sound_set_max_distance(pGroup, maxDistance); +} + +MA_API float ma_sound_group_get_max_distance(const ma_sound_group* pGroup) +{ + return ma_sound_get_max_distance(pGroup); +} + +MA_API void ma_sound_group_set_cone(ma_sound_group* pGroup, float innerAngleInRadians, float outerAngleInRadians, float outerGain) +{ + ma_sound_set_cone(pGroup, innerAngleInRadians, outerAngleInRadians, outerGain); +} + +MA_API void ma_sound_group_get_cone(const ma_sound_group* pGroup, float* pInnerAngleInRadians, float* pOuterAngleInRadians, float* pOuterGain) +{ + ma_sound_get_cone(pGroup, pInnerAngleInRadians, pOuterAngleInRadians, pOuterGain); +} + +MA_API void ma_sound_group_set_doppler_factor(ma_sound_group* pGroup, float dopplerFactor) +{ + ma_sound_set_doppler_factor(pGroup, dopplerFactor); +} + +MA_API float ma_sound_group_get_doppler_factor(const ma_sound_group* pGroup) +{ + return ma_sound_get_doppler_factor(pGroup); +} + +MA_API void ma_sound_group_set_directional_attenuation_factor(ma_sound_group* pGroup, float directionalAttenuationFactor) +{ + ma_sound_set_directional_attenuation_factor(pGroup, directionalAttenuationFactor); +} + +MA_API float ma_sound_group_get_directional_attenuation_factor(const ma_sound_group* pGroup) +{ + return ma_sound_get_directional_attenuation_factor(pGroup); +} + +MA_API void ma_sound_group_set_fade_in_pcm_frames(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInFrames) +{ + ma_sound_set_fade_in_pcm_frames(pGroup, volumeBeg, volumeEnd, fadeLengthInFrames); +} + +MA_API void ma_sound_group_set_fade_in_milliseconds(ma_sound_group* pGroup, float volumeBeg, float volumeEnd, ma_uint64 fadeLengthInMilliseconds) +{ + ma_sound_set_fade_in_milliseconds(pGroup, volumeBeg, volumeEnd, fadeLengthInMilliseconds); +} + +MA_API float ma_sound_group_get_current_fade_volume(ma_sound_group* pGroup) +{ + return ma_sound_get_current_fade_volume(pGroup); +} + +MA_API void ma_sound_group_set_start_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames) +{ + ma_sound_set_start_time_in_pcm_frames(pGroup, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_group_set_start_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + ma_sound_set_start_time_in_milliseconds(pGroup, absoluteGlobalTimeInMilliseconds); +} + +MA_API void ma_sound_group_set_stop_time_in_pcm_frames(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInFrames) +{ + ma_sound_set_stop_time_in_pcm_frames(pGroup, absoluteGlobalTimeInFrames); +} + +MA_API void ma_sound_group_set_stop_time_in_milliseconds(ma_sound_group* pGroup, ma_uint64 absoluteGlobalTimeInMilliseconds) +{ + ma_sound_set_stop_time_in_milliseconds(pGroup, absoluteGlobalTimeInMilliseconds); +} + +MA_API ma_bool32 ma_sound_group_is_playing(const ma_sound_group* pGroup) +{ + return ma_sound_is_playing(pGroup); +} + +MA_API ma_uint64 ma_sound_group_get_time_in_pcm_frames(const ma_sound_group* pGroup) +{ + return ma_sound_get_time_in_pcm_frames(pGroup); +} +#endif /* MA_NO_ENGINE */ + + + /************************************************************************************************************************************************************** *************************************************************************************************************************************************************** @@ -52852,6 +73812,7 @@ code below please report the bug to the respective repository for the relevant p #define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) #define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) #define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) +#define drwav_offset_ptr(p, offset) (((drwav_uint8*)(p)) + (offset)) #define DRWAV_MAX_SIMD_VECTOR_SIZE 64 #if defined(__x86_64__) || defined(_M_X64) #define DRWAV_X64 @@ -52864,9 +73825,14 @@ code below please report the bug to the respective repository for the relevant p #define DRWAV_INLINE __forceinline #elif defined(__GNUC__) #if defined(__STRICT_ANSI__) - #define DRWAV_INLINE __inline__ __attribute__((always_inline)) + #define DRWAV_GNUC_INLINE_HINT __inline__ #else - #define DRWAV_INLINE inline __attribute__((always_inline)) + #define DRWAV_GNUC_INLINE_HINT inline + #endif + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRWAV_INLINE DRWAV_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRWAV_INLINE __inline @@ -53095,6 +74061,9 @@ static DRWAV_INLINE void drwav__bswap_samples_pcm(void* pSamples, drwav_uint64 s { switch (bytesPerSample) { + case 1: + { + } break; case 2: { drwav__bswap_samples_s16((drwav_int16*)pSamples, sampleCount); @@ -53353,7 +74322,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_pr fmtOut->extendedSize = 0; fmtOut->validBitsPerSample = 0; fmtOut->channelMask = 0; - memset(fmtOut->subFormat, 0, sizeof(fmtOut->subFormat)); + DRWAV_ZERO_MEMORY(fmtOut->subFormat, sizeof(fmtOut->subFormat)); if (header.sizeInBytes > 16) { drwav_uint8 fmt_cbSize[2]; int bytesReadSoFar = 0; @@ -53486,7 +74455,7 @@ DRWAV_PRIVATE void drwav__metadata_request_extra_memory_for_stage_2(drwav__metad DRWAV_PRIVATE drwav_result drwav__metadata_alloc(drwav__metadata_parser* pParser, drwav_allocation_callbacks* pAllocationCallbacks) { if (pParser->extraCapacity != 0 || pParser->metadataCount != 0) { - free(pParser->pData); + pAllocationCallbacks->onFree(pParser->pData, pAllocationCallbacks->pUserData); pParser->pData = (drwav_uint8*)pAllocationCallbacks->onMalloc(drwav__metadata_memory_capacity(pParser), pAllocationCallbacks->pUserData); pParser->pDataCursor = pParser->pData; if (pParser->pData == NULL) { @@ -53505,12 +74474,13 @@ DRWAV_PRIVATE size_t drwav__metadata_parser_read(drwav__metadata_parser* pParser return pParser->onRead(pParser->pReadSeekUserData, pBufferOut, bytesToRead); } } -DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) { drwav_uint8 smplHeaderData[DRWAV_SMPL_BYTES]; drwav_uint64 totalBytesRead = 0; size_t bytesJustRead = drwav__metadata_parser_read(pParser, smplHeaderData, sizeof(smplHeaderData), &totalBytesRead); DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); + DRWAV_ASSERT(pChunkHeader != NULL); if (bytesJustRead == sizeof(smplHeaderData)) { drwav_uint32 iSampleLoop; pMetadata->type = drwav_metadata_type_smpl; @@ -53523,30 +74493,32 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars pMetadata->data.smpl.smpteOffset = drwav_bytes_to_u32(smplHeaderData + 24); pMetadata->data.smpl.sampleLoopCount = drwav_bytes_to_u32(smplHeaderData + 28); pMetadata->data.smpl.samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(smplHeaderData + 32); - pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); - for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { - drwav_uint8 smplLoopData[DRWAV_SMPL_LOOP_BYTES]; - bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); - if (bytesJustRead == sizeof(smplLoopData)) { - pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); - pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); - pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); - pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); - pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); - pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); - } else { - break; + if (pMetadata->data.smpl.sampleLoopCount == (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES) { + pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); + for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { + drwav_uint8 smplLoopData[DRWAV_SMPL_LOOP_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, smplLoopData, sizeof(smplLoopData), &totalBytesRead); + if (bytesJustRead == sizeof(smplLoopData)) { + pMetadata->data.smpl.pLoops[iSampleLoop].cuePointId = drwav_bytes_to_u32(smplLoopData + 0); + pMetadata->data.smpl.pLoops[iSampleLoop].type = drwav_bytes_to_u32(smplLoopData + 4); + pMetadata->data.smpl.pLoops[iSampleLoop].firstSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 8); + pMetadata->data.smpl.pLoops[iSampleLoop].lastSampleByteOffset = drwav_bytes_to_u32(smplLoopData + 12); + pMetadata->data.smpl.pLoops[iSampleLoop].sampleFraction = drwav_bytes_to_u32(smplLoopData + 16); + pMetadata->data.smpl.pLoops[iSampleLoop].playCount = drwav_bytes_to_u32(smplLoopData + 20); + } else { + break; + } + } + if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { + pMetadata->data.smpl.pSamplerSpecificData = drwav__metadata_get_memory(pParser, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, 1); + DRWAV_ASSERT(pMetadata->data.smpl.pSamplerSpecificData != NULL); + drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); } - } - if (pMetadata->data.smpl.samplerSpecificDataSizeInBytes > 0) { - pMetadata->data.smpl.pSamplerSpecificData = drwav__metadata_get_memory(pParser, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, 1); - DRWAV_ASSERT(pMetadata->data.smpl.pSamplerSpecificData != NULL); - bytesJustRead = drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); } } return totalBytesRead; } -DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata) +DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata* pMetadata) { drwav_uint8 cueHeaderSectionData[DRWAV_CUE_BYTES]; drwav_uint64 totalBytesRead = 0; @@ -53555,25 +74527,27 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_cue_to_metadata_obj(drwav__metadata_parse if (bytesJustRead == sizeof(cueHeaderSectionData)) { pMetadata->type = drwav_metadata_type_cue; pMetadata->data.cue.cuePointCount = drwav_bytes_to_u32(cueHeaderSectionData); - pMetadata->data.cue.pCuePoints = (drwav_cue_point*)drwav__metadata_get_memory(pParser, sizeof(drwav_cue_point) * pMetadata->data.cue.cuePointCount, DRWAV_METADATA_ALIGNMENT); - DRWAV_ASSERT(pMetadata->data.cue.pCuePoints != NULL); - if (pMetadata->data.cue.cuePointCount > 0) { - drwav_uint32 iCuePoint; - for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { - drwav_uint8 cuePointData[DRWAV_CUE_POINT_BYTES]; - bytesJustRead = drwav__metadata_parser_read(pParser, cuePointData, sizeof(cuePointData), &totalBytesRead); - if (bytesJustRead == sizeof(cuePointData)) { - pMetadata->data.cue.pCuePoints[iCuePoint].id = drwav_bytes_to_u32(cuePointData + 0); - pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition = drwav_bytes_to_u32(cuePointData + 4); - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[0] = cuePointData[8]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[1] = cuePointData[9]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[2] = cuePointData[10]; - pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; - pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); - pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); - pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); - } else { - break; + if (pMetadata->data.cue.cuePointCount == (pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES) { + pMetadata->data.cue.pCuePoints = (drwav_cue_point*)drwav__metadata_get_memory(pParser, sizeof(drwav_cue_point) * pMetadata->data.cue.cuePointCount, DRWAV_METADATA_ALIGNMENT); + DRWAV_ASSERT(pMetadata->data.cue.pCuePoints != NULL); + if (pMetadata->data.cue.cuePointCount > 0) { + drwav_uint32 iCuePoint; + for (iCuePoint = 0; iCuePoint < pMetadata->data.cue.cuePointCount; ++iCuePoint) { + drwav_uint8 cuePointData[DRWAV_CUE_POINT_BYTES]; + bytesJustRead = drwav__metadata_parser_read(pParser, cuePointData, sizeof(cuePointData), &totalBytesRead); + if (bytesJustRead == sizeof(cuePointData)) { + pMetadata->data.cue.pCuePoints[iCuePoint].id = drwav_bytes_to_u32(cuePointData + 0); + pMetadata->data.cue.pCuePoints[iCuePoint].playOrderPosition = drwav_bytes_to_u32(cuePointData + 4); + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[0] = cuePointData[8]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[1] = cuePointData[9]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[2] = cuePointData[10]; + pMetadata->data.cue.pCuePoints[iCuePoint].dataChunkId[3] = cuePointData[11]; + pMetadata->data.cue.pCuePoints[iCuePoint].chunkStart = drwav_bytes_to_u32(cuePointData + 12); + pMetadata->data.cue.pCuePoints[iCuePoint].blockStart = drwav_bytes_to_u32(cuePointData + 16); + pMetadata->data.cue.pCuePoints[iCuePoint].sampleByteOffset = drwav_bytes_to_u32(cuePointData + 20); + } else { + break; + } } } } @@ -53615,7 +74589,15 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_acid_to_metadata_obj(drwav__metadata_pars } return bytesRead; } -DRWAV_PRIVATE size_t drwav__strlen_clamped(char* str, size_t maxToRead) +DRWAV_PRIVATE size_t drwav__strlen(const char* str) +{ + size_t result = 0; + while (*str++) { + result += 1; + } + return result; +} +DRWAV_PRIVATE size_t drwav__strlen_clamped(const char* str, size_t maxToRead) { size_t result = 0; while (*str++ && result < maxToRead) { @@ -53623,71 +74605,147 @@ DRWAV_PRIVATE size_t drwav__strlen_clamped(char* str, size_t maxToRead) } return result; } -DRWAV_PRIVATE char* drwav__metadata_copy_string(drwav__metadata_parser* pParser, char* str, size_t maxToRead) +DRWAV_PRIVATE char* drwav__metadata_copy_string(drwav__metadata_parser* pParser, const char* str, size_t maxToRead) { size_t len = drwav__strlen_clamped(str, maxToRead); if (len) { char* result = (char*)drwav__metadata_get_memory(pParser, len + 1, 1); DRWAV_ASSERT(result != NULL); - memcpy(result, str, len); + DRWAV_COPY_MEMORY(result, str, len); result[len] = '\0'; return result; } else { return NULL; } } +typedef struct +{ + const void* pBuffer; + size_t sizeInBytes; + size_t cursor; +} drwav_buffer_reader; +DRWAV_PRIVATE drwav_result drwav_buffer_reader_init(const void* pBuffer, size_t sizeInBytes, drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pBuffer != NULL); + DRWAV_ASSERT(pReader != NULL); + DRWAV_ZERO_OBJECT(pReader); + pReader->pBuffer = pBuffer; + pReader->sizeInBytes = sizeInBytes; + pReader->cursor = 0; + return DRWAV_SUCCESS; +} +DRWAV_PRIVATE const void* drwav_buffer_reader_ptr(const drwav_buffer_reader* pReader) +{ + DRWAV_ASSERT(pReader != NULL); + return drwav_offset_ptr(pReader->pBuffer, pReader->cursor); +} +DRWAV_PRIVATE drwav_result drwav_buffer_reader_seek(drwav_buffer_reader* pReader, size_t bytesToSeek) +{ + DRWAV_ASSERT(pReader != NULL); + if (pReader->cursor + bytesToSeek > pReader->sizeInBytes) { + return DRWAV_BAD_SEEK; + } + pReader->cursor += bytesToSeek; + return DRWAV_SUCCESS; +} +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read(drwav_buffer_reader* pReader, void* pDst, size_t bytesToRead, size_t* pBytesRead) +{ + drwav_result result = DRWAV_SUCCESS; + size_t bytesRemaining; + DRWAV_ASSERT(pReader != NULL); + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + bytesRemaining = (pReader->sizeInBytes - pReader->cursor); + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + if (pDst == NULL) { + result = drwav_buffer_reader_seek(pReader, bytesToRead); + } else { + DRWAV_COPY_MEMORY(pDst, drwav_buffer_reader_ptr(pReader), bytesToRead); + pReader->cursor += bytesToRead; + } + DRWAV_ASSERT(pReader->cursor <= pReader->sizeInBytes); + if (result == DRWAV_SUCCESS) { + if (pBytesRead != NULL) { + *pBytesRead = bytesToRead; + } + } + return DRWAV_SUCCESS; +} +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u16(drwav_buffer_reader* pReader, drwav_uint16* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[2]; + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + *pDst = 0; + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + *pDst = drwav_bytes_to_u16(data); + return DRWAV_SUCCESS; +} +DRWAV_PRIVATE drwav_result drwav_buffer_reader_read_u32(drwav_buffer_reader* pReader, drwav_uint32* pDst) +{ + drwav_result result; + size_t bytesRead; + drwav_uint8 data[4]; + DRWAV_ASSERT(pReader != NULL); + DRWAV_ASSERT(pDst != NULL); + *pDst = 0; + result = drwav_buffer_reader_read(pReader, data, sizeof(*pDst), &bytesRead); + if (result != DRWAV_SUCCESS || bytesRead != sizeof(*pDst)) { + return result; + } + *pDst = drwav_bytes_to_u32(data); + return DRWAV_SUCCESS; +} DRWAV_PRIVATE drwav_uint64 drwav__read_bext_to_metadata_obj(drwav__metadata_parser* pParser, drwav_metadata* pMetadata, drwav_uint64 chunkSize) { drwav_uint8 bextData[DRWAV_BEXT_BYTES]; - drwav_uint64 bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); + size_t bytesRead = drwav__metadata_parser_read(pParser, bextData, sizeof(bextData), NULL); DRWAV_ASSERT(pParser->stage == drwav__metadata_parser_stage_read); if (bytesRead == sizeof(bextData)) { - drwav_uint8* pReadPointer; + drwav_buffer_reader reader; drwav_uint32 timeReferenceLow; drwav_uint32 timeReferenceHigh; size_t extraBytes; pMetadata->type = drwav_metadata_type_bext; - pReadPointer = bextData; - pMetadata->data.bext.pDescription = drwav__metadata_copy_string(pParser, (char*)(pReadPointer), DRWAV_BEXT_DESCRIPTION_BYTES); - pReadPointer += DRWAV_BEXT_DESCRIPTION_BYTES; - pMetadata->data.bext.pOriginatorName = drwav__metadata_copy_string(pParser, (char*)(pReadPointer), DRWAV_BEXT_ORIGINATOR_NAME_BYTES); - pReadPointer += DRWAV_BEXT_ORIGINATOR_NAME_BYTES; - pMetadata->data.bext.pOriginatorReference = drwav__metadata_copy_string(pParser, (char*)(pReadPointer), DRWAV_BEXT_ORIGINATOR_REF_BYTES); - pReadPointer += DRWAV_BEXT_ORIGINATOR_REF_BYTES; - memcpy(pReadPointer, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate)); - pReadPointer += sizeof(pMetadata->data.bext.pOriginationDate); - memcpy(pReadPointer, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime)); - pReadPointer += sizeof(pMetadata->data.bext.pOriginationTime); - timeReferenceLow = drwav_bytes_to_u32(pReadPointer); - pReadPointer += sizeof(drwav_uint32); - timeReferenceHigh = drwav_bytes_to_u32(pReadPointer); - pReadPointer += sizeof(drwav_uint32); - pMetadata->data.bext.timeReference = ((drwav_uint64)timeReferenceHigh << 32) + timeReferenceLow; - pMetadata->data.bext.version = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.pUMID = drwav__metadata_get_memory(pParser, DRWAV_BEXT_UMID_BYTES, 1); - memcpy(pMetadata->data.bext.pUMID, pReadPointer, DRWAV_BEXT_UMID_BYTES); - pReadPointer += DRWAV_BEXT_UMID_BYTES; - pMetadata->data.bext.loudnessValue = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.loudnessRange = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.maxTruePeakLevel = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.maxMomentaryLoudness = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - pMetadata->data.bext.maxShortTermLoudness = drwav_bytes_to_u16(pReadPointer); - pReadPointer += sizeof(drwav_uint16); - DRWAV_ASSERT((pReadPointer + DRWAV_BEXT_RESERVED_BYTES) == (bextData + DRWAV_BEXT_BYTES)); - extraBytes = (size_t)(chunkSize - DRWAV_BEXT_BYTES); - if (extraBytes > 0) { - pMetadata->data.bext.pCodingHistory = (char*)drwav__metadata_get_memory(pParser, extraBytes + 1, 1); - DRWAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); - bytesRead += drwav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); - pMetadata->data.bext.codingHistorySize = (drwav_uint32)strlen(pMetadata->data.bext.pCodingHistory); - } else { - pMetadata->data.bext.pCodingHistory = NULL; - pMetadata->data.bext.codingHistorySize = 0; + if (drwav_buffer_reader_init(bextData, bytesRead, &reader) == DRWAV_SUCCESS) { + pMetadata->data.bext.pDescription = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_DESCRIPTION_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_DESCRIPTION_BYTES); + pMetadata->data.bext.pOriginatorName = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_NAME_BYTES); + pMetadata->data.bext.pOriginatorReference = drwav__metadata_copy_string(pParser, (const char*)drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_ORIGINATOR_REF_BYTES); + drwav_buffer_reader_seek(&reader, DRWAV_BEXT_ORIGINATOR_REF_BYTES); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationDate, sizeof(pMetadata->data.bext.pOriginationDate), NULL); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pOriginationTime, sizeof(pMetadata->data.bext.pOriginationTime), NULL); + drwav_buffer_reader_read_u32(&reader, &timeReferenceLow); + drwav_buffer_reader_read_u32(&reader, &timeReferenceHigh); + pMetadata->data.bext.timeReference = ((drwav_uint64)timeReferenceHigh << 32) + timeReferenceLow; + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.version); + pMetadata->data.bext.pUMID = drwav__metadata_get_memory(pParser, DRWAV_BEXT_UMID_BYTES, 1); + drwav_buffer_reader_read(&reader, pMetadata->data.bext.pUMID, DRWAV_BEXT_UMID_BYTES, NULL); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessValue); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.loudnessRange); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxTruePeakLevel); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxMomentaryLoudness); + drwav_buffer_reader_read_u16(&reader, &pMetadata->data.bext.maxShortTermLoudness); + DRWAV_ASSERT((drwav_offset_ptr(drwav_buffer_reader_ptr(&reader), DRWAV_BEXT_RESERVED_BYTES)) == (bextData + DRWAV_BEXT_BYTES)); + extraBytes = (size_t)(chunkSize - DRWAV_BEXT_BYTES); + if (extraBytes > 0) { + pMetadata->data.bext.pCodingHistory = (char*)drwav__metadata_get_memory(pParser, extraBytes + 1, 1); + DRWAV_ASSERT(pMetadata->data.bext.pCodingHistory != NULL); + bytesRead += drwav__metadata_parser_read(pParser, pMetadata->data.bext.pCodingHistory, extraBytes, NULL); + pMetadata->data.bext.codingHistorySize = (drwav_uint32)drwav__strlen(pMetadata->data.bext.pCodingHistory); + } else { + pMetadata->data.bext.pCodingHistory = NULL; + pMetadata->data.bext.codingHistorySize = 0; + } } } return bytesRead; @@ -53707,7 +74765,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_list_label_or_note_to_metadata_obj(drwav_ pMetadata->data.labelOrNote.stringLength = sizeIncludingNullTerminator - 1; pMetadata->data.labelOrNote.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); DRWAV_ASSERT(pMetadata->data.labelOrNote.pString != NULL); - bytesJustRead = drwav__metadata_parser_read(pParser, pMetadata->data.labelOrNote.pString, sizeIncludingNullTerminator, &totalBytesRead); + drwav__metadata_parser_read(pParser, pMetadata->data.labelOrNote.pString, sizeIncludingNullTerminator, &totalBytesRead); } else { pMetadata->data.labelOrNote.stringLength = 0; pMetadata->data.labelOrNote.pString = NULL; @@ -53739,7 +74797,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_list_labelled_cue_region_to_metadata_obj( pMetadata->data.labelledCueRegion.stringLength = sizeIncludingNullTerminator - 1; pMetadata->data.labelledCueRegion.pString = (char*)drwav__metadata_get_memory(pParser, sizeIncludingNullTerminator, 1); DRWAV_ASSERT(pMetadata->data.labelledCueRegion.pString != NULL); - bytesJustRead = drwav__metadata_parser_read(pParser, pMetadata->data.labelledCueRegion.pString, sizeIncludingNullTerminator, &totalBytesRead); + drwav__metadata_parser_read(pParser, pMetadata->data.labelledCueRegion.pString, sizeIncludingNullTerminator, &totalBytesRead); } else { pMetadata->data.labelledCueRegion.stringLength = 0; pMetadata->data.labelledCueRegion.pString = NULL; @@ -53805,11 +74863,11 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_unknown_chunk(drwav__metadata } return bytesRead; } -DRWAV_PRIVATE drwav_bool32 drwav__chunk_matches(drwav_uint64 allowedMetadataTypes, const drwav_uint8* pChunkID, drwav_metadata_type type, const char* pID) +DRWAV_PRIVATE drwav_bool32 drwav__chunk_matches(drwav_metadata_type allowedMetadataTypes, const drwav_uint8* pChunkID, drwav_metadata_type type, const char* pID) { return (allowedMetadataTypes & type) && drwav_fourcc_equal(pChunkID, pID); } -DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_uint64 allowedMetadataTypes) +DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* pParser, const drwav_chunk_header* pChunkHeader, drwav_metadata_type allowedMetadataTypes) { const drwav_uint8 *pChunkID = pChunkHeader->id.fourcc; drwav_uint64 bytesRead = 0; @@ -53825,16 +74883,21 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); if (bytesJustRead == sizeof(buffer)) { drwav_uint32 loopCount = drwav_bytes_to_u32(buffer); - bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); - if (bytesJustRead == sizeof(buffer)) { - drwav_uint32 samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(buffer); - pParser->metadataCount += 1; - drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_smpl_loop) * loopCount, DRWAV_METADATA_ALIGNMENT); - drwav__metadata_request_extra_memory_for_stage_2(pParser, samplerSpecificDataSizeInBytes, 1); + drwav_uint64 calculatedLoopCount; + calculatedLoopCount = (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES; + if (calculatedLoopCount == loopCount) { + bytesJustRead = drwav__metadata_parser_read(pParser, buffer, sizeof(buffer), &bytesRead); + if (bytesJustRead == sizeof(buffer)) { + drwav_uint32 samplerSpecificDataSizeInBytes = drwav_bytes_to_u32(buffer); + pParser->metadataCount += 1; + drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_smpl_loop) * loopCount, DRWAV_METADATA_ALIGNMENT); + drwav__metadata_request_extra_memory_for_stage_2(pParser, samplerSpecificDataSizeInBytes, 1); + } + } else { } } } else { - bytesRead = drwav__read_smpl_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + bytesRead = drwav__read_smpl_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); if (bytesRead == pChunkHeader->sizeInBytes) { pParser->metadataCursor += 1; } else { @@ -53876,7 +74939,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* cueCount = (size_t)(pChunkHeader->sizeInBytes - DRWAV_CUE_BYTES) / DRWAV_CUE_POINT_BYTES; drwav__metadata_request_extra_memory_for_stage_2(pParser, sizeof(drwav_cue_point) * cueCount, DRWAV_METADATA_ALIGNMENT); } else { - bytesRead = drwav__read_cue_to_metadata_obj(pParser, &pParser->pMetadata[pParser->metadataCursor]); + bytesRead = drwav__read_cue_to_metadata_obj(pParser, pChunkHeader, &pParser->pMetadata[pParser->metadataCursor]); if (bytesRead == pChunkHeader->sizeInBytes) { pParser->metadataCursor += 1; } else { @@ -53895,19 +74958,19 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* if (bytesJustRead != DRWAV_BEXT_DESCRIPTION_BYTES) { return bytesRead; } - allocSizeNeeded += strlen(buffer) + 1; + allocSizeNeeded += drwav__strlen(buffer) + 1; buffer[DRWAV_BEXT_ORIGINATOR_NAME_BYTES] = '\0'; bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_NAME_BYTES, &bytesRead); if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_NAME_BYTES) { return bytesRead; } - allocSizeNeeded += strlen(buffer) + 1; + allocSizeNeeded += drwav__strlen(buffer) + 1; buffer[DRWAV_BEXT_ORIGINATOR_REF_BYTES] = '\0'; bytesJustRead = drwav__metadata_parser_read(pParser, buffer, DRWAV_BEXT_ORIGINATOR_REF_BYTES, &bytesRead); if (bytesJustRead != DRWAV_BEXT_ORIGINATOR_REF_BYTES) { return bytesRead; } - allocSizeNeeded += strlen(buffer) + 1; + allocSizeNeeded += drwav__strlen(buffer) + 1; allocSizeNeeded += (size_t)pChunkHeader->sizeInBytes - DRWAV_BEXT_BYTES; drwav__metadata_request_extra_memory_for_stage_2(pParser, allocSizeNeeded, 1); pParser->metadataCount += 1; @@ -53991,7 +75054,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_album); } else if (drwav__chunk_matches(allowedMetadataTypes, subchunkId, drwav_metadata_type_list_info_tracknumber, "ITRK")) { subchunkBytesRead = drwav__metadata_process_info_text_chunk(pParser, subchunkDataSize, drwav_metadata_type_list_info_tracknumber); - } else if (allowedMetadataTypes & drwav_metadata_type_unknown) { + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { subchunkBytesRead = drwav__metadata_process_unknown_chunk(pParser, subchunkId, subchunkDataSize, listType); } bytesRead += subchunkBytesRead; @@ -54010,18 +75073,25 @@ DRWAV_PRIVATE drwav_uint64 drwav__metadata_process_chunk(drwav__metadata_parser* bytesRead += 1; } } - } else if (allowedMetadataTypes & drwav_metadata_type_unknown) { + } else if ((allowedMetadataTypes & drwav_metadata_type_unknown) != 0) { bytesRead = drwav__metadata_process_unknown_chunk(pParser, pChunkID, pChunkHeader->sizeInBytes, drwav_metadata_location_top_level); } return bytesRead; } DRWAV_PRIVATE drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) { + drwav_uint32 bytesPerFrame; if ((pWav->bitsPerSample & 0x7) == 0) { - return (pWav->bitsPerSample * pWav->fmt.channels) >> 3; + bytesPerFrame = (pWav->bitsPerSample * pWav->fmt.channels) >> 3; } else { - return pWav->fmt.blockAlign; + bytesPerFrame = pWav->fmt.blockAlign; } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW || pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + if (bytesPerFrame != pWav->fmt.channels) { + return 0; + } + } + return bytesPerFrame; } DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) { @@ -54167,7 +75237,7 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { translatedFormatTag = drwav_bytes_to_u16(fmt.subFormat + 0); } - memset(&metadataParser, 0, sizeof(metadataParser)); + DRWAV_ZERO_MEMORY(&metadataParser, sizeof(metadataParser)); if (!sequential && pWav->allowedMetadataTypes != drwav_metadata_type_none && (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64)) { drwav_uint64 cursorForMetadata = cursor; metadataParser.onRead = pWav->onRead; @@ -54302,7 +75372,11 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on if (sampleCountFromFactChunk != 0) { pWav->totalPCMFrameCount = sampleCountFromFactChunk; } else { - pWav->totalPCMFrameCount = dataChunkSize / drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return DRWAV_FALSE; + } + pWav->totalPCMFrameCount = dataChunkSize / bytesPerFrame; if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { drwav_uint64 totalBlockHeaderSizeInBytes; drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; @@ -54328,6 +75402,9 @@ DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc on return DRWAV_FALSE; } } + if (drwav_get_bytes_per_pcm_frame(pWav) == 0) { + return DRWAV_FALSE; + } #ifdef DR_WAV_LIBSNDFILE_COMPAT if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; @@ -54592,7 +75669,7 @@ DRWAV_PRIVATE size_t drwav__write_or_count_metadata(drwav* pWav, drwav_metadata* bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxTruePeakLevel); bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxMomentaryLoudness); bytesWritten += drwav__write_or_count_u16ne_to_le(pWav, pMetadata->data.bext.maxShortTermLoudness); - memset(reservedBuf, 0, sizeof(reservedBuf)); + DRWAV_ZERO_MEMORY(reservedBuf, sizeof(reservedBuf)); bytesWritten += drwav__write_or_count(pWav, reservedBuf, sizeof(reservedBuf)); if (pMetadata->data.bext.codingHistorySize > 0) { bytesWritten += drwav__write_or_count(pWav, pMetadata->data.bext.pCodingHistory, pMetadata->data.bext.codingHistorySize); @@ -55829,6 +76906,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) { size_t bytesRead; + drwav_uint32 bytesPerFrame; if (pWav == NULL || bytesToRead == 0) { return 0; } @@ -55838,6 +76916,10 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu if (bytesToRead == 0) { return 0; } + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } if (pBufferOut != NULL) { bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); } else { @@ -55866,7 +76948,7 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu } } } - pWav->readCursorInPCMFrames += bytesRead / drwav_get_bytes_per_pcm_frame(pWav); + pWav->readCursorInPCMFrames += bytesRead / bytesPerFrame; pWav->bytesRemaining -= bytesRead; return bytesRead; } @@ -55897,7 +76979,11 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 frames { drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); if (pBufferOut != NULL) { - drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, drwav_get_bytes_per_pcm_frame(pWav)/pWav->channels, pWav->translatedFormatTag); + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, bytesPerFrame/pWav->channels, pWav->translatedFormatTag); } return framesRead; } @@ -55941,8 +77027,8 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF if (pWav->totalPCMFrameCount == 0) { return DRWAV_TRUE; } - if (targetFrameIndex >= pWav->totalPCMFrameCount) { - targetFrameIndex = pWav->totalPCMFrameCount - 1; + if (targetFrameIndex > pWav->totalPCMFrameCount) { + targetFrameIndex = pWav->totalPCMFrameCount; } if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { if (targetFrameIndex < pWav->readCursorInPCMFrames) { @@ -55977,10 +77063,15 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF drwav_uint64 currentBytePos; drwav_uint64 targetBytePos; drwav_uint64 offset; - totalSizeInBytes = pWav->totalPCMFrameCount * drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint32 bytesPerFrame; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return DRWAV_FALSE; + } + totalSizeInBytes = pWav->totalPCMFrameCount * bytesPerFrame; DRWAV_ASSERT(totalSizeInBytes >= pWav->bytesRemaining); currentBytePos = totalSizeInBytes - pWav->bytesRemaining; - targetBytePos = targetFrameIndex * drwav_get_bytes_per_pcm_frame(pWav); + targetBytePos = targetFrameIndex * bytesPerFrame; if (currentBytePos < targetBytePos) { offset = (targetBytePos - currentBytePos); } else { @@ -55994,7 +77085,7 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { return DRWAV_FALSE; } - pWav->readCursorInPCMFrames += offset32 / drwav_get_bytes_per_pcm_frame(pWav); + pWav->readCursorInPCMFrames += offset32 / bytesPerFrame; pWav->bytesRemaining -= offset32; offset -= offset32; } @@ -56080,6 +77171,9 @@ DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 frame bytesWritten = 0; pRunningData = (const drwav_uint8*)pData; bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels; + if (bytesPerSample == 0) { + return 0; + } while (bytesToWrite > 0) { drwav_uint8 temp[4096]; drwav_uint32 sampleCount; @@ -56278,7 +77372,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin return totalFramesRead; } pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); - pWav->ima.stepIndex[0] = header[2]; + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; pWav->ima.cachedFrameCount = 1; } else { @@ -56293,9 +77387,9 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uin return totalFramesRead; } pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); - pWav->ima.stepIndex[0] = header[2]; + pWav->ima.stepIndex[0] = drwav_clamp(header[2], 0, (drwav_int32)drwav_countof(stepTable)-1); pWav->ima.predictor[1] = drwav_bytes_to_s16(header + 4); - pWav->ima.stepIndex[1] = header[6]; + pWav->ima.stepIndex[1] = drwav_clamp(header[6], 0, (drwav_int32)drwav_countof(stepTable)-1); pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1]; pWav->ima.cachedFrameCount = 1; @@ -56409,7 +77503,7 @@ static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) } DRWAV_PRIVATE void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { - unsigned int i; + size_t i; if (bytesPerSample == 1) { drwav_u8_to_s16(pOut, pIn, totalSampleCount); return; @@ -56461,8 +77555,10 @@ DRWAV_PRIVATE void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } @@ -56470,14 +77566,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uin if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56486,8 +77593,10 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uin DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } @@ -56495,14 +77604,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_ui if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56511,8 +77631,10 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } @@ -56520,14 +77642,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_ui if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56536,8 +77669,10 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, NULL); } @@ -56545,14 +77680,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_u if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56733,49 +77879,50 @@ DRWAV_PRIVATE void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)framesRead*pWav->channels, bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { - drwav_uint64 totalFramesRead = 0; + drwav_uint64 totalFramesRead; drwav_int16 samples16[2048]; + totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); - if (framesRead == 0) { - break; - } - drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - return totalFramesRead; -} -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) -{ - drwav_uint64 totalFramesRead = 0; - drwav_int16 samples16[2048]; - while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); if (framesRead == 0) { break; } + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); drwav_s16_to_f32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; @@ -56786,8 +77933,10 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uin DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } @@ -56795,14 +77944,25 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_ui if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56811,19 +77971,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56832,19 +78006,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -56864,8 +78052,8 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 frame if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { return drwav_read_pcm_frames_f32__pcm(pWav, framesToRead, pBufferOut); } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - return drwav_read_pcm_frames_f32__msadpcm(pWav, framesToRead, pBufferOut); + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_f32__msadpcm_ima(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { return drwav_read_pcm_frames_f32__ieee(pWav, framesToRead, pBufferOut); @@ -56876,9 +78064,6 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 frame if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { return drwav_read_pcm_frames_f32__mulaw(pWav, framesToRead, pBufferOut); } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - return drwav_read_pcm_frames_f32__ima(pWav, framesToRead, pBufferOut); - } return 0; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) @@ -57035,8 +78220,10 @@ DRWAV_PRIVATE void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; + drwav_uint8 sampleData[4096] = {0}; drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } @@ -57044,44 +78231,41 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uin if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } return totalFramesRead; } -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm_ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); - if (framesRead == 0) { - break; - } - drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; - framesToRead -= framesRead; - totalFramesRead += framesRead; - } - return totalFramesRead; -} -DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) -{ - drwav_uint64 totalFramesRead = 0; - drwav_int16 samples16[2048]; - while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels); + drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToReadThisIteration, samples16); if (framesRead == 0) { break; } + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); drwav_s16_to_s32(pBufferOut, samples16, (size_t)(framesRead*pWav->channels)); pBufferOut += framesRead*pWav->channels; framesToRead -= framesRead; @@ -57092,19 +78276,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uin DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels), bytesPerFrame/pWav->channels); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -57113,19 +78311,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -57134,19 +78346,33 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_ui DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; - drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + drwav_uint8 sampleData[4096] = {0}; + drwav_uint32 bytesPerFrame; + drwav_uint32 bytesPerSample; + drwav_uint64 samplesRead; + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } + bytesPerSample = bytesPerFrame / pWav->channels; + if (bytesPerSample == 0 || (bytesPerFrame % pWav->channels) != 0) { + return 0; + } totalFramesRead = 0; while (framesToRead > 0) { - drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); + drwav_uint64 framesToReadThisIteration = drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame); + drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, framesToReadThisIteration, sampleData); if (framesRead == 0) { break; } - drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)(framesRead*pWav->channels)); - pBufferOut += framesRead*pWav->channels; + DRWAV_ASSERT(framesRead <= framesToReadThisIteration); + samplesRead = framesRead * pWav->channels; + if ((samplesRead * bytesPerSample) > sizeof(sampleData)) { + DRWAV_ASSERT(DRWAV_FALSE); + break; + } + drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + pBufferOut += samplesRead; framesToRead -= framesRead; totalFramesRead += framesRead; } @@ -57166,8 +78392,8 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 frame if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut); } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { - return drwav_read_pcm_frames_s32__msadpcm(pWav, framesToRead, pBufferOut); + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_pcm_frames_s32__msadpcm_ima(pWav, framesToRead, pBufferOut); } if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { return drwav_read_pcm_frames_s32__ieee(pWav, framesToRead, pBufferOut); @@ -57178,9 +78404,6 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 frame if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { return drwav_read_pcm_frames_s32__mulaw(pWav, framesToRead, pBufferOut); } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { - return drwav_read_pcm_frames_s32__ima(pWav, framesToRead, pBufferOut); - } return 0; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) @@ -57677,9 +78900,14 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #define DRFLAC_INLINE __forceinline #elif defined(__GNUC__) #if defined(__STRICT_ANSI__) - #define DRFLAC_INLINE __inline__ __attribute__((always_inline)) + #define DRFLAC_GNUC_INLINE_HINT __inline__ #else - #define DRFLAC_INLINE inline __attribute__((always_inline)) + #define DRFLAC_GNUC_INLINE_HINT inline + #endif + #if (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 2)) || defined(__clang__) + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT __attribute__((always_inline)) + #else + #define DRFLAC_INLINE DRFLAC_GNUC_INLINE_HINT #endif #elif defined(__WATCOMC__) #define DRFLAC_INLINE __inline @@ -57690,7 +78918,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #define DRFLAC_X64 #elif defined(__i386) || defined(_M_IX86) #define DRFLAC_X86 -#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) +#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64) #define DRFLAC_ARM #endif #if !defined(DR_FLAC_NO_SIMD) @@ -57727,13 +78955,6 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #if defined(DRFLAC_ARM) #if !defined(DRFLAC_NO_NEON) && (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) #define DRFLAC_SUPPORT_NEON - #endif - #if !defined(__GNUC__) && !defined(__clang__) && defined(__has_include) - #if !defined(DRFLAC_SUPPORT_NEON) && !defined(DRFLAC_NO_NEON) && __has_include() - #define DRFLAC_SUPPORT_NEON - #endif - #endif - #if defined(DRFLAC_SUPPORT_NEON) #include #endif #endif @@ -58136,6 +79357,11 @@ static DRFLAC_INLINE drflac_uint32 drflac__be2host_32(drflac_uint32 n) } return n; } +static DRFLAC_INLINE drflac_uint32 drflac__be2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *(pNum) << 24 | *(pNum+1) << 16 | *(pNum+2) << 8 | *(pNum+3); +} static DRFLAC_INLINE drflac_uint64 drflac__be2host_64(drflac_uint64 n) { if (drflac__is_little_endian()) { @@ -58150,6 +79376,11 @@ static DRFLAC_INLINE drflac_uint32 drflac__le2host_32(drflac_uint32 n) } return n; } +static DRFLAC_INLINE drflac_uint32 drflac__le2host_32_ptr_unaligned(const void* pData) +{ + const drflac_uint8* pNum = (drflac_uint8*)pData; + return *pNum | *(pNum+1) << 8 | *(pNum+2) << 16 | *(pNum+3) << 24; +} static DRFLAC_INLINE drflac_uint32 drflac__unsynchsafe_32(drflac_uint32 n) { drflac_uint32 result = 0; @@ -58535,6 +79766,9 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned i if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } *pResultOut = (resultHi << bitCountLo) | (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountLo); bs->consumedBits += bitCountLo; bs->cache <<= bitCountLo; @@ -58876,8 +80110,18 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, return DRFLAC_FALSE; } } + if (bs->cache == 1) { + *pOffsetOut = zeroCounter + (drflac_uint32)DRFLAC_CACHE_L1_BITS_REMAINING(bs) - 1; + if (!drflac__reload_cache(bs)) { + return DRFLAC_FALSE; + } + return DRFLAC_TRUE; + } setBitOffsetPlus1 = drflac__clz(bs->cache); setBitOffsetPlus1 += 1; + if (setBitOffsetPlus1 > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } bs->consumedBits += setBitOffsetPlus1; bs->cache <<= setBitOffsetPlus1; *pOffsetOut = zeroCounter + setBitOffsetPlus1 - 1; @@ -58963,6 +80207,24 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 *pCRCOut = crc; return DRFLAC_SUCCESS; } +static DRFLAC_INLINE drflac_uint32 drflac__ilog2_u32(drflac_uint32 x) +{ +#if 1 + drflac_uint32 result = 0; + while (x > 0) { + result += 1; + x >>= 1; + } + return result; +#endif +} +static DRFLAC_INLINE drflac_bool32 drflac__use_64_bit_prediction(drflac_uint32 bitsPerSample, drflac_uint32 order, drflac_uint32 precision) +{ + return bitsPerSample + precision + drflac__ilog2_u32(order) > 32; +} +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_int32 prediction = 0; @@ -59173,7 +80435,7 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 return (drflac_int32)(prediction >> shift); } #if 0 -static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); @@ -59205,10 +80467,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla } else { decodedRice = (decodedRice >> 1); } - if (bitsPerSample+shift >= 32) { - pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { - pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); + pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } return DRFLAC_TRUE; @@ -59287,6 +80549,9 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (bitCountLo > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } } riceParamPart = (drflac_uint32)(resultHi | DRFLAC_CACHE_L1_SELECT_AND_SHIFT_SAFE(bs, bitCountLo)); bs->consumedBits += bitCountLo; @@ -59334,6 +80599,9 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drf if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; } @@ -59403,6 +80671,9 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; } + if (riceParamPartLoBitCount > DRFLAC_CACHE_L1_BITS_REMAINING(bs)) { + return DRFLAC_FALSE; + } bs_cache = bs->cache; bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; } @@ -59464,7 +80735,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorde } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; drflac_uint32 zeroCountPart0 = 0; @@ -59480,12 +80751,12 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); - if (order == 0) { - return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder == 0) { + return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } riceParamMask = (drflac_uint32)~((~0UL) << riceParam); pSamplesOutEnd = pSamplesOut + (count & ~3); - if (bitsPerSample+shift > 32) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { while (pSamplesOut < pSamplesOutEnd) { if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0) || !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart1, &riceParamPart1) || @@ -59505,10 +80776,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); - pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 1); - pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 2); - pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 3); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); pSamplesOut += 4; } } else { @@ -59531,10 +80802,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b riceParamPart1 = (riceParamPart1 >> 1) ^ t[riceParamPart1 & 0x01]; riceParamPart2 = (riceParamPart2 >> 1) ^ t[riceParamPart2 & 0x01]; riceParamPart3 = (riceParamPart3 >> 1) ^ t[riceParamPart3 & 0x01]; - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); - pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1); - pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2); - pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); + pSamplesOut[1] = riceParamPart1 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 1); + pSamplesOut[2] = riceParamPart2 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 2); + pSamplesOut[3] = riceParamPart3 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 3); pSamplesOut += 4; } } @@ -59546,10 +80817,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b riceParamPart0 &= riceParamMask; riceParamPart0 |= (zeroCountPart0 << riceParam); riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; - if (bitsPerSample+shift > 32) { - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); } else { - pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); + pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + 0); } i += 1; pSamplesOut += 1; @@ -59894,18 +81165,18 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); - if (order > 0 && order <= 12) { - if (bitsPerSample+shift > 32) { - return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } else { - return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } } else { - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } } #endif @@ -60244,37 +81515,37 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_ } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); DRFLAC_ASSERT(pSamplesOut != NULL); - if (order > 0 && order <= 12) { - if (bitsPerSample+shift > 32) { - return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + if (lpcOrder > 0 && lpcOrder <= 12) { + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } else { - return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, lpcOrder, lpcShift, coefficients, pSamplesOut); } } else { - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } } #endif -static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { #if defined(DRFLAC_SUPPORT_SSE41) if (drflac__gIsSSE41Supported) { - return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } else #elif defined(DRFLAC_SUPPORT_NEON) if (drflac__gIsNEONSupported) { - return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); } else #endif { #if 0 - return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__reference(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); #else - return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pSamplesOut); #endif } } @@ -60289,7 +81560,10 @@ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_ } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +#if defined(__clang__) +__attribute__((no_sanitize("signed-integer-overflow"))) +#endif +static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 unencodedBitsPerSample, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); @@ -60303,15 +81577,15 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* } else { pSamplesOut[i] = 0; } - if (bitsPerSample >= 24) { - pSamplesOut[i] += drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); + if (drflac__use_64_bit_prediction(bitsPerSample, lpcOrder, lpcPrecision)) { + pSamplesOut[i] += drflac__calculate_prediction_64(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } else { - pSamplesOut[i] += drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); + pSamplesOut[i] += drflac__calculate_prediction_32(lpcOrder, lpcShift, coefficients, pSamplesOut + i); } } return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) +static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 blockSize, drflac_uint32 lpcOrder, drflac_int32 lpcShift, drflac_uint32 lpcPrecision, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) { drflac_uint8 residualMethod; drflac_uint8 partitionOrder; @@ -60326,17 +81600,17 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ if (residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE && residualMethod != DRFLAC_RESIDUAL_CODING_METHOD_PARTITIONED_RICE2) { return DRFLAC_FALSE; } - pDecodedSamples += order; + pDecodedSamples += lpcOrder; if (!drflac__read_uint8(bs, 4, &partitionOrder)) { return DRFLAC_FALSE; } if (partitionOrder > 8) { return DRFLAC_FALSE; } - if ((blockSize / (1 << partitionOrder)) < order) { + if ((blockSize / (1 << partitionOrder)) < lpcOrder) { return DRFLAC_FALSE; } - samplesInPartition = (blockSize / (1 << partitionOrder)) - order; + samplesInPartition = (blockSize / (1 << partitionOrder)) - lpcOrder; partitionsRemaining = (1 << partitionOrder); for (;;) { drflac_uint8 riceParam = 0; @@ -60356,7 +81630,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ } } if (riceParam != 0xFF) { - if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, order, shift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual__rice(bs, bitsPerSample, samplesInPartition, riceParam, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } } else { @@ -60364,7 +81638,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { return DRFLAC_FALSE; } - if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, order, shift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual__unencoded(bs, bitsPerSample, samplesInPartition, unencodedBitsPerSample, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } } @@ -60484,7 +81758,7 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 } pDecodedSamples[i] = sample; } - if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { + if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, 4, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { return DRFLAC_FALSE; } return DRFLAC_TRUE; @@ -60521,7 +81795,7 @@ static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 bl return DRFLAC_FALSE; } } - if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, coefficients, pDecodedSamples)) { + if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, lpcShift, lpcPrecision, coefficients, pDecodedSamples)) { return DRFLAC_FALSE; } return DRFLAC_TRUE; @@ -60630,6 +81904,9 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u return DRFLAC_FALSE; } crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16); + if (header->blockSizeInPCMFrames == 0xFFFF) { + return DRFLAC_FALSE; + } header->blockSizeInPCMFrames += 1; } else { DRFLAC_ASSERT(blockSize >= 8); @@ -60662,6 +81939,9 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u if (header->bitsPerSample == 0) { header->bitsPerSample = streaminfoBitsPerSample; } + if (header->bitsPerSample != streaminfoBitsPerSample) { + return DRFLAC_FALSE; + } if (!drflac__read_uint8(bs, 8, &header->crc8)) { return DRFLAC_FALSE; } @@ -60732,6 +82012,9 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { subframeBitsPerSample += 1; } + if (subframeBitsPerSample > 32) { + return DRFLAC_FALSE; + } if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { return DRFLAC_FALSE; } @@ -61215,6 +82498,9 @@ static drflac_bool32 drflac__seek_to_pcm_frame__seek_table(drflac* pFlac, drflac if (pFlac->pSeekpoints == NULL || pFlac->seekpointCount == 0) { return DRFLAC_FALSE; } + if (pFlac->pSeekpoints[0].firstPCMFrame > pcmFrameIndex) { + return DRFLAC_FALSE; + } for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { if (pFlac->pSeekpoints[iSeekpoint].firstPCMFrame >= pcmFrameIndex) { break; @@ -61558,13 +82844,13 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d metadata.rawDataSize = blockSize; pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; - metadata.data.vorbis_comment.vendorLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.vorbis_comment.vendorLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; - metadata.data.vorbis_comment.commentCount = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.vorbis_comment.commentCount = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; @@ -61576,7 +82862,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } - commentLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + commentLength = drflac__le2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; @@ -61660,24 +82946,24 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d metadata.rawDataSize = blockSize; pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; - metadata.data.picture.type = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.mimeLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.type = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.mimeLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; - metadata.data.picture.descriptionLength = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.descriptionLength = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; - metadata.data.picture.width = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.height = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.colorDepth = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.indexColorCount = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; - metadata.data.picture.pictureDataSize = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; + metadata.data.picture.width = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.height = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.colorDepth = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.indexColorCount = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; + metadata.data.picture.pictureDataSize = drflac__be2host_32_ptr_unaligned(pRunningData); pRunningData += 4; metadata.data.picture.pPictureData = (const drflac_uint8*)pRunningData; if (pRunningDataEnd - pRunningData < (drflac_int64)metadata.data.picture.pictureDataSize) { drflac__free_from_callbacks(pRawData, pAllocationCallbacks); @@ -62563,7 +83849,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac #ifndef DR_FLAC_NO_OGG if (init.container == drflac_container_ogg) { drflac_oggbs* pInternalOggbs = (drflac_oggbs*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize + seektableSize); - *pInternalOggbs = oggbs; + DRFLAC_COPY_MEMORY(pInternalOggbs, &oggbs, sizeof(oggbs)); pFlac->bs.onRead = drflac__on_read_ogg; pFlac->bs.onSeek = drflac__on_seek_ogg; pFlac->bs.pUserData = (void*)pInternalOggbs; @@ -65798,7 +87084,7 @@ DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator if (pIter == NULL || pIter->countRemaining == 0 || pIter->pRunningData == NULL) { return NULL; } - length = drflac__le2host_32(*(const drflac_uint32*)pIter->pRunningData); + length = drflac__le2host_32_ptr_unaligned(pIter->pRunningData); pIter->pRunningData += 4; pComment = pIter->pRunningData; pIter->pRunningData += length; @@ -67301,7 +88587,11 @@ static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) vst1_lane_s16(dstl + (49 + i)*nch, pcmb, 2); #endif #else + #if DRMP3_HAVE_SSE static const drmp3_f4 g_scale = { 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f, 1.0f/32768.0f }; + #else + const drmp3_f4 g_scale = vdupq_n_f32(1.0f/32768.0f); + #endif a = DRMP3_VMUL(a, g_scale); b = DRMP3_VMUL(b, g_scale); #if DRMP3_HAVE_SSE @@ -67575,7 +88865,6 @@ DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num } } } -#include #if defined(SIZE_MAX) #define DRMP3_SIZE_MAX SIZE_MAX #else @@ -67621,18 +88910,6 @@ static DRMP3_INLINE drmp3_uint32 drmp3_gcf_u32(drmp3_uint32 a, drmp3_uint32 b) } return a; } -static DRMP3_INLINE double drmp3_sin(double x) -{ - return sin(x); -} -static DRMP3_INLINE double drmp3_exp(double x) -{ - return exp(x); -} -static DRMP3_INLINE double drmp3_cos(double x) -{ - return drmp3_sin((DRMP3_PI_D*0.5) - x); -} static void* drmp3__malloc_default(size_t sz, void* pUserData) { (void)pUserData; @@ -69134,1093 +90411,6 @@ DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocation #endif /* miniaudio_c */ #endif /* MINIAUDIO_IMPLEMENTATION */ -/* -RELEASE NOTES - VERSION 0.10.x -============================== -Version 0.10 includes major API changes and refactoring, mostly concerned with the data conversion system. Data conversion is performed internally to convert -audio data between the format requested when initializing the `ma_device` object and the format of the internal device used by the backend. The same applies -to the `ma_decoder` object. The previous design has several design flaws and missing features which necessitated a complete redesign. - - -Changes to Data Conversion --------------------------- -The previous data conversion system used callbacks to deliver input data for conversion. This design works well in some specific situations, but in other -situations it has some major readability and maintenance issues. The decision was made to replace this with a more iterative approach where you just pass in a -pointer to the input data directly rather than dealing with a callback. - -The following are the data conversion APIs that have been removed and their replacements: - - - ma_format_converter -> ma_convert_pcm_frames_format() - - ma_channel_router -> ma_channel_converter - - ma_src -> ma_resampler - - ma_pcm_converter -> ma_data_converter - -The previous conversion APIs accepted a callback in their configs. There are no longer any callbacks to deal with. Instead you just pass the data into the -`*_process_pcm_frames()` function as a pointer to a buffer. - -The simplest aspect of data conversion is sample format conversion. To convert between two formats, just call `ma_convert_pcm_frames_format()`. Channel -conversion is also simple which you can do with `ma_channel_converter` via `ma_channel_converter_process_pcm_frames()`. - -Resampling is more complicated because the number of output frames that are processed is different to the number of input frames that are consumed. When you -call `ma_resampler_process_pcm_frames()` you need to pass in the number of input frames available for processing and the number of output frames you want to -output. Upon returning they will receive the number of input frames that were consumed and the number of output frames that were generated. - -The `ma_data_converter` API is a wrapper around format, channel and sample rate conversion and handles all of the data conversion you'll need which probably -makes it the best option if you need to do data conversion. - -In addition to changes to the API design, a few other changes have been made to the data conversion pipeline: - - - The sinc resampler has been removed. This was completely broken and never actually worked properly. - - The linear resampler now uses low-pass filtering to remove aliasing. The quality of the low-pass filter can be controlled via the resampler config with the - `lpfOrder` option, which has a maximum value of MA_MAX_FILTER_ORDER. - - Data conversion now supports s16 natively which runs through a fixed point pipeline. Previously everything needed to be converted to floating point before - processing, whereas now both s16 and f32 are natively supported. Other formats still require conversion to either s16 or f32 prior to processing, however - `ma_data_converter` will handle this for you. - - -Custom Memory Allocators ------------------------- -miniaudio has always supported macro level customization for memory allocation via MA_MALLOC, MA_REALLOC and MA_FREE, however some scenarios require more -flexibility by allowing a user data pointer to be passed to the custom allocation routines. Support for this has been added to version 0.10 via the -`ma_allocation_callbacks` structure. Anything making use of heap allocations has been updated to accept this new structure. - -The `ma_context_config` structure has been updated with a new member called `allocationCallbacks`. Leaving this set to it's defaults returned by -`ma_context_config_init()` will cause it to use MA_MALLOC, MA_REALLOC and MA_FREE. Likewise, The `ma_decoder_config` structure has been updated in the same -way, and leaving everything as-is after `ma_decoder_config_init()` will cause it to use the same defaults. - -The following APIs have been updated to take a pointer to a `ma_allocation_callbacks` object. Setting this parameter to NULL will cause it to use defaults. -Otherwise they will use the relevant callback in the structure. - - - ma_malloc() - - ma_realloc() - - ma_free() - - ma_aligned_malloc() - - ma_aligned_free() - - ma_rb_init() / ma_rb_init_ex() - - ma_pcm_rb_init() / ma_pcm_rb_init_ex() - -Note that you can continue to use MA_MALLOC, MA_REALLOC and MA_FREE as per normal. These will continue to be used by default if you do not specify custom -allocation callbacks. - - -Buffer and Period Configuration Changes ---------------------------------------- -The way in which the size of the internal buffer and periods are specified in the device configuration have changed. In previous versions, the config variables -`bufferSizeInFrames` and `bufferSizeInMilliseconds` defined the size of the entire buffer, with the size of a period being the size of this variable divided by -the period count. This became confusing because people would expect the value of `bufferSizeInFrames` or `bufferSizeInMilliseconds` to independantly determine -latency, when in fact it was that value divided by the period count that determined it. These variables have been removed and replaced with new ones called -`periodSizeInFrames` and `periodSizeInMilliseconds`. - -These new configuration variables work in the same way as their predecessors in that if one is set to 0, the other will be used, but the main difference is -that you now set these to you desired latency rather than the size of the entire buffer. The benefit of this is that it's much easier and less confusing to -configure latency. - -The following unused APIs have been removed: - - ma_get_default_buffer_size_in_milliseconds() - ma_get_default_buffer_size_in_frames() - -The following macros have been removed: - - MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_LOW_LATENCY - MA_BASE_BUFFER_SIZE_IN_MILLISECONDS_CONSERVATIVE - - -Other API Changes ------------------ -Other less major API changes have also been made in version 0.10. - -`ma_device_set_stop_callback()` has been removed. If you require a stop callback, you must now set it via the device config just like the data callback. - -The `ma_sine_wave` API has been replaced with a more general API called `ma_waveform`. This supports generation of different types of waveforms, including -sine, square, triangle and sawtooth. Use `ma_waveform_init()` in place of `ma_sine_wave_init()` to initialize the waveform object. This takes a configuration -object called `ma_waveform_config` which defines the properties of the waveform. Use `ma_waveform_config_init()` to initialize a `ma_waveform_config` object. -Use `ma_waveform_read_pcm_frames()` in place of `ma_sine_wave_read_f32()` and `ma_sine_wave_read_f32_ex()`. - -`ma_convert_frames()` and `ma_convert_frames_ex()` have been changed. Both of these functions now take a new parameter called `frameCountOut` which specifies -the size of the output buffer in PCM frames. This has been added for safety. In addition to this, the parameters for `ma_convert_frames_ex()` have changed to -take a pointer to a `ma_data_converter_config` object to specify the input and output formats to convert between. This was done to make it more flexible, to -prevent the parameter list getting too long, and to prevent API breakage whenever a new conversion property is added. - -`ma_calculate_frame_count_after_src()` has been renamed to `ma_calculate_frame_count_after_resampling()` for consistency with the new `ma_resampler` API. - - -Filters -------- -The following filters have been added: - - |-------------|-------------------------------------------------------------------| - | API | Description | - |-------------|-------------------------------------------------------------------| - | ma_biquad | Biquad filter (transposed direct form 2) | - | ma_lpf1 | First order low-pass filter | - | ma_lpf2 | Second order low-pass filter | - | ma_lpf | High order low-pass filter (Butterworth) | - | ma_hpf1 | First order high-pass filter | - | ma_hpf2 | Second order high-pass filter | - | ma_hpf | High order high-pass filter (Butterworth) | - | ma_bpf2 | Second order band-pass filter | - | ma_bpf | High order band-pass filter | - | ma_peak2 | Second order peaking filter | - | ma_notch2 | Second order notching filter | - | ma_loshelf2 | Second order low shelf filter | - | ma_hishelf2 | Second order high shelf filter | - |-------------|-------------------------------------------------------------------| - -These filters all support 32-bit floating point and 16-bit signed integer formats natively. Other formats need to be converted beforehand. - - -Sine, Square, Triangle and Sawtooth Waveforms ---------------------------------------------- -Previously miniaudio supported only sine wave generation. This has now been generalized to support sine, square, triangle and sawtooth waveforms. The old -`ma_sine_wave` API has been removed and replaced with the `ma_waveform` API. Use `ma_waveform_config_init()` to initialize a config object, and then pass it -into `ma_waveform_init()`. Then use `ma_waveform_read_pcm_frames()` to read PCM data. - - -Noise Generation ----------------- -A noise generation API has been added. This is used via the `ma_noise` API. Currently white, pink and Brownian noise is supported. The `ma_noise` API is -similar to the waveform API. Use `ma_noise_config_init()` to initialize a config object, and then pass it into `ma_noise_init()` to initialize a `ma_noise` -object. Then use `ma_noise_read_pcm_frames()` to read PCM data. - - -Miscellaneous Changes ---------------------- -The MA_NO_STDIO option has been removed. This would disable file I/O APIs, however this has proven to be too hard to maintain for it's perceived value and was -therefore removed. - -Internal functions have all been made static where possible. If you get warnings about unused functions, please submit a bug report. - -The `ma_device` structure is no longer defined as being aligned to MA_SIMD_ALIGNMENT. This resulted in a possible crash when allocating a `ma_device` object on -the heap, but not aligning it to MA_SIMD_ALIGNMENT. This crash would happen due to the compiler seeing the alignment specified on the structure and assuming it -was always aligned as such and thinking it was safe to emit alignment-dependant SIMD instructions. Since miniaudio's philosophy is for things to just work, -this has been removed from all structures. - -Results codes have been overhauled. Unnecessary result codes have been removed, and some have been renumbered for organisation purposes. If you are are binding -maintainer you will need to update your result codes. Support has also been added for retrieving a human readable description of a given result code via the -`ma_result_description()` API. - -ALSA: The automatic format conversion, channel conversion and resampling performed by ALSA is now disabled by default as they were causing some compatibility -issues with certain devices and configurations. These can be individually enabled via the device config: - - ```c - deviceConfig.alsa.noAutoFormat = MA_TRUE; - deviceConfig.alsa.noAutoChannels = MA_TRUE; - deviceConfig.alsa.noAutoResample = MA_TRUE; - ``` -*/ - -/* -RELEASE NOTES - VERSION 0.9.x -============================= -Version 0.9 includes major API changes, centered mostly around full-duplex and the rebrand to "miniaudio". Before I go into detail about the major changes I -would like to apologize. I know it's annoying dealing with breaking API changes, but I think it's best to get these changes out of the way now while the -library is still relatively young and unknown. - -There's been a lot of refactoring with this release so there's a good chance a few bugs have been introduced. I apologize in advance for this. You may want to -hold off on upgrading for the short term if you're worried. If mini_al v0.8.14 works for you, and you don't need full-duplex support, you can avoid upgrading -(though you won't be getting future bug fixes). - - -Rebranding to "miniaudio" -------------------------- -The decision was made to rename mini_al to miniaudio. Don't worry, it's the same project. The reason for this is simple: - -1) Having the word "audio" in the title makes it immediately clear that the library is related to audio; and -2) I don't like the look of the underscore. - -This rebrand has necessitated a change in namespace from "mal" to "ma". I know this is annoying, and I apologize, but it's better to get this out of the road -now rather than later. Also, since there are necessary API changes for full-duplex support I think it's better to just get the namespace change over and done -with at the same time as the full-duplex changes. I'm hoping this will be the last of the major API changes. Fingers crossed! - -The implementation define is now "#define MINIAUDIO_IMPLEMENTATION". You can also use "#define MA_IMPLEMENTATION" if that's your preference. - - -Full-Duplex Support -------------------- -The major feature added to version 0.9 is full-duplex. This has necessitated a few API changes. - -1) The data callback has now changed. Previously there was one type of callback for playback and another for capture. I wanted to avoid a third callback just - for full-duplex so the decision was made to break this API and unify the callbacks. Now, there is just one callback which is the same for all three modes - (playback, capture, duplex). The new callback looks like the following: - - void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); - - This callback allows you to move data straight out of the input buffer and into the output buffer in full-duplex mode. In playback-only mode, pInput will be - null. Likewise, pOutput will be null in capture-only mode. The sample count is no longer returned from the callback since it's not necessary for miniaudio - anymore. - -2) The device config needed to change in order to support full-duplex. Full-duplex requires the ability to allow the client to choose a different PCM format - for the playback and capture sides. The old ma_device_config object simply did not allow this and needed to change. With these changes you now specify the - device ID, format, channels, channel map and share mode on a per-playback and per-capture basis (see example below). The sample rate must be the same for - playback and capture. - - Since the device config API has changed I have also decided to take the opportunity to simplify device initialization. Now, the device ID, device type and - callback user data are set in the config. ma_device_init() is now simplified down to taking just the context, device config and a pointer to the device - object being initialized. The rationale for this change is that it just makes more sense to me that these are set as part of the config like everything - else. - - Example device initialization: - - ma_device_config config = ma_device_config_init(ma_device_type_duplex); // Or ma_device_type_playback or ma_device_type_capture. - config.playback.pDeviceID = &myPlaybackDeviceID; // Or NULL for the default playback device. - config.playback.format = ma_format_f32; - config.playback.channels = 2; - config.capture.pDeviceID = &myCaptureDeviceID; // Or NULL for the default capture device. - config.capture.format = ma_format_s16; - config.capture.channels = 1; - config.sampleRate = 44100; - config.dataCallback = data_callback; - config.pUserData = &myUserData; - - result = ma_device_init(&myContext, &config, &device); - if (result != MA_SUCCESS) { - ... handle error ... - } - - Note that the "onDataCallback" member of ma_device_config has been renamed to "dataCallback". Also, "onStopCallback" has been renamed to "stopCallback". - -This is the first pass for full-duplex and there is a known bug. You will hear crackling on the following backends when sample rate conversion is required for -the playback device: - - Core Audio - - JACK - - AAudio - - OpenSL - - WebAudio - -In addition to the above, not all platforms have been absolutely thoroughly tested simply because I lack the hardware for such thorough testing. If you -experience a bug, an issue report on GitHub or an email would be greatly appreciated (and a sample program that reproduces the issue if possible). - - -Other API Changes ------------------ -In addition to the above, the following API changes have been made: - -- The log callback is no longer passed to ma_context_config_init(). Instead you need to set it manually after initialization. -- The onLogCallback member of ma_context_config has been renamed to "logCallback". -- The log callback now takes a logLevel parameter. The new callback looks like: void log_callback(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) - - You can use ma_log_level_to_string() to convert the logLevel to human readable text if you want to log it. -- Some APIs have been renamed: - - mal_decoder_read() -> ma_decoder_read_pcm_frames() - - mal_decoder_seek_to_frame() -> ma_decoder_seek_to_pcm_frame() - - mal_sine_wave_read() -> ma_sine_wave_read_f32() - - mal_sine_wave_read_ex() -> ma_sine_wave_read_f32_ex() -- Some APIs have been removed: - - mal_device_get_buffer_size_in_bytes() - - mal_device_set_recv_callback() - - mal_device_set_send_callback() - - mal_src_set_input_sample_rate() - - mal_src_set_output_sample_rate() -- Error codes have been rearranged. If you're a binding maintainer you will need to update. -- The ma_backend enums have been rearranged to priority order. The rationale for this is to simplify automatic backend selection and to make it easier to see - the priority. If you're a binding maintainer you will need to update. -- ma_dsp has been renamed to ma_pcm_converter. The rationale for this change is that I'm expecting "ma_dsp" to conflict with some future planned high-level - APIs. -- For functions that take a pointer/count combo, such as ma_decoder_read_pcm_frames(), the parameter order has changed so that the pointer comes before the - count. The rationale for this is to keep it consistent with things like memcpy(). - - -Miscellaneous Changes ---------------------- -The following miscellaneous changes have also been made. - -- The AAudio backend has been added for Android 8 and above. This is Android's new "High-Performance Audio" API. (For the record, this is one of the nicest - audio APIs out there, just behind the BSD audio APIs). -- The WebAudio backend has been added. This is based on ScriptProcessorNode. This removes the need for SDL. -- The SDL and OpenAL backends have been removed. These were originally implemented to add support for platforms for which miniaudio was not explicitly - supported. These are no longer needed and have therefore been removed. -- Device initialization now fails if the requested share mode is not supported. If you ask for exclusive mode, you either get an exclusive mode device, or an - error. The rationale for this change is to give the client more control over how to handle cases when the desired shared mode is unavailable. -- A lock-free ring buffer API has been added. There are two varients of this. "ma_rb" operates on bytes, whereas "ma_pcm_rb" operates on PCM frames. -- The library is now licensed as a choice of Public Domain (Unlicense) _or_ MIT-0 (No Attribution) which is the same as MIT, but removes the attribution - requirement. The rationale for this is to support countries that don't recognize public domain. -*/ - -/* -REVISION HISTORY -================ -v0.10.42 - 2021-08-22 - - Fix a possible deadlock when stopping devices. - -v0.10.41 - 2021-08-15 - - Core Audio: Fix some deadlock errors. - -v0.10.40 - 2021-07-23 - - Fix a bug when converting from stereo to mono. - - PulseAudio: Fix a glitch when pausing and resuming a device. - -v0.10.39 - 2021-07-20 - - Core Audio: Fix a deadlock when the default device is changed. - - Core Audio: Fix compilation errors on macOS and iOS. - - PulseAudio: Fix a bug where the stop callback is not fired when a device is unplugged. - - PulseAudio: Fix a null pointer dereference. - -v0.10.38 - 2021-07-14 - - Fix a linking error when MA_DEBUG_OUTPUT is not enabled. - - Fix an error where ma_log_postv() does not return a value. - - OpenSL: Fix a bug with setting of stream types and recording presets. - -0.10.37 - 2021-07-06 - - Fix a bug with log message formatting. - - Fix build when compiling with MA_NO_THREADING. - - Minor updates to channel mapping. - -0.10.36 - 2021-07-03 - - Add support for custom decoding backends. - - Fix some bugs with the Vorbis decoder. - - PulseAudio: Fix a bug with channel mapping. - - PulseAudio: Fix a bug where miniaudio does not fall back to a supported format when PulseAudio - defaults to a format not known to miniaudio. - - OpenSL: Fix a crash when initializing a capture device when a recording preset other than the - default is specified. - - Silence some warnings when compiling with MA_DEBUG_OUTPUT - - Improvements to logging. See the `ma_log` API for details. The logCallback variable used by - ma_context has been deprecated and will be replaced with the new system in version 0.11. - - Initialize an `ma_log` object with `ma_log_init()`. - - Register a callback with `ma_log_register_callback()`. - - In the context config, set `pLog` to your `ma_log` object and stop using `logCallback`. - - Prep work for some upcoming changes to data sources. These changes are still compatible with - existing code, however code will need to be updated in preparation for version 0.11 which will - be breaking. You should make these changes now for any custom data sources: - - Change your base data source object from `ma_data_source_callbacks` to `ma_data_source_base`. - - Call `ma_data_source_init()` for your base object in your custom data source's initialization - routine. This takes a config object which includes a pointer to a vtable which is now where - your custom callbacks are defined. - - Call `ma_data_source_uninit()` in your custom data source's uninitialization routine. This - doesn't currently do anything, but it placeholder in case some future uninitialization code - is required to be added at a later date. - -v0.10.35 - 2021-04-27 - - Fix the C++ build. - -v0.10.34 - 2021-04-26 - - WASAPI: Fix a bug where a result code is not getting checked at initialization time. - - WASAPI: Bug fixes for loopback mode. - - ALSA: Fix a possible deadlock when stopping devices. - - Mark devices as default on the null backend. - -v0.10.33 - 2021-04-04 - - Core Audio: Fix a memory leak. - - Core Audio: Fix a bug where the performance profile is not being used by playback devices. - - JACK: Fix loading of 64-bit JACK on Windows. - - Fix a calculation error and add a safety check to the following APIs to prevent a division by zero: - - ma_calculate_buffer_size_in_milliseconds_from_frames() - - ma_calculate_buffer_size_in_frames_from_milliseconds() - - Fix compilation errors relating to c89atomic. - - Update FLAC decoder. - -v0.10.32 - 2021-02-23 - - WASAPI: Fix a deadlock in exclusive mode. - - WASAPI: No longer return an error from ma_context_get_device_info() when an exclusive mode format - cannot be retrieved. - - WASAPI: Attempt to fix some bugs with device uninitialization. - - PulseAudio: Yet another refactor, this time to remove the dependency on `pa_threaded_mainloop`. - - Web Audio: Fix a bug on Chrome and any other browser using the same engine. - - Web Audio: Automatically start the device on some user input if the device has been started. This - is to work around Google's policy of not starting audio if no user input has yet been performed. - - Fix a bug where thread handles are not being freed. - - Fix some static analysis warnings in FLAC, WAV and MP3 decoders. - - Fix a warning due to referencing _MSC_VER when it is undefined. - - Update to latest version of c89atomic. - - Internal refactoring to migrate over to the new backend callback system for the following backends: - - PulseAudio - - ALSA - - Core Audio - - AAudio - - OpenSL|ES - - OSS - - audio(4) - - sndio - -v0.10.31 - 2021-01-17 - - Make some functions const correct. - - Update ma_data_source_read_pcm_frames() to initialize pFramesRead to 0 for safety. - - Add the MA_ATOMIC annotation for use with variables that should be used atomically and remove unnecessary volatile qualifiers. - - Add support for enabling only specific backends at compile time. This is the reverse of the pre-existing system. With the new - system, all backends are first disabled with `MA_ENABLE_ONLY_SPECIFIC_BACKENDS`, which is then followed with `MA_ENABLE_*`. The - old system where you disable backends with `MA_NO_*` still exists and is still the default. - -v0.10.30 - 2021-01-10 - - Fix a crash in ma_audio_buffer_read_pcm_frames(). - - Update spinlock APIs to take a volatile parameter as input. - - Silence some unused parameter warnings. - - Fix a warning on GCC when compiling as C++. - -v0.10.29 - 2020-12-26 - - Fix some subtle multi-threading bugs on non-x86 platforms. - - Fix a bug resulting in superfluous memory allocations when enumerating devices. - - Core Audio: Fix a compilation error when compiling for iOS. - -v0.10.28 - 2020-12-16 - - Fix a crash when initializing a POSIX thread. - - OpenSL|ES: Respect the MA_NO_RUNTIME_LINKING option. - -v0.10.27 - 2020-12-04 - - Add support for dynamically configuring some properties of `ma_noise` objects post-initialization. - - Add support for configuring the channel mixing mode in the device config. - - Fix a bug with simple channel mixing mode (drop or silence excess channels). - - Fix some bugs with trying to access uninitialized variables. - - Fix some errors with stopping devices for synchronous backends where the backend's stop callback would get fired twice. - - Fix a bug in the decoder due to using an uninitialized variable. - - Fix some data race errors. - -v0.10.26 - 2020-11-24 - - WASAPI: Fix a bug where the exclusive mode format may not be retrieved correctly due to accessing freed memory. - - Fix a bug with ma_waveform where glitching occurs after changing frequency. - - Fix compilation with OpenWatcom. - - Fix compilation with TCC. - - Fix compilation with Digital Mars. - - Fix compilation warnings. - - Remove bitfields from public structures to aid in binding maintenance. - -v0.10.25 - 2020-11-15 - - PulseAudio: Fix a bug where the stop callback isn't fired. - - WebAudio: Fix an error that occurs when Emscripten increases the size of it's heap. - - Custom Backends: Change the onContextInit and onDeviceInit callbacks to take a parameter which is a pointer to the config that was - passed into ma_context_init() and ma_device_init(). This replaces the deviceType parameter of onDeviceInit. - - Fix compilation warnings on older versions of GCC. - -v0.10.24 - 2020-11-10 - - Fix a bug where initialization of a backend can fail due to some bad state being set from a prior failed attempt at initializing a - lower priority backend. - -v0.10.23 - 2020-11-09 - - AAudio: Add support for configuring a playback stream's usage. - - Fix a compilation error when all built-in asynchronous backends are disabled at compile time. - - Fix compilation errors when compiling as C++. - -v0.10.22 - 2020-11-08 - - Add support for custom backends. - - Add support for detecting default devices during device enumeration and with `ma_context_get_device_info()`. - - Refactor to the PulseAudio backend. This simplifies the implementation and fixes a capture bug. - - ALSA: Fix a bug in `ma_context_get_device_info()` where the PCM handle is left open in the event of an error. - - Core Audio: Further improvements to sample rate selection. - - Core Audio: Fix some bugs with capture mode. - - OpenSL: Add support for configuring stream types and recording presets. - - AAudio: Add support for configuring content types and input presets. - - Fix bugs in `ma_decoder_init_file*()` where the file handle is not closed after a decoding error. - - Fix some compilation warnings on GCC and Clang relating to the Speex resampler. - - Fix a compilation error for the Linux build when the ALSA and JACK backends are both disabled. - - Fix a compilation error for the BSD build. - - Fix some compilation errors on older versions of GCC. - - Add documentation for `MA_NO_RUNTIME_LINKING`. - -v0.10.21 - 2020-10-30 - - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. - - WASAPI: Fix a copy and paste bug relating to loopback mode. - - Core Audio: Fix a bug when using multiple contexts. - - Core Audio: Fix a compilation warning. - - Core Audio: Improvements to sample rate selection. - - Core Audio: Improvements to format/channels/rate selection when requesting defaults. - - Core Audio: Add notes regarding the Apple notarization process. - - Fix some bugs due to null pointer dereferences. - -v0.10.20 - 2020-10-06 - - Fix build errors with UWP. - - Minor documentation updates. - -v0.10.19 - 2020-09-22 - - WASAPI: Return an error when exclusive mode is requested, but the native format is not supported by miniaudio. - - Fix a bug where ma_decoder_seek_to_pcm_frames() never returns MA_SUCCESS even though it was successful. - - Store the sample rate in the `ma_lpf` and `ma_hpf` structures. - -v0.10.18 - 2020-08-30 - - Fix build errors with VC6. - - Fix a bug in channel converter for s32 format. - - Change channel converter configs to use the default channel map instead of a blank channel map when no channel map is specified when initializing the - config. This fixes an issue where the optimized mono expansion path would never get used. - - Use a more appropriate default format for FLAC decoders. This will now use ma_format_s16 when the FLAC is encoded as 16-bit. - - Update FLAC decoder. - - Update links to point to the new repository location (https://github.com/mackron/miniaudio). - -v0.10.17 - 2020-08-28 - - Fix an error where the WAV codec is incorrectly excluded from the build depending on which compile time options are set. - - Fix a bug in ma_audio_buffer_read_pcm_frames() where it isn't returning the correct number of frames processed. - - Fix compilation error on Android. - - Core Audio: Fix a bug with full-duplex mode. - - Add ma_decoder_get_cursor_in_pcm_frames(). - - Update WAV codec. - -v0.10.16 - 2020-08-14 - - WASAPI: Fix a potential crash due to using an uninitialized variable. - - OpenSL: Enable runtime linking. - - OpenSL: Fix a multithreading bug when initializing and uninitializing multiple contexts at the same time. - - iOS: Improvements to device enumeration. - - Fix a crash in ma_data_source_read_pcm_frames() when the output frame count parameter is NULL. - - Fix a bug in ma_data_source_read_pcm_frames() where looping doesn't work. - - Fix some compilation warnings on Windows when both DirectSound and WinMM are disabled. - - Fix some compilation warnings when no decoders are enabled. - - Add ma_audio_buffer_get_available_frames(). - - Add ma_decoder_get_available_frames(). - - Add sample rate to ma_data_source_get_data_format(). - - Change volume APIs to take 64-bit frame counts. - - Updates to documentation. - -v0.10.15 - 2020-07-15 - - Fix a bug when converting bit-masked channel maps to miniaudio channel maps. This affects the WASAPI and OpenSL backends. - -v0.10.14 - 2020-07-14 - - Fix compilation errors on Android. - - Fix compilation errors with -march=armv6. - - Updates to the documentation. - -v0.10.13 - 2020-07-11 - - Fix some potential buffer overflow errors with channel maps when channel counts are greater than MA_MAX_CHANNELS. - - Fix compilation error on Emscripten. - - Silence some unused function warnings. - - Increase the default buffer size on the Web Audio backend. This fixes glitching issues on some browsers. - - Bring FLAC decoder up-to-date with dr_flac. - - Bring MP3 decoder up-to-date with dr_mp3. - -v0.10.12 - 2020-07-04 - - Fix compilation errors on the iOS build. - -v0.10.11 - 2020-06-28 - - Fix some bugs with device tracking on Core Audio. - - Updates to documentation. - -v0.10.10 - 2020-06-26 - - Add include guard for the implementation section. - - Mark ma_device_sink_info_callback() as static. - - Fix compilation errors with MA_NO_DECODING and MA_NO_ENCODING. - - Fix compilation errors with MA_NO_DEVICE_IO - -v0.10.9 - 2020-06-24 - - Amalgamation of dr_wav, dr_flac and dr_mp3. With this change, including the header section of these libraries before the implementation of miniaudio is no - longer required. Decoding of WAV, FLAC and MP3 should be supported seamlessly without any additional libraries. Decoders can be excluded from the build - with the following options: - - MA_NO_WAV - - MA_NO_FLAC - - MA_NO_MP3 - If you get errors about multiple definitions you need to either enable the options above, move the implementation of dr_wav, dr_flac and/or dr_mp3 to before - the implementation of miniaudio, or update dr_wav, dr_flac and/or dr_mp3. - - Changes to the internal atomics library. This has been replaced with c89atomic.h which is embedded within this file. - - Fix a bug when a decoding backend reports configurations outside the limits of miniaudio's decoder abstraction. - - Fix the UWP build. - - Fix the Core Audio build. - - Fix the -std=c89 build on GCC. - -v0.10.8 - 2020-06-22 - - Remove dependency on ma_context from mutexes. - - Change ma_data_source_read_pcm_frames() to return a result code and output the frames read as an output parameter. - - Change ma_data_source_seek_pcm_frames() to return a result code and output the frames seeked as an output parameter. - - Change ma_audio_buffer_unmap() to return MA_AT_END when the end has been reached. This should be considered successful. - - Change playback.pDeviceID and capture.pDeviceID to constant pointers in ma_device_config. - - Add support for initializing decoders from a virtual file system object. This is achieved via the ma_vfs API and allows the application to customize file - IO for the loading and reading of raw audio data. Passing in NULL for the VFS will use defaults. New APIs: - - ma_decoder_init_vfs() - - ma_decoder_init_vfs_wav() - - ma_decoder_init_vfs_flac() - - ma_decoder_init_vfs_mp3() - - ma_decoder_init_vfs_vorbis() - - ma_decoder_init_vfs_w() - - ma_decoder_init_vfs_wav_w() - - ma_decoder_init_vfs_flac_w() - - ma_decoder_init_vfs_mp3_w() - - ma_decoder_init_vfs_vorbis_w() - - Add support for memory mapping to ma_data_source. - - ma_data_source_map() - - ma_data_source_unmap() - - Add ma_offset_pcm_frames_ptr() and ma_offset_pcm_frames_const_ptr() which can be used for offsetting a pointer by a specified number of PCM frames. - - Add initial implementation of ma_yield() which is useful for spin locks which will be used in some upcoming work. - - Add documentation for log levels. - - The ma_event API has been made public in preparation for some uncoming work. - - Fix a bug in ma_decoder_seek_to_pcm_frame() where the internal sample rate is not being taken into account for determining the seek location. - - Fix some bugs with the linear resampler when dynamically changing the sample rate. - - Fix compilation errors with MA_NO_DEVICE_IO. - - Fix some warnings with GCC and -std=c89. - - Fix some formatting warnings with GCC and -Wall and -Wpedantic. - - Fix some warnings with VC6. - - Minor optimization to ma_copy_pcm_frames(). This is now a no-op when the input and output buffers are the same. - -v0.10.7 - 2020-05-25 - - Fix a compilation error in the C++ build. - - Silence a warning. - -v0.10.6 - 2020-05-24 - - Change ma_clip_samples_f32() and ma_clip_pcm_frames_f32() to take a 64-bit sample/frame count. - - Change ma_zero_pcm_frames() to clear to 128 for ma_format_u8. - - Add ma_silence_pcm_frames() which replaces ma_zero_pcm_frames(). ma_zero_pcm_frames() will be removed in version 0.11. - - Add support for u8, s24 and s32 formats to ma_channel_converter. - - Add compile-time and run-time version querying. - - MA_VERSION_MINOR - - MA_VERSION_MAJOR - - MA_VERSION_REVISION - - MA_VERSION_STRING - - ma_version() - - ma_version_string() - - Add ma_audio_buffer for reading raw audio data directly from memory. - - Fix a bug in shuffle mode in ma_channel_converter. - - Fix compilation errors in certain configurations for ALSA and PulseAudio. - - The data callback now initializes the output buffer to 128 when the playback sample format is ma_format_u8. - -v0.10.5 - 2020-05-05 - - Change ma_zero_pcm_frames() to take a 64-bit frame count. - - Add ma_copy_pcm_frames(). - - Add MA_NO_GENERATION build option to exclude the `ma_waveform` and `ma_noise` APIs from the build. - - Add support for formatted logging to the VC6 build. - - Fix a crash in the linear resampler when LPF order is 0. - - Fix compilation errors and warnings with older versions of Visual Studio. - - Minor documentation updates. - -v0.10.4 - 2020-04-12 - - Fix a data conversion bug when converting from the client format to the native device format. - -v0.10.3 - 2020-04-07 - - Bring up to date with breaking changes to dr_mp3. - - Remove MA_NO_STDIO. This was causing compilation errors and the maintenance cost versus practical benefit is no longer worthwhile. - - Fix a bug with data conversion where it was unnecessarily converting to s16 or f32 and then straight back to the original format. - - Fix compilation errors and warnings with Visual Studio 2005. - - ALSA: Disable ALSA's automatic data conversion by default and add configuration options to the device config: - - alsa.noAutoFormat - - alsa.noAutoChannels - - alsa.noAutoResample - - WASAPI: Add some overrun recovery for ma_device_type_capture devices. - -v0.10.2 - 2020-03-22 - - Decorate some APIs with MA_API which were missed in the previous version. - - Fix a bug in ma_linear_resampler_set_rate() and ma_linear_resampler_set_rate_ratio(). - -v0.10.1 - 2020-03-17 - - Add MA_API decoration. This can be customized by defining it before including miniaudio.h. - - Fix a bug where opening a file would return a success code when in fact it failed. - - Fix compilation errors with Visual Studio 6 and 2003. - - Fix warnings on macOS. - -v0.10.0 - 2020-03-07 - - API CHANGE: Refactor data conversion APIs - - ma_format_converter has been removed. Use ma_convert_pcm_frames_format() instead. - - ma_channel_router has been replaced with ma_channel_converter. - - ma_src has been replaced with ma_resampler - - ma_pcm_converter has been replaced with ma_data_converter - - API CHANGE: Add support for custom memory allocation callbacks. The following APIs have been updated to take an extra parameter for the allocation - callbacks: - - ma_malloc() - - ma_realloc() - - ma_free() - - ma_aligned_malloc() - - ma_aligned_free() - - ma_rb_init() / ma_rb_init_ex() - - ma_pcm_rb_init() / ma_pcm_rb_init_ex() - - API CHANGE: Simplify latency specification in device configurations. The bufferSizeInFrames and bufferSizeInMilliseconds parameters have been replaced with - periodSizeInFrames and periodSizeInMilliseconds respectively. The previous variables defined the size of the entire buffer, whereas the new ones define the - size of a period. The following APIs have been removed since they are no longer relevant: - - ma_get_default_buffer_size_in_milliseconds() - - ma_get_default_buffer_size_in_frames() - - API CHANGE: ma_device_set_stop_callback() has been removed. If you require a stop callback, you must now set it via the device config just like the data - callback. - - API CHANGE: The ma_sine_wave API has been replaced with ma_waveform. The following APIs have been removed: - - ma_sine_wave_init() - - ma_sine_wave_read_f32() - - ma_sine_wave_read_f32_ex() - - API CHANGE: ma_convert_frames() has been updated to take an extra parameter which is the size of the output buffer in PCM frames. Parameters have also been - reordered. - - API CHANGE: ma_convert_frames_ex() has been changed to take a pointer to a ma_data_converter_config object to specify the input and output formats to - convert between. - - API CHANGE: ma_calculate_frame_count_after_src() has been renamed to ma_calculate_frame_count_after_resampling(). - - Add support for the following filters: - - Biquad (ma_biquad) - - First order low-pass (ma_lpf1) - - Second order low-pass (ma_lpf2) - - Low-pass with configurable order (ma_lpf) - - First order high-pass (ma_hpf1) - - Second order high-pass (ma_hpf2) - - High-pass with configurable order (ma_hpf) - - Second order band-pass (ma_bpf2) - - Band-pass with configurable order (ma_bpf) - - Second order peaking EQ (ma_peak2) - - Second order notching (ma_notch2) - - Second order low shelf (ma_loshelf2) - - Second order high shelf (ma_hishelf2) - - Add waveform generation API (ma_waveform) with support for the following: - - Sine - - Square - - Triangle - - Sawtooth - - Add noise generation API (ma_noise) with support for the following: - - White - - Pink - - Brownian - - Add encoding API (ma_encoder). This only supports outputting to WAV files via dr_wav. - - Add ma_result_description() which is used to retrieve a human readable description of a given result code. - - Result codes have been changed. Binding maintainers will need to update their result code constants. - - More meaningful result codes are now returned when a file fails to open. - - Internal functions have all been made static where possible. - - Fix potential crash when ma_device object's are not aligned to MA_SIMD_ALIGNMENT. - - Fix a bug in ma_decoder_get_length_in_pcm_frames() where it was returning the length based on the internal sample rate rather than the output sample rate. - - Fix bugs in some backends where the device is not drained properly in ma_device_stop(). - - Improvements to documentation. - -v0.9.10 - 2020-01-15 - - Fix compilation errors due to #if/#endif mismatches. - - WASAPI: Fix a bug where automatic stream routing is being performed for devices that are initialized with an explicit device ID. - - iOS: Fix a crash on device uninitialization. - -v0.9.9 - 2020-01-09 - - Fix compilation errors with MinGW. - - Fix compilation errors when compiling on Apple platforms. - - WASAPI: Add support for disabling hardware offloading. - - WASAPI: Add support for disabling automatic stream routing. - - Core Audio: Fix bugs in the case where the internal device uses deinterleaved buffers. - - Core Audio: Add support for controlling the session category (AVAudioSessionCategory) and options (AVAudioSessionCategoryOptions). - - JACK: Fix bug where incorrect ports are connected. - -v0.9.8 - 2019-10-07 - - WASAPI: Fix a potential deadlock when starting a full-duplex device. - - WASAPI: Enable automatic resampling by default. Disable with config.wasapi.noAutoConvertSRC. - - Core Audio: Fix bugs with automatic stream routing. - - Add support for controlling whether or not the content of the output buffer passed in to the data callback is pre-initialized - to zero. By default it will be initialized to zero, but this can be changed by setting noPreZeroedOutputBuffer in the device - config. Setting noPreZeroedOutputBuffer to true will leave the contents undefined. - - Add support for clipping samples after the data callback has returned. This only applies when the playback sample format is - configured as ma_format_f32. If you are doing clipping yourself, you can disable this overhead by setting noClip to true in - the device config. - - Add support for master volume control for devices. - - Use ma_device_set_master_volume() to set the volume to a factor between 0 and 1, where 0 is silence and 1 is full volume. - - Use ma_device_set_master_gain_db() to set the volume in decibels where 0 is full volume and < 0 reduces the volume. - - Fix warnings emitted by GCC when `__inline__` is undefined or defined as nothing. - -v0.9.7 - 2019-08-28 - - Add support for loopback mode (WASAPI only). - - To use this, set the device type to ma_device_type_loopback, and then fill out the capture section of the device config. - - If you need to capture from a specific output device, set the capture device ID to that of a playback device. - - Fix a crash when an error is posted in ma_device_init(). - - Fix a compilation error when compiling for ARM architectures. - - Fix a bug with the audio(4) backend where the device is incorrectly being opened in non-blocking mode. - - Fix memory leaks in the Core Audio backend. - - Minor refactoring to the WinMM, ALSA, PulseAudio, OSS, audio(4), sndio and null backends. - -v0.9.6 - 2019-08-04 - - Add support for loading decoders using a wchar_t string for file paths. - - Don't trigger an assert when ma_device_start() is called on a device that is already started. This will now log a warning - and return MA_INVALID_OPERATION. The same applies for ma_device_stop(). - - Try fixing an issue with PulseAudio taking a long time to start playback. - - Fix a bug in ma_convert_frames() and ma_convert_frames_ex(). - - Fix memory leaks in the WASAPI backend. - - Fix a compilation error with Visual Studio 2010. - -v0.9.5 - 2019-05-21 - - Add logging to ma_dlopen() and ma_dlsym(). - - Add ma_decoder_get_length_in_pcm_frames(). - - Fix a bug with capture on the OpenSL|ES backend. - - Fix a bug with the ALSA backend where a device would not restart after being stopped. - -v0.9.4 - 2019-05-06 - - Add support for C89. With this change, miniaudio should compile clean with GCC/Clang with "-std=c89 -ansi -pedantic" and - Microsoft compilers back to VC6. Other compilers should also work, but have not been tested. - -v0.9.3 - 2019-04-19 - - Fix compiler errors on GCC when compiling with -std=c99. - -v0.9.2 - 2019-04-08 - - Add support for per-context user data. - - Fix a potential bug with context configs. - - Fix some bugs with PulseAudio. - -v0.9.1 - 2019-03-17 - - Fix a bug where the output buffer is not getting zeroed out before calling the data callback. This happens when - the device is running in passthrough mode (not doing any data conversion). - - Fix an issue where the data callback is getting called too frequently on the WASAPI and DirectSound backends. - - Fix error on the UWP build. - - Fix a build error on Apple platforms. - -v0.9 - 2019-03-06 - - Rebranded to "miniaudio". All namespaces have been renamed from "mal" to "ma". - - API CHANGE: ma_device_init() and ma_device_config_init() have changed significantly: - - The device type, device ID and user data pointer have moved from ma_device_init() to the config. - - All variations of ma_device_config_init_*() have been removed in favor of just ma_device_config_init(). - - ma_device_config_init() now takes only one parameter which is the device type. All other properties need - to be set on the returned object directly. - - The onDataCallback and onStopCallback members of ma_device_config have been renamed to "dataCallback" - and "stopCallback". - - The ID of the physical device is now split into two: one for the playback device and the other for the - capture device. This is required for full-duplex. These are named "pPlaybackDeviceID" and "pCaptureDeviceID". - - API CHANGE: The data callback has changed. It now uses a unified callback for all device types rather than - being separate for each. It now takes two pointers - one containing input data and the other output data. This - design in required for full-duplex. The return value is now void instead of the number of frames written. The - new callback looks like the following: - void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); - - API CHANGE: Remove the log callback parameter from ma_context_config_init(). With this change, - ma_context_config_init() now takes no parameters and the log callback is set via the structure directly. The - new policy for config initialization is that only mandatory settings are passed in to *_config_init(). The - "onLog" member of ma_context_config has been renamed to "logCallback". - - API CHANGE: Remove ma_device_get_buffer_size_in_bytes(). - - API CHANGE: Rename decoding APIs to "pcm_frames" convention. - - mal_decoder_read() -> ma_decoder_read_pcm_frames() - - mal_decoder_seek_to_frame() -> ma_decoder_seek_to_pcm_frame() - - API CHANGE: Rename sine wave reading APIs to f32 convention. - - mal_sine_wave_read() -> ma_sine_wave_read_f32() - - mal_sine_wave_read_ex() -> ma_sine_wave_read_f32_ex() - - API CHANGE: Remove some deprecated APIs - - mal_device_set_recv_callback() - - mal_device_set_send_callback() - - mal_src_set_input_sample_rate() - - mal_src_set_output_sample_rate() - - API CHANGE: Add log level to the log callback. New signature: - - void on_log(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* message) - - API CHANGE: Changes to result codes. Constants have changed and unused codes have been removed. If you're - a binding mainainer you will need to update your result code constants. - - API CHANGE: Change the order of the ma_backend enums to priority order. If you are a binding maintainer, you - will need to update. - - API CHANGE: Rename mal_dsp to ma_pcm_converter. All functions have been renamed from mal_dsp_*() to - ma_pcm_converter_*(). All structures have been renamed from mal_dsp* to ma_pcm_converter*. - - API CHANGE: Reorder parameters of ma_decoder_read_pcm_frames() to be consistent with the new parameter order scheme. - - The resampling algorithm has been changed from sinc to linear. The rationale for this is that the sinc implementation - is too inefficient right now. This will hopefully be improved at a later date. - - Device initialization will no longer fall back to shared mode if exclusive mode is requested but is unusable. - With this change, if you request an device in exclusive mode, but exclusive mode is not supported, it will not - automatically fall back to shared mode. The client will need to reinitialize the device in shared mode if that's - what they want. - - Add ring buffer API. This is ma_rb and ma_pcm_rb, the difference being that ma_rb operates on bytes and - ma_pcm_rb operates on PCM frames. - - Add Web Audio backend. This is used when compiling with Emscripten. The SDL backend, which was previously - used for web support, will be removed in a future version. - - Add AAudio backend (Android Audio). This is the new priority backend for Android. Support for AAudio starts - with Android 8. OpenSL|ES is used as a fallback for older versions of Android. - - Remove OpenAL and SDL backends. - - Fix a possible deadlock when rapidly stopping the device after it has started. - - Update documentation. - - Change licensing to a choice of public domain _or_ MIT-0 (No Attribution). - -v0.8.14 - 2018-12-16 - - Core Audio: Fix a bug where the device state is not set correctly after stopping. - - Add support for custom weights to the channel router. - - Update decoders to use updated APIs in dr_flac, dr_mp3 and dr_wav. - -v0.8.13 - 2018-12-04 - - Core Audio: Fix a bug with channel mapping. - - Fix a bug with channel routing where the back/left and back/right channels have the wrong weight. - -v0.8.12 - 2018-11-27 - - Drop support for SDL 1.2. The Emscripten build now requires "-s USE_SDL=2". - - Fix a linking error with ALSA. - - Fix a bug on iOS where the device name is not set correctly. - -v0.8.11 - 2018-11-21 - - iOS bug fixes. - - Minor tweaks to PulseAudio. - -v0.8.10 - 2018-10-21 - - Core Audio: Fix a hang when uninitializing a device. - - Fix a bug where an incorrect value is returned from mal_device_stop(). - -v0.8.9 - 2018-09-28 - - Fix a bug with the SDL backend where device initialization fails. - -v0.8.8 - 2018-09-14 - - Fix Linux build with the ALSA backend. - - Minor documentation fix. - -v0.8.7 - 2018-09-12 - - Fix a bug with UWP detection. - -v0.8.6 - 2018-08-26 - - Automatically switch the internal device when the default device is unplugged. Note that this is still in the - early stages and not all backends handle this the same way. As of this version, this will not detect a default - device switch when changed from the operating system's audio preferences (unless the backend itself handles - this automatically). This is not supported in exclusive mode. - - WASAPI and Core Audio: Add support for stream routing. When the application is using a default device and the - user switches the default device via the operating system's audio preferences, miniaudio will automatically switch - the internal device to the new default. This is not supported in exclusive mode. - - WASAPI: Add support for hardware offloading via IAudioClient2. Only supported on Windows 8 and newer. - - WASAPI: Add support for low-latency shared mode via IAudioClient3. Only supported on Windows 10 and newer. - - Add support for compiling the UWP build as C. - - mal_device_set_recv_callback() and mal_device_set_send_callback() have been deprecated. You must now set this - when the device is initialized with mal_device_init*(). These will be removed in version 0.9.0. - -v0.8.5 - 2018-08-12 - - Add support for specifying the size of a device's buffer in milliseconds. You can still set the buffer size in - frames if that suits you. When bufferSizeInFrames is 0, bufferSizeInMilliseconds will be used. If both are non-0 - then bufferSizeInFrames will take priority. If both are set to 0 the default buffer size is used. - - Add support for the audio(4) backend to OpenBSD. - - Fix a bug with the ALSA backend that was causing problems on Raspberry Pi. This significantly improves the - Raspberry Pi experience. - - Fix a bug where an incorrect number of samples is returned from sinc resampling. - - Add support for setting the value to be passed to internal calls to CoInitializeEx(). - - WASAPI and WinMM: Stop the device when it is unplugged. - -v0.8.4 - 2018-08-06 - - Add sndio backend for OpenBSD. - - Add audio(4) backend for NetBSD. - - Drop support for the OSS backend on everything except FreeBSD and DragonFly BSD. - - Formats are now native-endian (were previously little-endian). - - Mark some APIs as deprecated: - - mal_src_set_input_sample_rate() and mal_src_set_output_sample_rate() are replaced with mal_src_set_sample_rate(). - - mal_dsp_set_input_sample_rate() and mal_dsp_set_output_sample_rate() are replaced with mal_dsp_set_sample_rate(). - - Fix a bug when capturing using the WASAPI backend. - - Fix some aliasing issues with resampling, specifically when increasing the sample rate. - - Fix warnings. - -v0.8.3 - 2018-07-15 - - Fix a crackling bug when resampling in capture mode. - - Core Audio: Fix a bug where capture does not work. - - ALSA: Fix a bug where the worker thread can get stuck in an infinite loop. - - PulseAudio: Fix a bug where mal_context_init() succeeds when PulseAudio is unusable. - - JACK: Fix a bug where mal_context_init() succeeds when JACK is unusable. - -v0.8.2 - 2018-07-07 - - Fix a bug on macOS with Core Audio where the internal callback is not called. - -v0.8.1 - 2018-07-06 - - Fix compilation errors and warnings. - -v0.8 - 2018-07-05 - - Changed MAL_IMPLEMENTATION to MINI_AL_IMPLEMENTATION for consistency with other libraries. The old - way is still supported for now, but you should update as it may be removed in the future. - - API CHANGE: Replace device enumeration APIs. mal_enumerate_devices() has been replaced with - mal_context_get_devices(). An additional low-level device enumration API has been introduced called - mal_context_enumerate_devices() which uses a callback to report devices. - - API CHANGE: Rename mal_get_sample_size_in_bytes() to mal_get_bytes_per_sample() and add - mal_get_bytes_per_frame(). - - API CHANGE: Replace mal_device_config.preferExclusiveMode with mal_device_config.shareMode. - - This new config can be set to mal_share_mode_shared (default) or mal_share_mode_exclusive. - - API CHANGE: Remove excludeNullDevice from mal_context_config.alsa. - - API CHANGE: Rename MAL_MAX_SAMPLE_SIZE_IN_BYTES to MAL_MAX_PCM_SAMPLE_SIZE_IN_BYTES. - - API CHANGE: Change the default channel mapping to the standard Microsoft mapping. - - API CHANGE: Remove backend-specific result codes. - - API CHANGE: Changes to the format conversion APIs (mal_pcm_f32_to_s16(), etc.) - - Add support for Core Audio (Apple). - - Add support for PulseAudio. - - This is the highest priority backend on Linux (higher priority than ALSA) since it is commonly - installed by default on many of the popular distros and offer's more seamless integration on - platforms where PulseAudio is used. In addition, if PulseAudio is installed and running (which - is extremely common), it's better to just use PulseAudio directly rather than going through the - "pulse" ALSA plugin (which is what the "default" ALSA device is likely set to). - - Add support for JACK. - - Remove dependency on asound.h for the ALSA backend. This means the ALSA development packages are no - longer required to build miniaudio. - - Remove dependency on dsound.h for the DirectSound backend. This fixes build issues with some - distributions of MinGW. - - Remove dependency on audioclient.h for the WASAPI backend. This fixes build issues with some - distributions of MinGW. - - Add support for dithering to format conversion. - - Add support for configuring the priority of the worker thread. - - Add a sine wave generator. - - Improve efficiency of sample rate conversion. - - Introduce the notion of standard channel maps. Use mal_get_standard_channel_map(). - - Introduce the notion of default device configurations. A default config uses the same configuration - as the backend's internal device, and as such results in a pass-through data transmission pipeline. - - Add support for passing in NULL for the device config in mal_device_init(), which uses a default - config. This requires manually calling mal_device_set_send/recv_callback(). - - Add support for decoding from raw PCM data (mal_decoder_init_raw(), etc.) - - Make mal_device_init_ex() more robust. - - Make some APIs more const-correct. - - Fix errors with SDL detection on Apple platforms. - - Fix errors with OpenAL detection. - - Fix some memory leaks. - - Fix a bug with opening decoders from memory. - - Early work on SSE2, AVX2 and NEON optimizations. - - Miscellaneous bug fixes. - - Documentation updates. - -v0.7 - 2018-02-25 - - API CHANGE: Change mal_src_read_frames() and mal_dsp_read_frames() to use 64-bit sample counts. - - Add decoder APIs for loading WAV, FLAC, Vorbis and MP3 files. - - Allow opening of devices without a context. - - In this case the context is created and managed internally by the device. - - Change the default channel mapping to the same as that used by FLAC. - - Fix build errors with macOS. - -v0.6c - 2018-02-12 - - Fix build errors with BSD/OSS. - -v0.6b - 2018-02-03 - - Fix some warnings when compiling with Visual C++. - -v0.6a - 2018-01-26 - - Fix errors with channel mixing when increasing the channel count. - - Improvements to the build system for the OpenAL backend. - - Documentation fixes. - -v0.6 - 2017-12-08 - - API CHANGE: Expose and improve mutex APIs. If you were using the mutex APIs before this version you'll - need to update. - - API CHANGE: SRC and DSP callbacks now take a pointer to a mal_src and mal_dsp object respectively. - - API CHANGE: Improvements to event and thread APIs. These changes make these APIs more consistent. - - Add support for SDL and Emscripten. - - Simplify the build system further for when development packages for various backends are not installed. - With this change, when the compiler supports __has_include, backends without the relevant development - packages installed will be ignored. This fixes the build for old versions of MinGW. - - Fixes to the Android build. - - Add mal_convert_frames(). This is a high-level helper API for performing a one-time, bulk conversion of - audio data to a different format. - - Improvements to f32 -> u8/s16/s24/s32 conversion routines. - - Fix a bug where the wrong value is returned from mal_device_start() for the OpenSL backend. - - Fixes and improvements for Raspberry Pi. - - Warning fixes. - -v0.5 - 2017-11-11 - - API CHANGE: The mal_context_init() function now takes a pointer to a mal_context_config object for - configuring the context. The works in the same kind of way as the device config. The rationale for this - change is to give applications better control over context-level properties, add support for backend- - specific configurations, and support extensibility without breaking the API. - - API CHANGE: The alsa.preferPlugHW device config variable has been removed since it's not really useful for - anything anymore. - - ALSA: By default, device enumeration will now only enumerate over unique card/device pairs. Applications - can enable verbose device enumeration by setting the alsa.useVerboseDeviceEnumeration context config - variable. - - ALSA: When opening a device in shared mode (the default), the dmix/dsnoop plugin will be prioritized. If - this fails it will fall back to the hw plugin. With this change the preferExclusiveMode config is now - honored. Note that this does not happen when alsa.useVerboseDeviceEnumeration is set to true (see above) - which is by design. - - ALSA: Add support for excluding the "null" device using the alsa.excludeNullDevice context config variable. - - ALSA: Fix a bug with channel mapping which causes an assertion to fail. - - Fix errors with enumeration when pInfo is set to NULL. - - OSS: Fix a bug when starting a device when the client sends 0 samples for the initial buffer fill. - -v0.4 - 2017-11-05 - - API CHANGE: The log callback is now per-context rather than per-device and as is thus now passed to - mal_context_init(). The rationale for this change is that it allows applications to capture diagnostic - messages at the context level. Previously this was only available at the device level. - - API CHANGE: The device config passed to mal_device_init() is now const. - - Added support for OSS which enables support on BSD platforms. - - Added support for WinMM (waveOut/waveIn). - - Added support for UWP (Universal Windows Platform) applications. Currently C++ only. - - Added support for exclusive mode for selected backends. Currently supported on WASAPI. - - POSIX builds no longer require explicit linking to libpthread (-lpthread). - - ALSA: Explicit linking to libasound (-lasound) is no longer required. - - ALSA: Latency improvements. - - ALSA: Use MMAP mode where available. This can be disabled with the alsa.noMMap config. - - ALSA: Use "hw" devices instead of "plughw" devices by default. This can be disabled with the - alsa.preferPlugHW config. - - WASAPI is now the highest priority backend on Windows platforms. - - Fixed an error with sample rate conversion which was causing crackling when capturing. - - Improved error handling. - - Improved compiler support. - - Miscellaneous bug fixes. - -v0.3 - 2017-06-19 - - API CHANGE: Introduced the notion of a context. The context is the highest level object and is required for - enumerating and creating devices. Now, applications must first create a context, and then use that to - enumerate and create devices. The reason for this change is to ensure device enumeration and creation is - tied to the same backend. In addition, some backends are better suited to this design. - - API CHANGE: Removed the rewinding APIs because they're too inconsistent across the different backends, hard - to test and maintain, and just generally unreliable. - - Added helper APIs for initializing mal_device_config objects. - - Null Backend: Fixed a crash when recording. - - Fixed build for UWP. - - Added support for f32 formats to the OpenSL|ES backend. - - Added initial implementation of the WASAPI backend. - - Added initial implementation of the OpenAL backend. - - Added support for low quality linear sample rate conversion. - - Added early support for basic channel mapping. - -v0.2 - 2016-10-28 - - API CHANGE: Add user data pointer as the last parameter to mal_device_init(). The rationale for this - change is to ensure the logging callback has access to the user data during initialization. - - API CHANGE: Have device configuration properties be passed to mal_device_init() via a structure. Rationale: - 1) The number of parameters is just getting too much. - 2) It makes it a bit easier to add new configuration properties in the future. In particular, there's a - chance there will be support added for backend-specific properties. - - Dropped support for f64, A-law and Mu-law formats since they just aren't common enough to justify the - added maintenance cost. - - DirectSound: Increased the default buffer size for capture devices. - - Added initial implementation of the OpenSL|ES backend. - -v0.1 - 2016-10-21 - - Initial versioned release. -*/ - /* This software is available as a choice of the following licenses. Choose diff --git a/vendor/miniaudio/synchronization.odin b/vendor/miniaudio/synchronization.odin new file mode 100644 index 000000000..7615e8f45 --- /dev/null +++ b/vendor/miniaudio/synchronization.odin @@ -0,0 +1,152 @@ +package miniaudio + +when ODIN_OS == .Windows { + foreign import lib "lib/miniaudio.lib" +} else when ODIN_OS == .Linux { + foreign import lib "lib/miniaudio.a" +} else { + foreign import lib "system:miniaudio" +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + /* + Locks a spinlock. + */ + spinlock_lock :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- + + /* + Locks a spinlock, but does not yield() when looping. + */ + spinlock_lock_noyield :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- + + /* + Unlocks a spinlock. + */ + spinlock_unlock :: proc(/*volatile*/ pSpinlock: ^spinlock) -> result --- + +when NO_THREADING { + /* + Creates a mutex. + + A mutex must be created from a valid context. A mutex is initially unlocked. + */ + mutex_init :: proc(pMutex: ^mutex) -> result --- + + /* + Deletes a mutex. + */ + mutex_uninit :: proc(pMutex: ^mutex) --- + + /* + Locks a mutex with an infinite timeout. + */ + mutex_lock :: proc(pMutex: ^mutex) --- + + /* + Unlocks a mutex. + */ + mutex_unlock :: proc(pMutex: ^mutex) --- + + + /* + Initializes an auto-reset event. + */ + event_init :: proc(pEvent: ^event) -> result --- + + /* + Uninitializes an auto-reset event. + */ + event_uninit :: proc(pEvent: ^event) --- + + /* + Waits for the specified auto-reset event to become signalled. + */ + event_wait :: proc(pEvent: ^event) -> result --- + + /* + Signals the specified auto-reset event. + */ + event_signal :: proc(pEvent: ^event) -> result --- +} /* NO_THREADING */ + +} + +/* +Fence +===== +This locks while the counter is larger than 0. Counter can be incremented and decremented by any +thread, but care needs to be taken when waiting. It is possible for one thread to acquire the +fence just as another thread returns from ma_fence_wait(). + +The idea behind a fence is to allow you to wait for a group of operations to complete. When an +operation starts, the counter is incremented which locks the fence. When the operation completes, +the fence will be released which decrements the counter. ma_fence_wait() will block until the +counter hits zero. + +If threading is disabled, ma_fence_wait() will spin on the counter. +*/ +fence :: struct { + e: (struct {} when NO_THREADING else event), + counter: (u32 when NO_THREADING else struct {}), +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + fence_init :: proc(pFence: ^fence) -> result --- + fence_uninit :: proc(pFence: ^fence) --- + fence_acquire :: proc(pFence: ^fence) -> result --- /* Increment counter. */ + fence_release :: proc(pFence: ^fence) -> result --- /* Decrement counter. */ + fence_wait :: proc(pFence: ^fence) -> result --- /* Wait for counter to reach 0. */ +} + + +/* +Notification callback for asynchronous operations. +*/ +async_notification :: struct {} + +async_notification_callbacks :: struct { + onSignal: proc "c" (pNotification: ^async_notification), +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + async_notification_signal :: proc(pNotification: ^async_notification) -> result --- +} + + +/* +Simple polling notification. + +This just sets a variable when the notification has been signalled which is then polled with ma_async_notification_poll_is_signalled() +*/ +async_notification_poll :: struct { + cb: async_notification_callbacks, + signalled: b32, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + async_notification_poll_init :: proc(pNotificationPoll: ^async_notification_poll) -> result --- + async_notification_poll_is_signalled :: proc(pNotificationPoll: ^async_notification_poll) -> b32 --- +} + + +/* +Event Notification + +This uses an ma_event. If threading is disabled (MA_NO_THREADING), initialization will fail. +*/ +async_notification_event :: struct { + cb: async_notification_callbacks, + e: (struct {} when NO_THREADING else event), +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + async_notification_event_init :: proc(pNotificationEvent: ^async_notification_event) -> result --- + async_notification_event_uninit :: proc(pNotificationEvent: ^async_notification_event) -> result --- + async_notification_event_wait :: proc(pNotificationEvent: ^async_notification_event) -> result --- + async_notification_event_signal :: proc(pNotificationEvent: ^async_notification_event) -> result --- +} diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index 9ced019f5..791482cbf 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -1,5 +1,7 @@ package miniaudio +import c "core:c/libc" + when ODIN_OS == .Windows { foreign import lib "lib/miniaudio.lib" } else when ODIN_OS == .Linux { @@ -10,13 +12,6 @@ when ODIN_OS == .Windows { @(default_calling_convention="c", link_prefix="ma_") foreign lib { - /* - Adjust buffer size based on a scaling factor. - - This just multiplies the base size by the scaling factor, making sure it's a size of at least 1. - */ - scale_buffer_size :: proc(baseBufferSize: u32, scale: f32) -> u32 --- - /* Calculates a buffer size in milliseconds from the specified number of frames and sample rate. */ @@ -51,9 +46,14 @@ foreign lib { /* - Clips f32 samples. + Clips samples. */ - clip_samples_f32 :: proc(p: [^]f32, sampleCount: u64) --- + clip_samples_u8 :: proc(pDst: [^]u8, pSrc: [^]i16, count: u64) --- + clip_samples_s16 :: proc(pDst: [^]i16, pSrc: [^]i32, count: u64) --- + clip_samples_s24 :: proc(pDst: [^]u8, pSrc: [^]i64, count: u64) --- + clip_samples_s32 :: proc(pDst: [^]i32, pSrc: [^]i64, count: u64) --- + clip_samples_f32 :: proc(pDst, pSrc: [^]f32, count: u64) --- + clip_pcm_frames :: proc(pDst, pSrc: rawptr, frameCount: u64, format: format, channels: u32) --- /* Helper for applying a volume factor to samples. @@ -86,20 +86,26 @@ foreign lib { apply_volume_factor_pcm_frames_f32 :: proc(pFrames: [^]f32, frameCount: u64, channels: u32, factor: f32) --- apply_volume_factor_pcm_frames :: proc(pFrames: rawptr, frameCount: u64, format: format, channels: u32, factor: f32) --- + copy_and_apply_volume_factor_per_channel_f32 :: proc(pFramesOut, pFramesIn: [^]f32, frameCount: u64, channels: u32, pChannelGains: [^]f32) --- + + + ma_copy_and_apply_volume_and_clip_samples_u8 :: proc(pDst: [^]u8, pSrc: [^]i16, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_samples_s16 :: proc(pDst: [^]i16, pSrc: [^]i32, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_samples_s24 :: proc(pDst: [^]u8, pSrc: [^]i64, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_samples_s32 :: proc(pDst: [^]i32, pSrc: [^]i64, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_samples_f32 :: proc(pDst, pSrc: [^]f32, count: u64, volume: f32) --- + ma_copy_and_apply_volume_and_clip_pcm_frames :: proc(pDst, pSrc: rawptr, frameCount: u64, format: format, channels: u32, volume: f32) --- + /* Helper for converting a linear factor to gain in decibels. */ - factor_to_gain_db :: proc(factor: f32) -> f32 --- + volume_linear_to_db :: proc(factor: f32) -> f32 --- /* Helper for converting gain in decibels to a linear factor. */ - gain_db_to_factor :: proc(gain: f32) -> f32 --- -} - -zero_pcm_frames :: #force_inline proc "c" (p: rawptr, frameCount: u64, format: format, channels: u32) { - silence_pcm_frames(p, frameCount, format, channels) + volume_db_to_linear :: proc(gain: f32) -> f32 --- } offset_pcm_frames_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFrames: u64, channels: u32) -> [^]f32 { @@ -109,23 +115,20 @@ offset_pcm_frames_const_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFr return cast([^]f32)offset_pcm_frames_ptr(p, offsetInFrames, .f32, channels) } -clip_pcm_frames_f32 :: #force_inline proc "c" (p: [^]f32, frameCount: u64, channels: u32) { - clip_samples_f32(p, frameCount*u64(channels)) -} - data_source :: struct {} +DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT :: 0x00000001 + data_source_vtable :: struct { onRead: proc "c" (pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result, onSeek: proc "c" (pDataSource: ^data_source, frameIndex: u64) -> result, - onMap: proc "c" (pDataSource: ^data_source, ppFramesOut: ^rawptr, pFrameCount: ^u64) -> result, /* Returns MA_AT_END if the end has been reached. This should be considered successful. */ - onUnmap: proc "c" (pDataSource: ^data_source, frameCount: u64) -> result, - onGetDataFormat: proc "c" (pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32) -> result, + onGetDataFormat: proc "c" (pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result, onGetCursor: proc "c" (pDataSource: ^data_source, pCursor: ^u64) -> result, onGetLength: proc "c" (pDataSource: ^data_source, pLength: ^u64) -> result, + onSetLooping: proc "c" (pDataSource: ^data_source, isLooping: b32) -> result, + flags: u32, } -data_source_callbacks :: data_source_vtable /* TODO: Remove ma_data_source_callbacks in version 0.11. */ data_source_get_next_proc :: proc "c" (pDataSource: ^data_source) -> ^data_source @@ -134,45 +137,43 @@ data_source_config :: struct { } data_source_base :: struct { - cb: data_source_callbacks, /* TODO: Remove this. */ - - /* Variables below are placeholder and not yet used. */ vtable: ^data_source_vtable, rangeBegInFrames: u64, - rangeEndInFrames: u64, /* Set to -1 for unranged (default). */ - loopBegInFrames: u64, /* Relative to rangeBegInFrames. */ - loopEndInFrames: u64, /* Relative to rangeBegInFrames. Set to -1 for the end of the range. */ - pCurrent: ^data_source, /* When non-NULL, the data source being initialized will act as a proxy and will route all operations to pCurrent. Used in conjunction with pNext/onGetNext for seamless chaining. */ - pNext: ^data_source, /* When set to NULL, onGetNext will be used. */ - onGetNext: ^data_source_get_next_proc, /* Will be used when pNext is NULL. If both are NULL, no next will be used. */ + rangeEndInFrames: u64, /* Set to -1 for unranged (default). */ + loopBegInFrames: u64, /* Relative to rangeBegInFrames. */ + loopEndInFrames: u64, /* Relative to rangeBegInFrames. Set to -1 for the end of the range. */ + pCurrent: ^data_source, /* When non-NULL, the data source being initialized will act as a proxy and will route all operations to pCurrent. Used in conjunction with pNext/onGetNext for seamless chaining. */ + pNext: ^data_source, /* When set to NULL, onGetNext will be used. */ + onGetNext: data_source_get_next_proc, /* Will be used when pNext is NULL. If both are NULL, no next will be used. */ + isLooping: b32, /*atomic*/ } @(default_calling_convention="c", link_prefix="ma_") foreign lib { - ma_data_source_config_init :: proc() -> data_source_config --- + data_source_config_init :: proc() -> data_source_config --- - data_source_init :: proc(pConfig: ^data_source_config, pDataSource: ^data_source) -> result --- - data_source_uninit :: proc(pDataSource: ^data_source) --- - data_source_read_pcm_frames :: proc(pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64, loop: b32) -> result --- /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ - data_source_seek_pcm_frames :: proc(pDataSource: ^data_source, frameCount: u64, pFramesSeeked: ^u64, loop: b32) -> result --- /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount); */ - data_source_seek_to_pcm_frame :: proc(pDataSource: ^data_source, frameIndex: u64) -> result --- - data_source_map :: proc(pDataSource: ^data_source, ppFramesOut: ^rawptr, pFrameCount: ^u64) -> result --- /* Returns MA_NOT_IMPLEMENTED if mapping is not supported. */ - data_source_unmap :: proc(pDataSource: ^data_source, frameCount: u64) -> result --- /* Returns MA_AT_END if the end has been reached. */ - data_source_get_data_format :: proc(pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32) -> result --- - data_source_get_cursor_in_pcm_frames :: proc(pDataSource: ^data_source, pCursor: ^u64) -> result --- - data_source_get_length_in_pcm_frames :: proc(pDataSource: ^data_source, pLength: ^u64) -> result --- /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ - // #if defined(MA_EXPERIMENTAL__DATA_LOOPING_AND_CHAINING) - // MA_API ma_result ma_data_source_set_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 rangeBegInFrames, ma_uint64 rangeEndInFrames); - // MA_API void ma_data_source_get_range_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pRangeBegInFrames, ma_uint64* pRangeEndInFrames); - // MA_API ma_result ma_data_source_set_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64 loopBegInFrames, ma_uint64 loopEndInFrames); - // MA_API void ma_data_source_get_loop_point_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLoopBegInFrames, ma_uint64* pLoopEndInFrames); - // MA_API ma_result ma_data_source_set_current(ma_data_source* pDataSource, ma_data_source* pCurrentDataSource); - // MA_API ma_data_source* ma_data_source_get_current(ma_data_source* pDataSource); - // MA_API ma_result ma_data_source_set_next(ma_data_source* pDataSource, ma_data_source* pNextDataSource); - // MA_API ma_data_source* ma_data_source_get_next(ma_data_source* pDataSource); - // MA_API ma_result ma_data_source_set_next_callback(ma_data_source* pDataSource, ma_data_source_get_next_proc onGetNext); - // MA_API ma_data_source_get_next_proc ma_data_source_get_next_callback(ma_data_source* pDataSource); - // #endif + data_source_init :: proc(pConfig: ^data_source_config, pDataSource: ^data_source) -> result --- + data_source_uninit :: proc(pDataSource: ^data_source) --- + data_source_read_pcm_frames :: proc(pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- /* Must support pFramesOut = NULL in which case a forward seek should be performed. */ + data_source_seek_pcm_frames :: proc(pDataSource: ^data_source, frameCount: u64, pFramesSeeked: ^u64) -> result --- /* Can only seek forward. Equivalent to ma_data_source_read_pcm_frames(pDataSource, NULL, frameCount); */ + data_source_seek_to_pcm_frame :: proc(pDataSource: ^data_source, frameIndex: u64) -> result --- + data_source_get_data_format :: proc(pDataSource: ^data_source, pFormat: ^format, pChannels: ^u32, pSampleRate: ^u32, pChannelMap: [^]channel, channelMapCap: c.size_t) -> result --- + data_source_get_cursor_in_pcm_frames :: proc(pDataSource: ^data_source, pCursor: ^u64) -> result --- + data_source_get_length_in_pcm_frames :: proc(pDataSource: ^data_source, pLength: ^u64) -> result --- /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ + data_source_get_cursor_in_seconds :: proc(pDataSource: ^data_source, pCursor: ^f32) -> result --- + data_source_get_length_in_seconds :: proc(pDataSource: ^data_source, pLength: ^f32) -> result --- + data_source_set_looping :: proc(pDataSource: ^data_source, isLooping: b32) -> result --- + data_source_is_looping :: proc(pDataSource: ^data_source) -> b32 --- + data_source_set_range_in_pcm_frames :: proc(pDataSource: ^data_source, rangeBegInFrames: u64, rangeEndInFrames: u64) -> result --- + data_source_get_range_in_pcm_frames :: proc(pDataSource: ^data_source, pRangeBegInFrames: ^u64, pRangeEndInFrames: ^u64) --- + data_source_set_loop_point_in_pcm_frames :: proc(pDataSource: ^data_source, loopBegInFrames: u64, loopEndInFrames: u64) -> result --- + data_source_get_loop_point_in_pcm_frames :: proc(pDataSource: ^data_source, pLoopBegInFrames: ^u64, pLoopEndInFrames: ^u64) --- + data_source_set_current :: proc(pDataSource: ^data_source, pCurrentDataSource: ^data_source) -> result --- + data_source_get_current :: proc(pDataSource: ^data_source) -> ^data_source --- + data_source_set_next :: proc(pDataSource: ^data_source, pNextDataSource: ^data_source) -> result --- + data_source_get_next :: proc(pDataSource: ^data_source) -> ^data_source --- + data_source_set_next_callback :: proc(pDataSource: ^data_source, onGetNext: ^data_source_get_next_proc) -> result --- + data_source_get_next_callback :: proc(pDataSource: ^data_source) -> ^data_source_get_next_proc --- } @@ -180,6 +181,7 @@ audio_buffer_ref :: struct { ds: data_source_base, format: format, channels: u32, + sampleRate: u32, cursor: u64, sizeInFrames: u64, pData: rawptr, @@ -204,6 +206,7 @@ foreign lib { audio_buffer_config :: struct { format: format, channels: u32, + sampleRate: u32, sizeInFrames: u64, pData: rawptr, /* If set to NULL, will allocate a block of memory for you. */ allocationCallbacks: allocation_callbacks, @@ -234,3 +237,65 @@ foreign lib { audio_buffer_get_length_in_pcm_frames :: proc(pAudioBuffer: ^audio_buffer, pLength: ^u64) -> result --- audio_buffer_get_available_frames :: proc(pAudioBuffer: ^audio_buffer, pAvailableFrames: ^u64) -> result --- } + +/* +Paged Audio Buffer +================== +A paged audio buffer is made up of a linked list of pages. It's expandable, but not shrinkable. It +can be used for cases where audio data is streamed in asynchronously while allowing data to be read +at the same time. + +This is lock-free, but not 100% thread safe. You can append a page and read from the buffer across +simultaneously across different threads, however only one thread at a time can append, and only one +thread at a time can read and seek. +*/ +paged_audio_buffer_page :: struct { + pNext: ^paged_audio_buffer_page, /*atomic*/ + sizeInFrames: u64, + pAudioData: [1]u8, +}; + +paged_audio_buffer_data :: struct { + format: format, + channels: u32, + head: paged_audio_buffer_page, /* Dummy head for the lock-free algorithm. Always has a size of 0. */ + pTail: ^paged_audio_buffer_page, /*atomic*/ /* Never null. Initially set to &head. */ +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + paged_audio_buffer_data_init :: proc(format: format, channels: u32, pData: ^paged_audio_buffer_data) -> result --- + paged_audio_buffer_data_uninit :: proc(pData: ^paged_audio_buffer_data, pAllocationCallbacks: ^allocation_callbacks) --- + paged_audio_buffer_data_get_head :: proc(pData: ^paged_audio_buffer_data) -> ^paged_audio_buffer_page --- + paged_audio_buffer_data_get_tail :: proc(pData: ^paged_audio_buffer_data) -> ^paged_audio_buffer_page --- + paged_audio_buffer_data_get_length_in_pcm_frames :: proc(pData: ^paged_audio_buffer_data, pLength: ^u64) -> result --- + paged_audio_buffer_data_allocate_page :: proc(pData: ^paged_audio_buffer_data, pageSizeInFrames: u64, pInitialData: rawptr, pAllocationCallbacks: ^allocation_callbacks, ppPage: ^^paged_audio_buffer_page) -> result --- + paged_audio_buffer_data_free_page :: proc(pData: ^paged_audio_buffer_data, pPage: ^paged_audio_buffer_page, pAllocationCallbacks: ^allocation_callbacks) -> result --- + paged_audio_buffer_data_append_page :: proc(pData: ^paged_audio_buffer_data, pPage: ^paged_audio_buffer_page) -> result --- + paged_audio_buffer_data_allocate_and_append_page :: proc(pData: ^paged_audio_buffer_data, pageSizeInFrames: u32, pInitialData: rawptr, pAllocationCallbacks: ^allocation_callbacks) -> result --- +} + + +paged_audio_buffer_config :: struct { + pData: ^paged_audio_buffer_data, /* Must not be null. */ +} + +paged_audio_buffer :: struct { + ds: data_source_base, + pData: ^paged_audio_buffer_data, /* Audio data is read from here. Cannot be null. */ + pCurrent: ^paged_audio_buffer_page, + relativeCursor: u64, /* Relative to the current page. */ + absoluteCursor: u64, +} + +@(default_calling_convention="c", link_prefix="ma_") +foreign lib { + paged_audio_buffer_config_init :: proc(pData: ^paged_audio_buffer_data) -> paged_audio_buffer_config --- + + paged_audio_buffer_init :: proc(pConfig: ^paged_audio_buffer_config, pPagedAudioBuffer: ^paged_audio_buffer) -> result --- + paged_audio_buffer_uninit :: proc(pPagedAudioBuffer: ^paged_audio_buffer) --- + paged_audio_buffer_read_pcm_frames :: proc(pPagedAudioBuffer: ^paged_audio_buffer, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result --- /* Returns MA_AT_END if no more pages available. */ + paged_audio_buffer_seek_to_pcm_frame :: proc(pPagedAudioBuffer: ^paged_audio_buffer, frameIndex: u64) -> result --- + paged_audio_buffer_get_cursor_in_pcm_frames :: proc(pPagedAudioBuffer: ^paged_audio_buffer, pCursor: ^u64) -> result --- + paged_audio_buffer_get_length_in_pcm_frames :: proc(pPagedAudioBuffer: ^paged_audio_buffer, pLength: ^u64) -> result --- +} diff --git a/vendor/miniaudio/vfs.odin b/vendor/miniaudio/vfs.odin index 85571341e..9731c713f 100644 --- a/vendor/miniaudio/vfs.odin +++ b/vendor/miniaudio/vfs.odin @@ -22,8 +22,10 @@ appropriate for a given situation. vfs :: struct {} vfs_file :: distinct handle -OPEN_MODE_READ :: 0x00000001 -OPEN_MODE_WRITE :: 0x00000002 +open_mode_flags :: enum c.int { + READ = 0x00000001, + WRITE = 0x00000002, +} seek_origin :: enum c.int { start, @@ -71,10 +73,6 @@ foreign lib { default_vfs_init :: proc(pVFS: ^default_vfs, pAllocationCallbacks: ^allocation_callbacks) -> result --- } -resource_format :: enum c.int { - wav, -} - encoding_format :: enum c.int { unknown = 0, wav, From 4911df9f99d8c0de4531c794c8fc7e7fccf009f0 Mon Sep 17 00:00:00 2001 From: bkrypt <4868093+bkrypt@users.noreply.github.com> Date: Fri, 29 Apr 2022 21:39:10 +0200 Subject: [PATCH 024/254] Remove unneeded semicolons --- vendor/miniaudio/node_graph.odin | 4 ++-- vendor/miniaudio/resource_manager.odin | 2 +- vendor/miniaudio/utilities.odin | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin index ac47d43d8..c0df39c0f 100644 --- a/vendor/miniaudio/node_graph.odin +++ b/vendor/miniaudio/node_graph.odin @@ -156,7 +156,7 @@ node_base :: struct { _outputBuses: [MAX_NODE_LOCAL_BUS_COUNT]node_output_bus, _pHeap: rawptr, /* A heap allocation for internal use only. pInputBuses and/or pOutputBuses will point to this if the bus count exceeds MA_MAX_NODE_LOCAL_BUS_COUNT. */ _ownsHeap: b32, /* If set to true, the node owns the heap allocation and _pHeap will be freed in ma_node_uninit(). */ -}; +} @(default_calling_convention="c", link_prefix="ma_") foreign lib { @@ -199,7 +199,7 @@ node_graph :: struct { /* Read and written by multiple threads. */ isReading: b32, /*atomic*/ -}; +} @(default_calling_convention="c", link_prefix="ma_") foreign lib { diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index c4d722342..e67d4a475 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -218,7 +218,7 @@ foreign lib { /* Init. */ resource_manager_init :: proc(pConfig: ^resource_manager_config, pResourceManager: ^resource_manager) -> result --- resource_manager_uninit :: proc(pResourceManager: ^resource_manager) --- - resource_manager_get_log :: proc(pResourceManager: ^resource_manager) -> ^log ---; + resource_manager_get_log :: proc(pResourceManager: ^resource_manager) -> ^log --- /* Registration. */ resource_manager_register_file :: proc(pResourceManager: ^resource_manager, pFilePath: cstring, flags: u32) -> result --- diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index 791482cbf..708cc820e 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -253,7 +253,7 @@ paged_audio_buffer_page :: struct { pNext: ^paged_audio_buffer_page, /*atomic*/ sizeInFrames: u64, pAudioData: [1]u8, -}; +} paged_audio_buffer_data :: struct { format: format, From 9e694523277b75a44e0dc58dafec910c2fa0e8f6 Mon Sep 17 00:00:00 2001 From: bkrypt <4868093+bkrypt@users.noreply.github.com> Date: Sat, 30 Apr 2022 20:42:42 +0200 Subject: [PATCH 025/254] Remove unnecessary value (`count`) from enum --- vendor/miniaudio/common.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/vendor/miniaudio/common.odin b/vendor/miniaudio/common.odin index d7b901714..1d64dc182 100644 --- a/vendor/miniaudio/common.odin +++ b/vendor/miniaudio/common.odin @@ -193,7 +193,6 @@ format :: enum c.int { s24 = 3, /* Tightly packed. 3 bytes per sample. */ s32 = 4, f32 = 5, - count, } standard_sample_rate :: enum u32 { From be9b935953c50b7a9e8ee73442717335155ccb03 Mon Sep 17 00:00:00 2001 From: bkrypt <4868093+bkrypt@users.noreply.github.com> Date: Sat, 30 Apr 2022 20:43:22 +0200 Subject: [PATCH 026/254] Fix indentation --- vendor/miniaudio/device_io_types.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index b3ecb2301..5a2c4bc73 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -19,11 +19,11 @@ SUPPORT_CUSTOM :: true SUPPORT_NULL :: true // ODIN_OS != .Emscripten device_state :: enum c.int { - uninitialized = 0, - stopped = 1, /* The device's default state after initialization. */ - started = 2, /* The device is started and is requesting and/or delivering audio data. */ - starting = 3, /* Transitioning from a stopped state to started. */ - stopping = 4, /* Transitioning from a started state to stopped. */ + uninitialized = 0, + stopped = 1, /* The device's default state after initialization. */ + started = 2, /* The device is started and is requesting and/or delivering audio data. */ + starting = 3, /* Transitioning from a stopped state to started. */ + stopping = 4, /* Transitioning from a started state to stopped. */ } @@ -985,7 +985,7 @@ device :: struct { noPreSilencedOutputBuffer: b8, noClip: b8, noDisableDenormals: b8, - noFixedSizedCallback: b8, + noFixedSizedCallback: b8, masterVolumeFactor: f32, /*atomic*/ /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ duplexRB: duplex_rb, /* Intermediary buffer for duplex device on asynchronous backends. */ resampling: struct { From 7e0cc0af252810ea0177eabbb773695c33bd3544 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 4 May 2022 17:55:15 -0400 Subject: [PATCH 027/254] heap_linux.odin --- core/os/os2/heap_linux.odin | 719 +++++++++++++++++++++++++++++++++++- 1 file changed, 709 insertions(+), 10 deletions(-) diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index f617f8cc8..d72fab3ec 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,27 +1,726 @@ //+private package os2 +import "core:sys/unix" +import "core:sync" import "core:mem" -heap_alloc :: proc(size: int) -> rawptr { - // TODO - return nil +// NOTEs +// +// All allocations below DIRECT_MMAP_THRESHOLD exist inside of memory "Regions." A region +// consists of a Region_Header and the memory that will be divided into allocations to +// send to the user. The memory is an array of "Allocation_Headers" which are 8 bytes. +// Allocation_Headers are used to navigate the memory in the region. The "next" member of +// the Allocation_Header points to the next header, and the space between the headers +// can be used to send to the user. This space between is referred to as "blocks" in the +// code. The indexes in the header refer to these blocks instead of bytes. This allows us +// to index all the memory in the region with a u16. +// +// When an allocation request is made, it will use the first free block that can contain +// the entire block. If there is an excess number of blocks (as specified by the constant +// BLOCK_SEGMENT_THRESHOLD), this extra space will be segmented and left in the free_list. +// +// To keep the implementation simple, there can never exist 2 free blocks adjacent to each +// other. Any freeing will result in attempting to merge the blocks before and after the +// newly free'd blocks. +// +// Any request for size above the DIRECT_MMAP_THRESHOLD will result in the allocation +// getting its own individual mmap. Individual mmaps will still get an Allocation_Header +// that contains the size with the last bit set to 1 to indicate it is indeed a direct +// mmap allocation. + +// Why not brk? +// glibc's malloc utilizes a mix of the brk and mmap system calls. This implementation +// does *not* utilize the brk system call to avoid possible conflicts with foreign C +// code. Just because we aren't directly using libc, there is nothing stopping the user +// from doing it. + +// What's with all the #no_bounds_check? +// When memory is returned from mmap, it technically doesn't get written ... well ... anywhere +// until that region is written to by *you*. So, when a new region is created, we call mmap +// to get a pointer to some memory, and we claim that memory is a ^Region. Therefor, the +// region itself is never formally initialized by the compiler as this would result in writing +// zeros to memory that we can already assume are 0. This would also have the effect of +// actually commiting this data to memory whether it gets used or not. + + +// +// Some variables to play with +// + +// Minimum blocks used for any one allocation +MINIMUM_BLOCK_COUNT :: 2 + +// Number of extra blocks beyond the requested amount where we would segment. +// E.g. (blocks) |H0123456| 7 available +// |H01H0123| Ask for 2, now 4 available +BLOCK_SEGMENT_THRESHOLD :: 4 + +// Anything above this threshold will get its own memory map. Since regions +// are indexed by 16 bit integers, this value should not surpass max(u16) * 6 +DIRECT_MMAP_THRESHOLD_USER :: int(max(u16)) + +// The point at which we convert direct mmap to region. This should be a decent +// amount less than DIRECT_MMAP_THRESHOLD to avoid jumping in and out of regions. +MMAP_TO_REGION_SHRINK_THRESHOLD :: DIRECT_MMAP_THRESHOLD - PAGE_SIZE * 4 + +// free_list is dynamic and is initialized in the begining of the region memory +// when the region is initialized. Once resized, it can be moved anywhere. +FREE_LIST_DEFAULT_CAP :: 32 + + +// +// Other constants that should not be touched +// + +// This universally seems to be 4096 outside of uncommon archs. +PAGE_SIZE :: 4096 + +// just rounding up to nearest PAGE_SIZE +DIRECT_MMAP_THRESHOLD :: (DIRECT_MMAP_THRESHOLD_USER-1) + PAGE_SIZE - (DIRECT_MMAP_THRESHOLD_USER-1) % PAGE_SIZE + +// Regions must be big enough to hold DIRECT_MMAP_THRESHOLD - 1 as well +// as end right on a page boundary as to not waste space. +SIZE_OF_REGION :: DIRECT_MMAP_THRESHOLD + 4 * int(PAGE_SIZE) + +// size of user memory blocks +BLOCK_SIZE :: size_of(Allocation_Header) + +// number of allocation sections (call them blocks) of the region used for allocations +BLOCKS_PER_REGION :: u16((SIZE_OF_REGION - size_of(Region_Header)) / BLOCK_SIZE) + +// minimum amount of space that can used by any individual allocation (includes header) +MINIMUM_ALLOCATION :: (MINIMUM_BLOCK_COUNT * BLOCK_SIZE) + BLOCK_SIZE + +// This is used as a boolean value for Region_Header.local_addr. +CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0)) + +FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16) + +MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE +MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE + + +//@thread_local _local_region: ^Region +_local_region: ^Region +global_regions: ^Region + + +// There is no way of correctly setting the last bit of free_idx or +// the last bit of requested, so we can safely use it as a flag to +// determine if we are interacting with a direct mmap. +REQUESTED_MASK :: 0x7FFFFFFFFFFFFFFF +IS_DIRECT_MMAP :: 0x8000000000000000 + +// Special free_idx value that does not index the free_list. +NOT_FREE :: 0x7FFF +Allocation_Header :: struct #raw_union { + using _: struct { + // Block indicies + idx: u16, + prev: u16, + next: u16, + free_idx: u16, + }, + requested: u64, } -heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { - // TODO - return nil +Region_Header :: struct #align 16 { + next_region: ^Region, // points to next region in global_heap (linked list) + local_addr: ^^Region, // tracks region ownership via address of _local_region + reset_addr: ^^Region, // tracks old local addr for reset + free_list: []u16, + free_list_len: u16, + free_blocks: u16, // number of free blocks in region (includes headers) + last_used: u16, // farthest back block that has been used (need zeroing?) + _reserved: u16, } -heap_free :: proc(ptr: rawptr) { - if ptr == nil { + +Region :: struct { + hdr: Region_Header, + memory: [BLOCKS_PER_REGION]Allocation_Header, +} + +heap_alloc :: proc(size: int) -> rawptr { + if size >= DIRECT_MMAP_THRESHOLD { + return _direct_mmap_alloc(size) + } + + // atomically check if the local region has been stolen + if _local_region != nil { + res := sync.atomic_compare_exchange_strong_explicit( + &_local_region.hdr.local_addr, + &_local_region, + CURRENTLY_ACTIVE, + .Acquire, + .Relaxed, + ) + if res != &_local_region { + // At this point, the region has been stolen and res contains the unexpected value + expected := res + if res != CURRENTLY_ACTIVE { + expected = res + res = sync.atomic_compare_exchange_strong_explicit( + &_local_region.hdr.local_addr, + expected, + CURRENTLY_ACTIVE, + .Acquire, + .Relaxed, + ) + } + if res != expected { + _local_region = nil + } + } + } + + size := size + size = _round_up_to_nearest(size, BLOCK_SIZE) + blocks_needed := u16(max(MINIMUM_BLOCK_COUNT, size / BLOCK_SIZE)) + + // retrieve a region if new thread or stolen + if _local_region == nil { + _local_region, _ = _region_retrieve_with_space(blocks_needed) + if _local_region == nil { + return nil + } + } + defer sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + + // At this point we have a usable region. Let's find the user some memory + idx: u16 + local_region_idx := _region_get_local_idx() + back_idx := -1 + infinite: for { + for i := 0; i < int(_local_region.hdr.free_list_len); i += 1 { + idx = _local_region.hdr.free_list[i] + #no_bounds_check if _get_block_count(_local_region.memory[idx]) >= blocks_needed { + break infinite + } + } + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + _local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx) + } + user_ptr, used := _region_get_block(_local_region, idx, blocks_needed) + _local_region.hdr.free_blocks -= (used + 1) + + // If this memory was ever used before, it now needs to be zero'd. + if idx < _local_region.hdr.last_used { + mem.zero(user_ptr, int(used) * BLOCK_SIZE) + } else { + _local_region.hdr.last_used = idx + used + } + + return user_ptr +} + +heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_check { + alloc := _get_allocation_header(old_memory) + if alloc.requested & IS_DIRECT_MMAP > 0 { + return _direct_mmap_resize(alloc, new_size) + } + + if new_size > DIRECT_MMAP_THRESHOLD { + return _direct_mmap_from_region(alloc, new_size) + } + + return _region_resize(alloc, new_size) +} + +heap_free :: proc(memory: rawptr) { + alloc := _get_allocation_header(memory) + if alloc.requested & IS_DIRECT_MMAP == IS_DIRECT_MMAP { + _direct_mmap_free(alloc) return } - // TODO + + assert(alloc.free_idx == NOT_FREE) + + _region_find_and_assign_local(alloc) + _region_local_free(alloc) + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) +} + +// +// Regions +// +_new_region :: proc() -> ^Region #no_bounds_check { + res := unix.sys_mmap(nil, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) + if res < 0 { + return nil + } + new_region := (^Region)(uintptr(res)) + + new_region.hdr.local_addr = CURRENTLY_ACTIVE + new_region.hdr.reset_addr = &_local_region + + free_list_blocks := _round_up_to_nearest(FREE_LIST_DEFAULT_CAP, FREE_LIST_ENTRIES_PER_BLOCK) + _region_assign_free_list(new_region, &new_region.memory[1], u16(free_list_blocks) * FREE_LIST_ENTRIES_PER_BLOCK) + + // + 2 to account for free_list's allocation header + first_user_block := len(new_region.hdr.free_list) / FREE_LIST_ENTRIES_PER_BLOCK + 2 + + // first allocation header (this is a free list) + new_region.memory[0].next = u16(first_user_block) + new_region.memory[0].free_idx = NOT_FREE + new_region.memory[first_user_block].idx = u16(first_user_block) + new_region.memory[first_user_block].next = BLOCKS_PER_REGION - 1 + + // add the first user block to the free list + new_region.hdr.free_list[0] = u16(first_user_block) + new_region.hdr.free_list_len = 1 + new_region.hdr.free_blocks = _get_block_count(new_region.memory[first_user_block]) + 1 + + for r := sync.atomic_compare_exchange_strong(&global_regions, nil, new_region); + r != nil; + r = sync.atomic_compare_exchange_strong(&r.hdr.next_region, nil, new_region) {} + + return new_region +} + +_region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_list: bool = false) -> rawptr #no_bounds_check { + assert(alloc.free_idx == NOT_FREE) + + old_memory := mem.ptr_offset(alloc, 1) + + old_block_count := _get_block_count(alloc^) + new_block_count := u16( + max(MINIMUM_BLOCK_COUNT, _round_up_to_nearest(new_size, BLOCK_SIZE) / BLOCK_SIZE), + ) + if new_block_count < old_block_count { + if new_block_count - old_block_count >= MINIMUM_BLOCK_COUNT { + _region_find_and_assign_local(alloc) + _region_segment(_local_region, alloc, new_block_count, alloc.free_idx) + new_block_count = _get_block_count(alloc^) + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + } + // need to zero anything within the new block that that lies beyond new_size + extra_bytes := int(new_block_count * BLOCK_SIZE) - new_size + extra_bytes_ptr := mem.ptr_offset((^u8)(alloc), new_size + BLOCK_SIZE) + mem.zero(extra_bytes_ptr, extra_bytes) + return old_memory + } + + if !alloc_is_free_list { + _region_find_and_assign_local(alloc) + } + defer if !alloc_is_free_list { + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + } + + // First, let's see if we can grow in place. + if alloc.next != BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { + next_alloc := _local_region.memory[alloc.next] + total_available := old_block_count + _get_block_count(next_alloc) + 1 + if total_available >= new_block_count { + alloc.next = next_alloc.next + _local_region.memory[alloc.next].prev = alloc.idx + if total_available - new_block_count > BLOCK_SEGMENT_THRESHOLD { + _region_segment(_local_region, alloc, new_block_count, next_alloc.free_idx) + } else { + _region_free_list_remove(_local_region, next_alloc.free_idx) + } + mem.zero(&_local_region.memory[next_alloc.idx], int(alloc.next - next_alloc.idx) * BLOCK_SIZE) + _local_region.hdr.last_used = max(alloc.next, _local_region.hdr.last_used) + _local_region.hdr.free_blocks -= (_get_block_count(alloc^) - old_block_count) + if alloc_is_free_list { + _region_assign_free_list(_local_region, old_memory, _get_block_count(alloc^)) + } + return old_memory + } + } + + // If we made it this far, we need to resize, copy, zero and free. + region_iter := _local_region + local_region_idx := _region_get_local_idx() + back_idx := -1 + idx: u16 + infinite: for { + for i := 0; i < len(region_iter.hdr.free_list); i += 1 { + idx = region_iter.hdr.free_list[i] + if _get_block_count(region_iter.memory[idx]) >= new_block_count { + break infinite + } + } + if region_iter != _local_region { + sync.atomic_store_explicit( + ®ion_iter.hdr.local_addr, + region_iter.hdr.reset_addr, + .Release, + ) + } + region_iter, back_idx = _region_retrieve_with_space(new_block_count, local_region_idx, back_idx) + } + if region_iter != _local_region { + sync.atomic_store_explicit( + ®ion_iter.hdr.local_addr, + region_iter.hdr.reset_addr, + .Release, + ) + } + + // copy from old memory + new_memory, used_blocks := _region_get_block(region_iter, idx, new_block_count) + mem.copy(new_memory, old_memory, int(old_block_count * BLOCK_SIZE)) + + // zero any new memory + addon_section := mem.ptr_offset((^Allocation_Header)(new_memory), old_block_count) + new_blocks := used_blocks - old_block_count + mem.zero(addon_section, int(new_blocks) * BLOCK_SIZE) + + region_iter.hdr.free_blocks -= (used_blocks + 1) + + // Set free_list before freeing. + if alloc_is_free_list { + _region_assign_free_list(_local_region, new_memory, used_blocks) + } + + // free old memory + _region_local_free(alloc) + return new_memory +} + +_region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { + alloc := alloc + add_to_free_list := true + + _local_region.hdr.free_blocks += _get_block_count(alloc^) + 1 + + // try to merge with prev + if alloc.idx > 0 && _local_region.memory[alloc.prev].free_idx != NOT_FREE { + _local_region.memory[alloc.prev].next = alloc.next + _local_region.memory[alloc.next].prev = alloc.prev + alloc = &_local_region.memory[alloc.prev] + add_to_free_list = false + } + + // try to merge with next + if alloc.next < BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { + old_next := alloc.next + alloc.next = _local_region.memory[old_next].next + _local_region.memory[alloc.next].prev = alloc.idx + + if add_to_free_list { + _local_region.hdr.free_list[_local_region.memory[old_next].free_idx] = alloc.idx + alloc.free_idx = _local_region.memory[old_next].free_idx + } else { + // NOTE: We have aleady merged with prev, and now merged with next. + // Now, we are actually going to remove from the free_list. + _region_free_list_remove(_local_region, _local_region.memory[old_next].free_idx) + } + add_to_free_list = false + } + + // This is the only place where anything is appended to the free list. + if add_to_free_list { + fl := _local_region.hdr.free_list + alloc.free_idx = _local_region.hdr.free_list_len + fl[alloc.free_idx] = alloc.idx + _local_region.hdr.free_list_len += 1 + if int(_local_region.hdr.free_list_len) == len(fl) { + free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list)) + _region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true) + } + } +} + +_region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) { + raw_free_list := transmute(mem.Raw_Slice)region.hdr.free_list + raw_free_list.len = int(blocks) * FREE_LIST_ENTRIES_PER_BLOCK + raw_free_list.data = memory + region.hdr.free_list = transmute([]u16)(raw_free_list) +} + +_region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) { + r: ^Region + idx: int + for r = global_regions; r != nil; r = r.hdr.next_region { + if idx == local_idx || idx < back_idx || r.hdr.free_blocks < blocks { + idx += 1 + continue + } + idx += 1 + local_addr: ^^Region = sync.atomic_load(&r.hdr.local_addr) + if local_addr != CURRENTLY_ACTIVE { + res := sync.atomic_compare_exchange_strong_explicit( + &r.hdr.local_addr, + local_addr, + CURRENTLY_ACTIVE, + .Acquire, + .Relaxed, + ) + if res == local_addr { + r.hdr.reset_addr = local_addr + return r, idx + } + } + } + + return _new_region(), idx +} + +_region_retrieve_from_addr :: proc(addr: rawptr) -> ^Region { + r: ^Region + for r = global_regions; r != nil; r = r.hdr.next_region { + if _region_contains_mem(r, addr) { + return r + } + } + unreachable() +} + +_region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, u16) #no_bounds_check { + alloc := ®ion.memory[idx] + + assert(alloc.free_idx != NOT_FREE) + assert(alloc.next > 0) + + block_count := _get_block_count(alloc^) + segmented_blocks: u16 + + if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD { + _region_segment(region, alloc, blocks_needed, alloc.free_idx) + } else { + _region_free_list_remove(region, alloc.free_idx) + } + + alloc.free_idx = NOT_FREE + return mem.ptr_offset(alloc, 1), _get_block_count(alloc^) +} + +_region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_free_idx: u16) #no_bounds_check { + old_next := alloc.next + alloc.next = alloc.idx + blocks + 1 + region.memory[old_next].prev = alloc.next + + // Initialize alloc.next allocation header here. + region.memory[alloc.next].prev = alloc.idx + region.memory[alloc.next].next = old_next + region.memory[alloc.next].idx = alloc.next + region.memory[alloc.next].free_idx = new_free_idx + + // Replace our original spot in the free_list with new segment. + region.hdr.free_list[new_free_idx] = alloc.next +} + +_region_get_local_idx :: proc() -> int { + idx: int + for r := global_regions; r != nil; r = r.hdr.next_region { + if r == _local_region { + return idx + } + idx += 1 + } + + return -1 +} + +_region_find_and_assign_local :: proc(alloc: ^Allocation_Header) { + // Find the region that contains this memory + if !_region_contains_mem(_local_region, alloc) { + _local_region = _region_retrieve_from_addr(alloc) + } + + // At this point, _local_region is set correctly. Spin until acquired + res: ^^Region + for res != &_local_region { + res = sync.atomic_compare_exchange_strong_explicit( + &_local_region.hdr.local_addr, + &_local_region, + CURRENTLY_ACTIVE, + .Acquire, + .Relaxed, + ) + } +} + +_region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_check { + if r == nil { + return false + } + mem_int := uintptr(memory) + return mem_int >= uintptr(&r.memory[0]) && mem_int <= uintptr(&r.memory[BLOCKS_PER_REGION - 1]) +} + +_region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check { + // pop, swap and update allocation hdr + if n := region.hdr.free_list_len - 1; free_idx != n { + region.hdr.free_list[free_idx] = region.hdr.free_list[n] + alloc_idx := region.hdr.free_list[free_idx] + region.memory[alloc_idx].free_idx = free_idx + } + region.hdr.free_list_len -= 1 +} + +// +// Direct mmap +// +_direct_mmap_alloc :: proc(size: int) -> rawptr { + mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE) + new_allocation := unix.sys_mmap(nil, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0) + if new_allocation < 0 && new_allocation > -4096 { + return nil + } + + alloc := (^Allocation_Header)(uintptr(new_allocation)) + alloc.requested = u64(size) // NOTE: requested = requested size + alloc.requested += IS_DIRECT_MMAP + return rawptr(mem.ptr_offset(alloc, 1)) +} + +_direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { + old_requested := int(alloc.requested & REQUESTED_MASK) + old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE) + new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE) + if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD { + return _direct_mmap_to_region(alloc, old_mmap_size, new_mmap_size) + } else if old_requested == new_size { + return mem.ptr_offset(alloc, 1) + } + + new_allocation := unix.sys_mremap( + alloc, + uint(old_mmap_size), + uint(new_mmap_size), + unix.MREMAP_MAYMOVE, + ) + if new_allocation < 0 && new_allocation > -4096 { + return nil + } + + new_header := (^Allocation_Header)(uintptr(new_allocation)) + new_header.requested = u64(new_size) + new_header.requested += IS_DIRECT_MMAP + + if new_mmap_size > old_mmap_size { + // new section may not be pointer aligned, so cast to ^u8 + new_section := mem.ptr_offset((^u8)(new_header), old_requested + BLOCK_SIZE) + mem.zero(new_section, new_mmap_size - old_mmap_size) + } + return mem.ptr_offset(new_header, 1) + +} + +_direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { + new_memory := _direct_mmap_alloc(new_size) + if new_memory != nil { + old_memory := mem.ptr_offset(alloc, 1) + mem.copy(new_memory, old_memory, int(_get_block_count(alloc^)) * BLOCK_SIZE) + } + _region_find_and_assign_local(alloc) + _region_local_free(alloc) + sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) + return new_memory +} + +_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, old_size, new_size: int) -> rawptr { + new_memory := heap_alloc(new_size) + if new_memory != nil { + mem.copy(new_memory, mem.ptr_offset(alloc, -1), old_size) + _direct_mmap_free(alloc) + } + return new_memory +} + +_direct_mmap_free :: proc(alloc: ^Allocation_Header) { + requested := int(alloc.requested & REQUESTED_MASK) + mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE) + unix.sys_munmap(alloc, uint(mmap_size)) +} + +// +// Util +// + +_get_block_count :: #force_inline proc(alloc: Allocation_Header) -> u16 { + return alloc.next - alloc.idx - 1 +} + +_get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Header { + return mem.ptr_offset((^Allocation_Header)(raw_mem), -1) +} + +_round_up_to_nearest :: #force_inline proc(size, round: int) -> int { + return (size-1) + round - (size-1) % round } _heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { - // TODO + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) + } else { + allocated_mem = heap_alloc(space+size_of(rawptr)) + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space { + return nil, .Out_Of_Memory + } + + aligned_mem = rawptr(aligned_ptr) + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem + + return mem.byte_slice(aligned_mem, size), nil + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + heap_free(mem.ptr_offset((^rawptr)(p), -1)^) + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { + if p == nil { + return nil, nil + } + + return aligned_alloc(new_size, new_alignment, p) + } + + switch mode { + case .Alloc: + return aligned_alloc(size, alignment) + + case .Free: + aligned_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize: + if old_memory == nil { + return aligned_alloc(size, alignment) + } + return aligned_resize(old_memory, old_size, size, alignment) + + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + return nil, nil } + From bac96cf2ad8929c0ff06670fd40ed847fe997ba8 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 4 May 2022 18:32:14 -0400 Subject: [PATCH 028/254] fix mmap_to_region --- core/os/os2/heap_linux.odin | 169 ++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 86 deletions(-) diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index d72fab3ec..8706b51ef 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -141,6 +141,86 @@ Region :: struct { memory: [BLOCKS_PER_REGION]Allocation_Header, } +_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { + a := max(alignment, align_of(rawptr)) + space := size + a - 1 + + allocated_mem: rawptr + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) + } else { + allocated_mem = heap_alloc(space+size_of(rawptr)) + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) + + ptr := uintptr(aligned_mem) + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) + diff := int(aligned_ptr - ptr) + if (size + diff) > space { + return nil, .Out_Of_Memory + } + + aligned_mem = rawptr(aligned_ptr) + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem + + return mem.byte_slice(aligned_mem, size), nil + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + heap_free(mem.ptr_offset((^rawptr)(p), -1)^) + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { + if p == nil { + return nil, nil + } + + return aligned_alloc(new_size, new_alignment, p) + } + + switch mode { + case .Alloc: + return aligned_alloc(size, alignment) + + case .Free: + aligned_free(old_memory) + + case .Free_All: + return nil, .Mode_Not_Implemented + + case .Resize: + if old_memory == nil { + return aligned_alloc(size, alignment) + } + return aligned_resize(old_memory, old_size, size, alignment) + + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return nil, nil +} + heap_alloc :: proc(size: int) -> rawptr { if size >= DIRECT_MMAP_THRESHOLD { return _direct_mmap_alloc(size) @@ -476,8 +556,6 @@ _region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, assert(alloc.next > 0) block_count := _get_block_count(alloc^) - segmented_blocks: u16 - if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD { _region_segment(region, alloc, blocks_needed, alloc.free_idx) } else { @@ -573,7 +651,7 @@ _direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE) new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE) if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD { - return _direct_mmap_to_region(alloc, old_mmap_size, new_mmap_size) + return _direct_mmap_to_region(alloc, new_size) } else if old_requested == new_size { return mem.ptr_offset(alloc, 1) } @@ -613,10 +691,10 @@ _direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> ra return new_memory } -_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, old_size, new_size: int) -> rawptr { +_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { new_memory := heap_alloc(new_size) if new_memory != nil { - mem.copy(new_memory, mem.ptr_offset(alloc, -1), old_size) + mem.copy(new_memory, mem.ptr_offset(alloc, -1), new_size) _direct_mmap_free(alloc) } return new_memory @@ -643,84 +721,3 @@ _get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Hea _round_up_to_nearest :: #force_inline proc(size, round: int) -> int { return (size-1) + round - (size-1) % round } - -_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { - // - // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. - // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert - // padding. We also store the original pointer returned by heap_alloc right before - // the pointer we return to the user. - // - - aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr)) - } - aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space { - return nil, .Out_Of_Memory - } - - aligned_mem = rawptr(aligned_ptr) - mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem - - return mem.byte_slice(aligned_mem, size), nil - } - - aligned_free :: proc(p: rawptr) { - if p != nil { - heap_free(mem.ptr_offset((^rawptr)(p), -1)^) - } - } - - aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { - if p == nil { - return nil, nil - } - - return aligned_alloc(new_size, new_alignment, p) - } - - switch mode { - case .Alloc: - return aligned_alloc(size, alignment) - - case .Free: - aligned_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize: - if old_memory == nil { - return aligned_alloc(size, alignment) - } - return aligned_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^mem.Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Free, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - From 97d1a6787189d7630650612f44c393f7a635019a Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 4 May 2022 18:45:39 -0400 Subject: [PATCH 029/254] make vet happy, thread_local heap --- core/os/os2/heap_linux.odin | 4 ++-- core/os/os2/path_linux.odin | 20 +++++++++----------- core/os/os2/stat_linux.odin | 1 - 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index 8706b51ef..c470d2007 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -101,8 +101,8 @@ MMAP_FLAGS :: unix.MAP_ANONYMOUS | unix.MAP_PRIVATE MMAP_PROT :: unix.PROT_READ | unix.PROT_WRITE -//@thread_local _local_region: ^Region -_local_region: ^Region +@thread_local _local_region: ^Region +//_local_region: ^Region global_regions: ^Region diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 5dadb7608..8e5aa35fe 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -3,7 +3,6 @@ package os2 import "core:strings" import "core:sys/unix" -import "core:path/filepath" _Path_Separator :: '/' _Path_List_Separator :: ':' @@ -127,23 +126,22 @@ _remove_all :: proc(path: string) -> Error { defer delete(buf) loop: for { - res := unix.sys_getdents64(int(dfd), &buf[0], n) - switch res { + getdents_res := unix.sys_getdents64(int(dfd), &buf[0], n) + switch getdents_res { case -EINVAL: delete(buf) n *= 2 buf = make([]u8, n) continue loop case -4096..<0: - return _get_platform_error(res) + return _get_platform_error(getdents_res) case 0: break loop } d: ^dirent64 - for i := 0; i < res; i += int(d.d_reclen) { - description: string + for i := 0; i < getdents_res; i += int(d.d_reclen) { d = (^dirent64)(rawptr(&buf[i])) d_name_cstr := cstring(&d.d_name[0]) @@ -159,7 +157,7 @@ _remove_all :: proc(path: string) -> Error { continue } - res: int + unlink_res: int switch d.d_type { case DT_DIR: @@ -169,13 +167,13 @@ _remove_all :: proc(path: string) -> Error { } defer unix.sys_close(handle_i) _remove_all_dir(Handle(handle_i)) or_return - res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR)) + unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr, int(unix.AT_REMOVEDIR)) case: - res = unix.sys_unlinkat(int(dfd), d_name_cstr) + unlink_res = unix.sys_unlinkat(int(dfd), d_name_cstr) } - if res < 0 { - return _get_platform_error(res) + if unlink_res < 0 { + return _get_platform_error(unlink_res) } } } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 9bfd900b6..a52a84027 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -2,7 +2,6 @@ package os2 import "core:time" -import "core:strings" import "core:sys/unix" import "core:path/filepath" From 8559790bd8e9e69187566ab3fa7855bbba2e7ec3 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 10 May 2022 20:02:39 +0200 Subject: [PATCH 030/254] Fix ; typo. --- core/intrinsics/intrinsics.odin | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 7e75aecc4..85859e8c3 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -87,20 +87,20 @@ atomic_load :: proc(dst: ^$T) -> T --- atomic_load_explicit :: proc(dst: ^$T, order: Atomic_Memory_Order) -> T --- // fetch then operator -atomic_add :: proc(dst; ^$T, val: T) -> T --- -atomic_add_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_sub :: proc(dst; ^$T, val: T) -> T --- -atomic_sub_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_and :: proc(dst; ^$T, val: T) -> T --- -atomic_and_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_nand :: proc(dst; ^$T, val: T) -> T --- -atomic_nand_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_or :: proc(dst; ^$T, val: T) -> T --- -atomic_or_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_xor :: proc(dst; ^$T, val: T) -> T --- -atomic_xor_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- -atomic_exchange :: proc(dst; ^$T, val: T) -> T --- -atomic_exchange_explicit :: proc(dst; ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_add :: proc(dst: ^$T, val: T) -> T --- +atomic_add_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_sub :: proc(dst: ^$T, val: T) -> T --- +atomic_sub_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_and :: proc(dst: ^$T, val: T) -> T --- +atomic_and_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_nand :: proc(dst: ^$T, val: T) -> T --- +atomic_nand_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_or :: proc(dst: ^$T, val: T) -> T --- +atomic_or_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_xor :: proc(dst: ^$T, val: T) -> T --- +atomic_xor_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- +atomic_exchange :: proc(dst: ^$T, val: T) -> T --- +atomic_exchange_explicit :: proc(dst: ^$T, val: T, order: Atomic_Memory_Order) -> T --- atomic_compare_exchange_strong :: proc(dst: ^$T, old, new: T) -> (T, bool) #optional_ok --- atomic_compare_exchange_strong_explicit :: proc(dst: ^$T, old, new: T, success, failure: Atomic_Memory_Order) -> (T, bool) #optional_ok --- From d48d3bfa87f8f944c7bb96a1efe298beaaa9c1cf Mon Sep 17 00:00:00 2001 From: Thimilius Date: Wed, 11 May 2022 13:12:07 +0200 Subject: [PATCH 031/254] Fix join_multiple typo --- core/thread/thread.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index d1b95a2fd..90230ae75 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -53,7 +53,7 @@ join :: proc(thread: ^Thread) { } -join_mulitple :: proc(threads: ..^Thread) { +join_multiple :: proc(threads: ..^Thread) { _join_multiple(..threads) } From dca2fbccffd22b47cdb89e56170f3dae9a6257ad Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 11 May 2022 12:15:10 +0100 Subject: [PATCH 032/254] Improve ternary if type inference --- src/check_expr.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 336a711d4..f578f8c73 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7241,7 +7241,11 @@ ExprKind check_ternary_if_expr(CheckerContext *c, Operand *o, Ast *node, Type *t node->viral_state_flags |= te->x->viral_state_flags; if (te->y != nullptr) { - check_expr_or_type(c, &y, te->y, type_hint); + Type *th = type_hint; + if (type_hint == nullptr && is_type_typed(x.type)) { + th = x.type; + } + check_expr_or_type(c, &y, te->y, th); node->viral_state_flags |= te->y->viral_state_flags; } else { error(node, "A ternary expression must have an else clause"); From b4df272eb5ca8b43a47ad0f70dff19dc98563642 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 11 May 2022 12:15:37 +0100 Subject: [PATCH 033/254] Improve -vet shadowing to allow `x := x if cond else y` etc --- src/checker.cpp | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index fdd75126f..d186163e4 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -544,6 +544,28 @@ GB_COMPARE_PROC(vetted_entity_variable_pos_cmp) { return token_pos_cmp(x->token.pos, y->token.pos); } +bool check_vet_shadowing_assignment(Checker *c, Entity *shadowed, Ast *expr) { + Ast *init = unparen_expr(expr); + if (init == nullptr) { + return false; + } + if (init->kind == Ast_Ident) { + // TODO(bill): Which logic is better? Same name or same entity + // bool ignore = init->Ident.token.string == name; + bool ignore = init->Ident.entity == shadowed; + if (ignore) { + return true; + } + } else if (init->kind == Ast_TernaryIfExpr) { + bool x = check_vet_shadowing_assignment(c, shadowed, init->TernaryIfExpr.x); + bool y = check_vet_shadowing_assignment(c, shadowed, init->TernaryIfExpr.y); + if (x || y) { + return true; + } + } + + return false; +} bool check_vet_shadowing(Checker *c, Entity *e, VettedEntity *ve) { @@ -594,17 +616,14 @@ bool check_vet_shadowing(Checker *c, Entity *e, VettedEntity *ve) { } // NOTE(bill): Ignore intentional redeclaration - // x := x; + // x := x // Suggested in issue #637 (2020-05-11) + // Also allow the following + // x := x if cond else y + // x := z if cond else x if ((e->flags & EntityFlag_Using) == 0 && e->kind == Entity_Variable) { - Ast *init = unparen_expr(e->Variable.init_expr); - if (init != nullptr && init->kind == Ast_Ident) { - // TODO(bill): Which logic is better? Same name or same entity - // bool ignore = init->Ident.token.string == name; - bool ignore = init->Ident.entity == shadowed; - if (ignore) { - return false; - } + if (check_vet_shadowing_assignment(c, shadowed, e->Variable.init_expr)) { + return false; } } From 56e3b7cb7d96a467ace98ebdca11d2baeef0a8c0 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 11 May 2022 13:43:29 +0200 Subject: [PATCH 034/254] Fix join on *nix. --- core/thread/thread_unix.odin | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 8452df112..3897f6100 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -7,6 +7,8 @@ import "core:intrinsics" import "core:sync" import "core:sys/unix" +CAS :: intrinsics.atomic_compare_exchange_strong + Thread_State :: enum u8 { Started, Joined, @@ -98,7 +100,7 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ } _start :: proc(t: ^Thread) { - sync.guard(&t.mutex) + // sync.guard(&t.mutex) t.flags += { .Started } sync.signal(&t.cond) } @@ -108,15 +110,22 @@ _is_done :: proc(t: ^Thread) -> bool { } _join :: proc(t: ^Thread) { - sync.guard(&t.mutex) + // sync.guard(&t.mutex) - if .Joined in t.flags || unix.pthread_equal(unix.pthread_self(), t.unix_thread) { + if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { return } - unix.pthread_join(t.unix_thread, nil) + // Preserve other flags besides `.Joined`, like `.Started`. + unjoined := intrinsics.atomic_load(&t.flags) - {.Joined} + joined := unjoined + {.Joined} - t.flags += { .Joined } + // Try to set `t.flags` from unjoined to joined. If it returns joined, + // it means the previous value had that flag set and we can return. + if res, ok := CAS(&t.flags, unjoined, joined); res == joined && !ok { + return + } + unix.pthread_join(t.unix_thread, nil) } _join_multiple :: proc(threads: ..^Thread) { From 8fb718245a76cc9daa45122e6e6990f558b14de7 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 11 May 2022 15:52:04 +0200 Subject: [PATCH 035/254] Implement pthread_cancel. --- core/sys/unix/pthread_darwin.odin | 13 +++++++++++++ core/sys/unix/pthread_freebsd.odin | 10 +++++++++- core/sys/unix/pthread_linux.odin | 9 +++++++++ core/sys/unix/pthread_openbsd.odin | 11 ++++++++++- core/thread/thread_unix.odin | 11 ++++++++++- examples/demo/demo.odin | 29 ++++++++++++++++++++++++++++- 6 files changed, 79 insertions(+), 4 deletions(-) diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin index 542a550cb..e138b8610 100644 --- a/core/sys/unix/pthread_darwin.odin +++ b/core/sys/unix/pthread_darwin.odin @@ -81,3 +81,16 @@ PTHREAD_MUTEX_NORMAL :: 0 PTHREAD_MUTEX_RECURSIVE :: 1 PTHREAD_MUTEX_ERRORCHECK :: 2 +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + +foreign import pthread "System.framework" + +@(default_calling_convention="c") +foreign pthread { + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +} \ No newline at end of file diff --git a/core/sys/unix/pthread_freebsd.odin b/core/sys/unix/pthread_freebsd.odin index dd5306417..e02345cad 100644 --- a/core/sys/unix/pthread_freebsd.odin +++ b/core/sys/unix/pthread_freebsd.odin @@ -92,6 +92,11 @@ sem_t :: struct { _padding: u32, } +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + foreign import "system:pthread" @(default_calling_convention="c") @@ -110,5 +115,8 @@ foreign pthread { // NOTE: unclear whether pthread_yield is well-supported on Linux systems, // see https://linux.die.net/man/3/pthread_yield pthread_yield :: proc() --- -} + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +} \ No newline at end of file diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin index 099e7c7e9..9c297ef22 100644 --- a/core/sys/unix/pthread_linux.odin +++ b/core/sys/unix/pthread_linux.odin @@ -94,6 +94,11 @@ when size_of(int) == 8 { SEM_T_SIZE :: 16 } +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + foreign import "system:pthread" @(default_calling_convention="c") @@ -112,4 +117,8 @@ foreign pthread { // NOTE: unclear whether pthread_yield is well-supported on Linux systems, // see https://linux.die.net/man/3/pthread_yield pthread_yield :: proc() -> c.int --- + + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- } diff --git a/core/sys/unix/pthread_openbsd.odin b/core/sys/unix/pthread_openbsd.odin index c855f95c0..7ae82e662 100644 --- a/core/sys/unix/pthread_openbsd.odin +++ b/core/sys/unix/pthread_openbsd.odin @@ -46,6 +46,11 @@ sched_param :: struct { sem_t :: distinct rawptr +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + foreign import libc "system:c" @(default_calling_convention="c") @@ -62,4 +67,8 @@ foreign libc { // NOTE: unclear whether pthread_yield is well-supported on Linux systems, // see https://linux.die.net/man/3/pthread_yield pthread_yield :: proc() --- -} + + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +} \ No newline at end of file diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 3897f6100..1a2b30197 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -31,6 +31,9 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { t := (^Thread)(t) + // We need to give the thread a moment to start up before we enable cancellation. + can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) == 0 + context = runtime.default_context() sync.lock(&t.mutex) @@ -44,6 +47,12 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ init_context := t.init_context context = init_context.? or_else runtime.default_context() + // Enable thread's cancelability. + if can_set_thread_cancel_state { + unix.pthread_setcanceltype (unix.PTHREAD_CANCEL_ASYNCHRONOUS, nil) + unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) + } + t.procedure(t) intrinsics.atomic_store(&t.flags, t.flags + { .Done }) @@ -141,7 +150,7 @@ _destroy :: proc(t: ^Thread) { } _terminate :: proc(t: ^Thread, exit_code: int) { - // TODO(bill) + unix.pthread_cancel(t.unix_thread) } _yield :: proc() { diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index a36acdf18..c50a5bdf8 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1110,9 +1110,16 @@ prefix_table := [?]string{ "Black", } +print_mutex := b64(false) + threading_example :: proc() { fmt.println("\n# threading_example") + did_acquire :: proc(m: ^b64) -> (acquired: bool) { + res, ok := intrinsics.atomic_compare_exchange_strong(m, false, true) + return ok && res == false + } + { // Basic Threads fmt.println("\n## Basic Threads") worker_proc :: proc(t: ^thread.Thread) { @@ -1154,14 +1161,21 @@ threading_example :: proc() { task_proc :: proc(t: thread.Task) { index := t.user_index % len(prefix_table) for iteration in 1..=5 { + for !did_acquire(&print_mutex) { thread.yield() } // Allow one thread to print at a time. + fmt.printf("Worker Task %d is on iteration %d\n", t.user_index, iteration) fmt.printf("`%s`: iteration %d\n", prefix_table[index], iteration) + + print_mutex = false + time.sleep(1 * time.Millisecond) } } + N :: 3 + pool: thread.Pool - thread.pool_init(pool=&pool, thread_count=3, allocator=context.allocator) + thread.pool_init(pool=&pool, thread_count=N, allocator=context.allocator) defer thread.pool_destroy(&pool) @@ -1171,6 +1185,19 @@ threading_example :: proc() { } thread.pool_start(&pool) + + { + // Wait a moment before we cancel a thread + time.sleep(5 * time.Millisecond) + + // Allow one thread to print at a time. + for !did_acquire(&print_mutex) { thread.yield() } + + thread.terminate(pool.threads[N - 1], 0) + fmt.println("Canceled last thread") + print_mutex = false + } + thread.pool_finish(&pool) } } From f4ad4c7aa6f9137dd10c9d201e80f072181d2f93 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 11 May 2022 16:17:35 +0200 Subject: [PATCH 036/254] Disable thread.terminate on Darwin for now. --- core/thread/thread_unix.odin | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 1a2b30197..8c7058f17 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -31,8 +31,10 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { t := (^Thread)(t) - // We need to give the thread a moment to start up before we enable cancellation. - can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) == 0 + when ODIN_OS != .Darwin { + // We need to give the thread a moment to start up before we enable cancellation. + can_set_thread_cancel_state := unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) == 0 + } context = runtime.default_context() @@ -47,10 +49,12 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ init_context := t.init_context context = init_context.? or_else runtime.default_context() - // Enable thread's cancelability. - if can_set_thread_cancel_state { - unix.pthread_setcanceltype (unix.PTHREAD_CANCEL_ASYNCHRONOUS, nil) - unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) + when ODIN_OS != .Darwin { + // Enable thread's cancelability. + if can_set_thread_cancel_state { + unix.pthread_setcanceltype (unix.PTHREAD_CANCEL_ASYNCHRONOUS, nil) + unix.pthread_setcancelstate(unix.PTHREAD_CANCEL_DISABLE, nil) + } } t.procedure(t) @@ -150,7 +154,10 @@ _destroy :: proc(t: ^Thread) { } _terminate :: proc(t: ^Thread, exit_code: int) { - unix.pthread_cancel(t.unix_thread) + // `pthread_cancel` is unreliable on Darwin for unknown reasons. + when ODIN_OS != .Darwin { + unix.pthread_cancel(t.unix_thread) + } } _yield :: proc() { From 8b4b81fdeb5e5367602855d5bedd83083118dfc1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 11:33:01 +0100 Subject: [PATCH 037/254] Fill in most of os2/file_windows.odin --- core/os/os2/errors.odin | 3 + core/os/os2/file.odin | 34 ++- core/os/os2/file_windows.odin | 473 +++++++++++++++++++++++++++++++-- core/sys/windows/kernel32.odin | 5 + core/sys/windows/types.odin | 1 + 5 files changed, 479 insertions(+), 37 deletions(-) diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 6ddae74b1..b54b10cb2 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -13,6 +13,9 @@ General_Error :: enum u32 { Timeout, Invalid_File, + Invalid_Path, + + Unsupported, } Platform_Error :: struct { diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 0ed08c850..268631dd6 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -2,6 +2,7 @@ package os2 import "core:io" import "core:time" +import "core:runtime" File :: struct { impl: _File, @@ -31,6 +32,7 @@ File_Flag :: enum { Sync, Trunc, Sparse, + Close_On_Exec, } O_RDONLY :: File_Flags{.Read} @@ -137,23 +139,36 @@ symlink :: proc(old_name, new_name: string) -> Error { return _symlink(old_name, new_name) } -read_link :: proc(name: string) -> (string, Error) { - return _read_link(name) +read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) { + return _read_link(name,allocator) } -chdir :: proc(f: ^File) -> Error { - return _chdir(f) +chdir :: proc(name: string) -> Error { + return _chdir(name) } -chmod :: proc(f: ^File, mode: File_Mode) -> Error { - return _chmod(f, mode) +chmod :: proc(name: string, mode: File_Mode) -> Error { + return _chmod(name, mode) } -chown :: proc(f: ^File, uid, gid: int) -> Error { - return _chown(f, uid, gid) +chown :: proc(name: string, uid, gid: int) -> Error { + return _chown(name, uid, gid) } +fchdir :: proc(f: ^File) -> Error { + return _fchdir(f) +} + +fchmod :: proc(f: ^File, mode: File_Mode) -> Error { + return _fchmod(f, mode) +} + +fchown :: proc(f: ^File, uid, gid: int) -> Error { + return _fchown(f, uid, gid) +} + + lchown :: proc(name: string, uid, gid: int) -> Error { return _lchown(name, uid, gid) @@ -163,6 +178,9 @@ lchown :: proc(name: string, uid, gid: int) -> Error { chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return _chtimes(name, atime, mtime) } +fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + return _fchtimes(f, atime, mtime) +} exists :: proc(path: string) -> bool { return _exists(path) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 880305830..07117a15a 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -2,13 +2,19 @@ package os2 import "core:io" -import "core:time" +import "core:mem" import "core:runtime" import "core:strings" +import "core:time" +import "core:unicode/utf16" import win32 "core:sys/windows" INVALID_HANDLE :: ~uintptr(0) +S_IWRITE :: 0o200 +_ERROR_BAD_NETPATH :: 53 +MAX_RW :: 1<<30 + _file_allocator :: proc() -> runtime.Allocator { return heap_allocator() } @@ -26,6 +32,10 @@ _File :: struct { kind: _File_Kind, } +_handle :: proc(f: ^File) -> win32.HANDLE { + return win32.HANDLE(_fd(f)) +} + _get_platform_error :: proc() -> Error { err := i32(win32.GetLastError()) if err != 0 { @@ -34,8 +44,79 @@ _get_platform_error :: proc() -> Error { return nil } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (^File, Error) { - return nil, nil +_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) { + if len(name) == 0 { + err = .Not_Exist + return + } + + path := _fix_long_path(name) + access: u32 + switch flags & {.Read, .Write} { + case {.Read}: access = win32.FILE_GENERIC_READ + case {.Write}: access = win32.FILE_GENERIC_WRITE + case {.Read, .Write}: access = win32.FILE_GENERIC_READ | win32.FILE_GENERIC_WRITE + } + + if .Create in flags { + access |= win32.FILE_GENERIC_WRITE + } + if .Append in flags { + access &~= win32.FILE_GENERIC_WRITE + access |= win32.FILE_APPEND_DATA + } + share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) + sa: ^win32.SECURITY_ATTRIBUTES + if .Close_On_Exec not_in flags { + sa = &win32.SECURITY_ATTRIBUTES{} + sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) + sa.bInheritHandle = true + } + + create_mode: u32 = win32.OPEN_EXISTING + switch { + case flags & {.Create, .Excl} == {.Create, .Excl}: + create_mode = win32.CREATE_NEW + case flags & {.Create, .Trunc} == {.Create, .Trunc}: + create_mode = win32.CREATE_ALWAYS + case flags & {.Create} == {.Create}: + create_mode = win32.OPEN_ALWAYS + case flags & {.Trunc} == {.Trunc}: + create_mode = win32.TRUNCATE_EXISTING + } + + attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL + if perm & S_IWRITE == 0 { + attrs = win32.FILE_ATTRIBUTE_READONLY + if create_mode == win32.CREATE_ALWAYS { + // NOTE(bill): Open has just asked to create a file in read-only mode. + // If the file already exists, to make it akin to a *nix open call, + // the call preserves the existing permissions. + h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) + if h == win32.INVALID_HANDLE { + switch e := win32.GetLastError(); e { + case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: + // file does not exist, create the file + case 0: + return uintptr(h), nil + case: + return 0, Platform_Error{i32(e)} + } + } + } + } + h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil) + if h == win32.INVALID_HANDLE { + return 0, _get_platform_error() + } + return uintptr(h), nil +} + + +_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { + flags := flags if flags != nil else {.Read} + handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return + return _new_file(handle, name), nil } _new_file :: proc(handle: uintptr, name: string) -> ^File { @@ -48,11 +129,12 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { f.impl.name = strings.clone(name, context.allocator) f.impl.wname = win32.utf8_to_wstring(name, context.allocator) + handle := _handle(f) kind := _File_Kind.File - if m: u32; win32.GetConsoleMode(win32.HANDLE(fd), &m) { + if m: u32; win32.GetConsoleMode(handle, &m) { kind = .Console } - if win32.GetFileType(win32.HANDLE(fd)) == win32.FILE_TYPE_PIPE { + if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { kind = .Pipe } f.impl.kind = kind @@ -95,9 +177,14 @@ _name :: proc(f: ^File) -> string { } _seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { - if f == nil { - return + handle := _handle(f) + if handle == win32.INVALID_HANDLE { + return 0, .Invalid_File } + if f.impl.kind == .Pipe { + return 0, .Invalid_File + } + w: u32 switch whence { case .Start: w = win32.FILE_BEGIN @@ -106,12 +193,8 @@ _seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error } hi := i32(offset>>32) lo := i32(offset) - ft := win32.GetFileType(win32.HANDLE(fd)) - if ft == win32.FILE_TYPE_PIPE { - return 0, .Invalid_File - } - dw_ptr := win32.SetFilePointer(win32.HANDLE(fd), lo, &hi, w) + dw_ptr := win32.SetFilePointer(handle, lo, &hi, w) if dw_ptr == win32.INVALID_SET_FILE_POINTER { return 0, _get_platform_error() } @@ -119,35 +202,192 @@ _seek :: proc(f: ^File, offset: i64, whence: Seek_From) -> (ret: i64, err: Error } _read :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - return + read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { + if len(b) == 0 { + return 0, nil + } + + BUF_SIZE :: 386 + buf16: [BUF_SIZE]u16 + buf8: [4*BUF_SIZE]u8 + + for n < len(b) && err == nil { + min_read := max(len(b)/4, 1 if len(b) > 0 else 0) + max_read := u32(min(BUF_SIZE, min_read)) + if max_read == 0 { + break + } + + single_read_length: u32 + ok := win32.ReadConsoleW(handle, &buf16[0], max_read, &single_read_length, nil) + if !ok { + err = _get_platform_error() + } + + buf8_len := utf16.decode_to_utf8(buf8[:], buf16[:single_read_length]) + src := buf8[:buf8_len] + + ctrl_z := false + for i := 0; i < len(src) && n+i < len(b); i += 1 { + x := src[i] + if x == 0x1a { // ctrl-z + ctrl_z = true + break + } + b[n] = x + n += 1 + } + if ctrl_z || single_read_length < max_read { + break + } + + // NOTE(bill): if the last two values were a newline, then it is expected that + // this is the end of the input + if n >= 2 && single_read_length == max_read && string(b[n-2:n]) == "\r\n" { + break + } + } + + return + } + + handle := _handle(f) + + single_read_length: win32.DWORD + total_read: int + length := len(p) + + to_read := min(win32.DWORD(length), MAX_RW) + + e: win32.BOOL + if f.impl.kind == .Console { + n, err := read_console(handle, p[total_read:][:to_read]) + total_read += n + if err != nil { + return int(total_read), err + } + } else { + e = win32.ReadFile(handle, &p[total_read], to_read, &single_read_length, nil) + } + if single_read_length <= 0 || !e { + return int(total_read), _get_platform_error() + } + total_read += int(single_read_length) + + return int(total_read), nil } _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) { + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + + } + curr_offset := seek(f, offset, .Current) or_return + defer seek(f, curr_offset, .Start) + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + // TODO(bill): Determine the correct behaviour for consoles + + h := _handle(f) + done: win32.DWORD + if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + err = _get_platform_error() + done = 0 + } + n = int(done) + return + } + p, offset := p, offset + for len(p) > 0 { + m := pread(f, p, offset) or_return + n += m + p = p[m:] + offset += i64(m) + } return } _read_from :: proc(f: ^File, r: io.Reader) -> (n: i64, err: Error) { + // TODO(bill) return } _write :: proc(f: ^File, p: []byte) -> (n: int, err: Error) { - return + if len(p) == 0 { + return + } + + single_write_length: win32.DWORD + total_write: i64 + length := i64(len(p)) + + handle := _handle(f) + + for total_write < length { + remaining := length - total_write + to_write := win32.DWORD(min(i32(remaining), MAX_RW)) + + e := win32.WriteFile(handle, &p[total_write], to_write, &single_write_length, nil) + if single_write_length <= 0 || !e { + n = int(total_write) + err = _get_platform_error() + return + } + total_write += i64(single_write_length) + } + return int(total_write), nil } _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: int, err: Error) { + pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: int, err: Error) { + buf := data + if len(buf) > MAX_RW { + buf = buf[:MAX_RW] + + } + curr_offset := seek(f, offset, .Current) or_return + defer seek(f, curr_offset, .Start) + + o := win32.OVERLAPPED{ + OffsetHigh = u32(offset>>32), + Offset = u32(offset), + } + + h := _handle(f) + done: win32.DWORD + if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { + err = _get_platform_error() + done = 0 + } + n = int(done) + return + } + + p, offset := p, offset + for len(p) > 0 { + m := pwrite(f, p, offset) or_return + n += m + p = p[m:] + offset += i64(m) + } return } _write_to :: proc(f: ^File, w: io.Writer) -> (n: i64, err: Error) { + // TODO(bill) return } _file_size :: proc(f: ^File) -> (n: i64, err: Error) { - if f == nil { - return - } length: win32.LARGE_INTEGER - if !win32.GetFileSizeEx(win32.HANDLE(fd), &length) { + handle := _handle(f) + if !win32.GetFileSizeEx(handle, &length) { err = _get_platform_error() } n = i64(length) @@ -156,10 +396,14 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) { _sync :: proc(f: ^File) -> Error { - return nil + return _flush(f) } _flush :: proc(f: ^File) -> Error { + handle := _handle(f) + if !win32.FlushFileBuffers(handle) { + return _get_platform_error() + } return nil } @@ -170,7 +414,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error { curr_off := seek(f, 0, .Current) or_return defer seek(f, curr_off, .Start) seek(f, size, .Start) or_return - if !win32.SetEndOfFile(win32.HANDLE(fd)) { + handle := _handle(f) + if !win32.SetEndOfFile(handle) { return _get_platform_error() } return nil @@ -234,43 +479,213 @@ _link :: proc(old_name, new_name: string) -> Error { } _symlink :: proc(old_name, new_name: string) -> Error { - return nil + return .Unsupported } -_read_link :: proc(name: string) -> (string, Error) { +_open_sym_link :: proc(p: [^]u16) -> (handle: win32.HANDLE, err: Error) { + attrs := u32(win32.FILE_FLAG_BACKUP_SEMANTICS) + attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT + handle = win32.CreateFileW(p, 0, 0, nil, win32.OPEN_EXISTING, attrs, nil) + if handle == win32.INVALID_HANDLE { + return nil, _get_platform_error() + } + return + +} + +_normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: string, err: Error) { + has_prefix :: proc(p: []u16, str: string) -> bool { + if len(p) < len(str) { + return false + } + // assume ascii + for i in 0.. bool { + return has_prefix(p, `\??\`) + } + + if !has_unc_prefix(p) { + return win32.utf16_to_utf8(p, allocator), nil + } + + ws := p[4:] + switch { + case len(ws) >= 2 && ws[1] == ':': + return win32.utf16_to_utf8(ws, allocator), nil + case has_prefix(ws, `UNC\`): + ws[3] = '\\' // override data in buffer + return win32.utf16_to_utf8(ws[3:], allocator), nil + } + + + handle := _open_sym_link(raw_data(p)) or_return + defer win32.CloseHandle(handle) + + n := win32.GetFinalPathNameByHandleW(handle, nil, 0, win32.VOLUME_NAME_DOS) + if n == 0 { + return "", _get_platform_error() + } + buf := make([]u16, n+1, context.temp_allocator) + n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) + if n == 0 { + return "", _get_platform_error() + } + + ws = buf[:n] + if has_unc_prefix(ws) { + ws = ws[4:] + if len(ws) > 3 && has_prefix(ws, `UNC`) { + ws[2] = '\\' + return win32.utf16_to_utf8(ws[2:], allocator), nil + } + return win32.utf16_to_utf8(ws, allocator), nil + } + return "", .Invalid_Path +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + MAXIMUM_REPARSE_DATA_BUFFER_SIZE :: 16 * 1024 + + @thread_local + rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte + + p := _fix_long_path(name) + handle := _open_sym_link(p) or_return + defer win32.CloseHandle(handle) + + bytes_returned: u32 + if !win32.DeviceIoControl(handle, win32.FSCTL_GET_REPARSE_POINT, nil, 0, &rdb_buf[0], len(rdb_buf)-1, &bytes_returned, nil) { + err = _get_platform_error() + return + } + mem.zero_slice(rdb_buf[:min(bytes_returned+1, len(rdb_buf))]) + + + rdb := (^win32.REPARSE_DATA_BUFFER)(&rdb_buf[0]) + switch rdb.ReparseTag { + case win32.IO_REPARSE_TAG_SYMLINK: + rb := (^win32.SYMBOLIC_LINK_REPARSE_BUFFER)(&rdb.rest) + pb := win32.wstring(&rb.PathBuffer) + pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 + p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] + if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { + return win32.utf16_to_utf8(p, allocator), nil + } + return _normalize_link_path(p, allocator) + + case win32.IO_REPARSE_TAG_MOUNT_POINT: + rb := (^win32.MOUNT_POINT_REPARSE_BUFFER)(&rdb.rest) + pb := win32.wstring(&rb.PathBuffer) + pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 + p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] + return _normalize_link_path(p, allocator) + } + // Path wasn't a symlink/junction but another reparse point kind return "", nil } -_chdir :: proc(f: ^File) -> Error { +_fchdir :: proc(f: ^File) -> Error { if f == nil { return nil } - if win32.SetCurrentDirectoryW(f.impl.wname) { + if !win32.SetCurrentDirectoryW(f.impl.wname) { + return _get_platform_error() + } + return nil +} + +_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { + if f == nil { return nil } - return _get_platform_error() -} + d: win32.BY_HANDLE_FILE_INFORMATION + if !win32.GetFileInformationByHandle(_handle(f), &d) { + return _get_platform_error() + } + attrs := d.dwFileAttributes + if mode & S_IWRITE != 0 { + attrs &~= win32.FILE_ATTRIBUTE_READONLY + } else { + attrs |= win32.FILE_ATTRIBUTE_READONLY + } -_chmod :: proc(f: ^File, mode: File_Mode) -> Error { + info: win32.FILE_BASIC_INFO + info.FileAttributes = attrs + if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { + return _get_platform_error() + } return nil } -_chown :: proc(f: ^File, uid, gid: int) -> Error { +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + return .Unsupported +} + +_chdir :: proc(name: string) -> Error { + p := _fix_long_path(name) + if !win32.SetCurrentDirectoryW(p) { + return _get_platform_error() + } return nil } +_chmod :: proc(name: string, mode: File_Mode) -> Error { + f := open(name, {.Write}) or_return + defer close(f) + return _fchmod(f, mode) +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} _lchown :: proc(name: string, uid, gid: int) -> Error { - return nil + return .Unsupported } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + f := open(name, {.Write}) or_return + defer close(f) + return _fchtimes(f, atime, mtime) +} +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + if f == nil { + return nil + } + d: win32.BY_HANDLE_FILE_INFORMATION + if !win32.GetFileInformationByHandle(_handle(f), &d) { + return _get_platform_error() + } + + to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER { + // a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC) + return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000) + } + + atime, mtime := atime, mtime + if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) { + atime = mtime + } + + info: win32.FILE_BASIC_INFO + info.LastAccessTime = to_windows_time(atime) + info.LastWriteTime = to_windows_time(mtime) + if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { + return _get_platform_error() + } return nil } + _exists :: proc(path: string) -> bool { wpath := _fix_long_path(path) attribs := win32.GetFileAttributesW(wpath) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index d95ff91c9..1f8927a16 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -29,6 +29,11 @@ foreign kernel32 { SetHandleInformation :: proc(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) -> BOOL --- + SetFileInformationByHandle :: proc(hFile: HANDLE, + FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, + lpFileInformation: LPVOID, + dwBufferSize: DWORD) -> BOOL --- + AddVectoredExceptionHandler :: proc(FirstHandler: ULONG, VectoredHandler: PVECTORED_EXCEPTION_HANDLER) -> LPVOID --- AddVectoredContinueHandler :: proc(FirstHandler: ULONG, VectoredHandler: PVECTORED_EXCEPTION_HANDLER) -> LPVOID --- diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index d06cd287c..6770e7a95 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -191,6 +191,7 @@ FILE_WRITE_DATA: DWORD : 0x00000002 FILE_APPEND_DATA: DWORD : 0x00000004 FILE_WRITE_EA: DWORD : 0x00000010 FILE_WRITE_ATTRIBUTES: DWORD : 0x00000100 +FILE_READ_ATTRIBUTES: DWORD : 0x000000080 READ_CONTROL: DWORD : 0x00020000 SYNCHRONIZE: DWORD : 0x00100000 GENERIC_READ: DWORD : 0x80000000 From ccb38c3dc684ece829c7ca1066867cc5212533c3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 12:54:14 +0100 Subject: [PATCH 038/254] Add _safe versions --- core/bytes/bytes.odin | 43 +++++++++++++++++++++++++++++++++++++++ core/strings/strings.odin | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 09a3ed259..66fd20829 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -10,6 +10,12 @@ clone :: proc(s: []byte, allocator := context.allocator, loc := #caller_location return c[:len(s)] } +clone_safe :: proc(s: []byte, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: mem.Allocator_Error) { + c := make([]byte, len(s), allocator, loc) or_return + copy(c, s) + return c[:len(s)], nil +} + ptr_from_slice :: proc(str: []byte) -> ^byte { d := transmute(mem.Raw_String)str return d.data @@ -134,6 +140,25 @@ join :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> []byte return b } +join_safe :: proc(a: [][]byte, sep: []byte, allocator := context.allocator) -> (data: []byte, err: mem.Allocator_Error) { + if len(a) == 0 { + return nil, nil + } + + n := len(sep) * (len(a) - 1) + for s in a { + n += len(s) + } + + b := make([]byte, n, allocator) or_return + i := copy(b, a[0]) + for s in a[1:] { + i += copy(b[i:], sep) + i += copy(b[i:], s) + } + return b, nil +} + concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte { if len(a) == 0 { return nil @@ -151,6 +176,24 @@ concatenate :: proc(a: [][]byte, allocator := context.allocator) -> []byte { return b } +concatenate_safe :: proc(a: [][]byte, allocator := context.allocator) -> (data: []byte, err: mem.Allocator_Error) { + if len(a) == 0 { + return nil, nil + } + + n := 0 + for s in a { + n += len(s) + } + b := make([]byte, n, allocator) or_return + i := 0 + for s in a { + i += copy(b[i:], s) + } + return b, nil +} + + @private _split :: proc(s, sep: []byte, sep_save, n: int, allocator := context.allocator) -> [][]byte { s, n := s, n diff --git a/core/strings/strings.odin b/core/strings/strings.odin index a3d9fa93e..2429b451d 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -14,6 +14,13 @@ clone :: proc(s: string, allocator := context.allocator, loc := #caller_location return string(c[:len(s)]) } +// returns a clone of the string `s` allocated using the `allocator` +clone_safe :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (str: string, err: mem.Allocator_Error) { + c := make([]byte, len(s), allocator, loc) or_return + copy(c, s) + return string(c[:len(s)]), nil +} + // returns a clone of the string `s` allocated using the `allocator` as a cstring // a nul byte is appended to the clone, to make the cstring safe clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> cstring { @@ -260,6 +267,25 @@ join :: proc(a: []string, sep: string, allocator := context.allocator) -> string return string(b) } +join_safe :: proc(a: []string, sep: string, allocator := context.allocator) -> (str: string, err: mem.Allocator_Error) { + if len(a) == 0 { + return "", nil + } + + n := len(sep) * (len(a) - 1) + for s in a { + n += len(s) + } + + b := make([]byte, n, allocator) or_return + i := copy(b, a[0]) + for s in a[1:] { + i += copy(b[i:], sep) + i += copy(b[i:], s) + } + return string(b), nil +} + /* returns a combined string from the slice of strings `a` without a seperator allocates the string using the `allocator` @@ -285,6 +311,23 @@ concatenate :: proc(a: []string, allocator := context.allocator) -> string { return string(b) } +concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { + if len(a) == 0 { + return "", nil + } + + n := 0 + for s in a { + n += len(s) + } + b := make([]byte, n, allocator) or_return + i := 0 + for s in a { + i += copy(b[i:], s) + } + return string(b), nil +} + /* `rune_offset` and `rune_length` are in runes, not bytes. If `rune_length` <= 0, then it'll return the remainder of the string starting at `rune_offset`. From bb4f1084879975e2f6c76cda325d4d60154fae46 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 12:54:27 +0100 Subject: [PATCH 039/254] Update error handling for os2 on windows --- core/os/os2/env_windows.odin | 10 ++- core/os/os2/errors.odin | 79 ++++++++++------- core/os/os2/errors_windows.odin | 46 ++++++++++ core/os/os2/file_util.odin | 10 ++- core/os/os2/file_windows.odin | 80 ++++++++++------- core/os/os2/path.odin | 4 +- core/os/os2/path_windows.odin | 57 ++++++++++++- core/os/os2/pipe_windows.odin | 2 +- core/os/os2/stat.odin | 11 +-- core/os/os2/stat_windows.odin | 132 ++++++++++++----------------- core/os/os2/temp_file.odin | 7 +- core/os/os2/temp_file_windows.odin | 30 +++---- core/os/os2/user.odin | 31 ++++--- core/os/stat_unix.odin | 1 - core/sys/windows/types.odin | 1 + 15 files changed, 310 insertions(+), 191 deletions(-) diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 2ce755203..52c6c0f55 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -2,8 +2,9 @@ package os2 import win32 "core:sys/windows" +import "core:runtime" -_lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if key == "" { return } @@ -17,7 +18,7 @@ _lookup_env :: proc(key: string, allocator := context.allocator) -> (value: stri } return "", true } - b := make([]u16, n+1, context.temp_allocator) + b := make([]u16, n+1, _temp_allocator()) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) if n == 0 { @@ -25,6 +26,7 @@ _lookup_env :: proc(key: string, allocator := context.allocator) -> (value: stri if err == win32.ERROR_ENVVAR_NOT_FOUND { return "", false } + return "", false } value = win32.utf16_to_utf8(b[:n], allocator) @@ -45,7 +47,7 @@ _unset_env :: proc(key: string) -> bool { } _clear_env :: proc() { - envs := environ(context.temp_allocator) + envs := environ(_temp_allocator()) for env in envs { for j in 1.. bool { } _mkdir :: proc(name: string, perm: File_Mode) -> Error { + if !win32.CreateDirectoryW(_fix_long_path(name), nil) { + return _get_platform_error() + } return nil } _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { - // TODO(bill): _mkdir_all for windows + fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { + if len(p) == len(`\\?\c:`) { + if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { + s = strings.concatenate_safe({p, `\`}, _file_allocator()) or_return + allocated = true + return + } + } + return p, false, nil + } + + dir, err := stat(path, _temp_allocator()) + if err == nil { + if dir.is_dir { + return nil + } + return .Exist + } + + i := len(path) + for i > 0 && is_path_separator(path[i-1]) { + i -= 1 + } + + j := i + for j > 0 && !is_path_separator(path[j-1]) { + j -= 1 + } + + if j > 1 { + new_path, allocated := fix_root_directory(path[:j-1]) or_return + defer if allocated { + delete(new_path, _file_allocator()) + } + mkdir_all(new_path, perm) or_return + } + + err = mkdir(path, perm) + if err != nil { + dir1, err1 := lstat(path, _temp_allocator()) + if err1 == nil && dir1.is_dir { + return nil + } + return err + } return nil } @@ -24,11 +73,13 @@ _remove_all :: proc(path: string) -> Error { return nil } -_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { +_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + // TODO(bill) return "", nil } _setwd :: proc(dir: string) -> (err: Error) { + // TODO(bill) return nil } @@ -75,7 +126,7 @@ _fix_long_path_internal :: proc(path: string) -> string { } PREFIX :: `\\?` - path_buf := make([]byte, len(PREFIX)+len(path)+1, context.temp_allocator) + path_buf := make([]byte, len(PREFIX)+len(path)+1, _temp_allocator()) copy(path_buf, PREFIX) n := len(path) r, w := 0, len(PREFIX) diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index ddb54f80c..bab8b44f5 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -6,7 +6,7 @@ import win32 "core:sys/windows" _pipe :: proc() -> (r, w: ^File, err: Error) { p: [2]win32.HANDLE if !win32.CreatePipe(&p[0], &p[1], nil, 0) { - return nil, nil, Platform_Error{i32(win32.GetLastError())} + return nil, nil, _get_platform_error() } return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil } diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 63f5a17e8..24a01fb0a 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -1,6 +1,7 @@ package os2 import "core:time" +import "core:runtime" File_Info :: struct { fullpath: string, @@ -13,26 +14,26 @@ File_Info :: struct { access_time: time.Time, } -file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) { +file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { for i := len(infos)-1; i >= 0; i -= 1 { file_info_delete(infos[i], allocator) } delete(infos, allocator) } -file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { +file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) { delete(fi.fullpath, allocator) } -fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) { +fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { return _fstat(f, allocator) } -stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { +stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _stat(name, allocator) } -lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { +lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _lstat(name, allocator) } diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index a79e5aae2..603343a18 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -1,22 +1,22 @@ //+private package os2 +import "core:runtime" import "core:time" import "core:strings" import win32 "core:sys/windows" -_fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) { +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { if f == nil || f.impl.fd == nil { - return {}, .Invalid_Argument + return {}, nil } - context.allocator = allocator - path, err := _cleanpath_from_handle(f) + path, err := _cleanpath_from_handle(f, allocator) if err != nil { return {}, err } - h := win32.HANDLE(f.impl.fd) + h := _handle(f) switch win32.GetFileType(h) { case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: fi: File_Info @@ -26,13 +26,13 @@ _fstat :: proc(f: ^File, allocator := context.allocator) -> (File_Info, Error) { return fi, nil } - return _file_info_from_get_file_information_by_handle(path, h) + return _file_info_from_get_file_information_by_handle(path, h, allocator) } -_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS) +_stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) } -_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Error) { - return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT) +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) } _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath @@ -40,50 +40,38 @@ _same_file :: proc(fi1, fi2: File_Info) -> bool { -_stat_errno :: proc(errno: win32.DWORD) -> Error { - return Platform_Error{i32(errno)} -} - -full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Error) { - context.allocator = allocator - +full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { name := name if name == "" { name = "." } - p := win32.utf8_to_utf16(name, context.temp_allocator) - buf := make([dynamic]u16, 100) - for { - n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) - if n == 0 { - delete(buf) - return "", _stat_errno(win32.GetLastError()) - } - if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n]), nil - } - resize(&buf, len(buf)*2) - } + p := win32.utf8_to_utf16(name, _temp_allocator()) - return + n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) + if n == 0 { + return "", _get_platform_error() + } + buf := make([]u16, n+1, _temp_allocator()) + n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) + if n == 0 { + return "", _get_platform_error() + } + return win32.utf16_to_utf8(buf[:n], allocator), nil } -internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Error) { +internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { return {}, .Not_Exist } - context.allocator = allocator - - wname := _fix_long_path(name) fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { // Not a symlink - return _file_info_from_win32_file_attribute_data(&fa, name) + return _file_info_from_win32_file_attribute_data(&fa, name, allocator) } err := 0 if ok else win32.GetLastError() @@ -97,7 +85,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co } win32.FindClose(sh) - return _file_info_from_win32_find_data(&fd, name) + return _file_info_from_win32_find_data(&fd, name, allocator) } h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil) @@ -106,7 +94,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator := co return } defer win32.CloseHandle(h) - return _file_info_from_get_file_information_by_handle(name, h) + return _file_info_from_get_file_information_by_handle(name, h, allocator) } @@ -131,56 +119,40 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { } -_cleanpath_from_handle :: proc(f: ^File) -> (string, Error) { +_cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { if f == nil || f.impl.fd == nil { - return "", .Invalid_Argument + return "", nil } - h := win32.HANDLE(f.impl.fd) + h := _handle(f) - MAX_PATH := win32.DWORD(260) + 1 - buf: []u16 - for { - buf = make([]u16, MAX_PATH, context.temp_allocator) - err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0) - switch err { - case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: - return "", _stat_errno(err) - case win32.ERROR_NOT_ENOUGH_MEMORY: - MAX_PATH = MAX_PATH*2 + 1 - continue - } - break + n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) + if n == 0 { + return "", _get_platform_error() } - return _cleanpath_from_buf(buf), nil + buf := make([]u16, max(n, 260)+1, _temp_allocator()) + n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) + return _cleanpath_from_buf(buf[:n], allocator), nil } _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { if f == nil || f.impl.fd == nil { - return nil, .Invalid_Argument + return nil, nil } - h := win32.HANDLE(f.impl.fd) + h := _handle(f) - MAX_PATH := win32.DWORD(260) + 1 - buf: []u16 - for { - buf = make([]u16, MAX_PATH, context.temp_allocator) - err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0) - switch err { - case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: - return nil, _stat_errno(err) - case win32.ERROR_NOT_ENOUGH_MEMORY: - MAX_PATH = MAX_PATH*2 + 1 - continue - } - break + n := win32.GetFinalPathNameByHandleW(h, nil, 0, 0) + if n == 0 { + return nil, _get_platform_error() } - return _cleanpath_strip_prefix(buf), nil + buf := make([]u16, max(n, 260)+1, _temp_allocator()) + n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) + return _cleanpath_strip_prefix(buf[:n]), nil } -_cleanpath_from_buf :: proc(buf: []u16) -> string { +_cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> string { buf := buf buf = _cleanpath_strip_prefix(buf) - return win32.utf16_to_utf8(buf, context.allocator) + return win32.utf16_to_utf8(buf, allocator) } @@ -252,7 +224,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA } -_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Error) { +_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -262,14 +234,14 @@ _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - fi.fullpath, e = full_path_from_name(name) + fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) return } -_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Error) { +_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) @@ -279,17 +251,17 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - fi.fullpath, e = full_path_from_name(name) + fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) return } -_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Error) { +_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { - return {}, _stat_errno(win32.GetLastError()) + return {}, _get_platform_error() } @@ -297,7 +269,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { err := win32.GetLastError() if err != win32.ERROR_INVALID_PARAMETER { - return {}, _stat_errno(err) + return {}, Platform_Error(err) } // Indicate this is a symlink on FAT file systems ti.ReparseTag = 0 diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 90131699d..faf176de1 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -1,14 +1,15 @@ package os2 +import "core:runtime" create_temp :: proc(dir, pattern: string) -> (^File, Error) { return _create_temp(dir, pattern) } -mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { - return _mkdir_temp(dir, pattern) +mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { + return _mkdir_temp(dir, pattern, allocator) } -temp_dir :: proc(allocator := context.allocator) -> string { +temp_dir :: proc(allocator: runtime.Allocator) -> string { return _temp_dir(allocator) } diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 17967393a..a7587988b 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -1,29 +1,29 @@ //+private package os2 +import "core:runtime" import win32 "core:sys/windows" _create_temp :: proc(dir, pattern: string) -> (^File, Error) { return nil, nil } -_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { +_mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (string, Error) { return "", nil } -_temp_dir :: proc(allocator := context.allocator) -> string { - b := make([dynamic]u16, u32(win32.MAX_PATH), context.temp_allocator) - for { - n := win32.GetTempPathW(u32(len(b)), raw_data(b)) - if n > u32(len(b)) { - resize(&b, int(n)) - continue - } - if n == 3 && b[1] == ':' && b[2] == '\\' { - - } else if n > 0 && b[n-1] == '\\' { - n -= 1 - } - return win32.utf16_to_utf8(b[:n], allocator) +_temp_dir :: proc(allocator: runtime.Allocator) -> string { + n := win32.GetTempPathW(0, nil) + if n == 0 { + return "" } + b := make([]u16, max(win32.MAX_PATH, n), _temp_allocator()) + n = win32.GetTempPathW(u32(len(b)), raw_data(b)) + + if n == 3 && b[1] == ':' && b[2] == '\\' { + + } else if n > 0 && b[n-1] == '\\' { + n -= 1 + } + return win32.utf16_to_utf8(b[:n], allocator) } diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index 976e61bb1..1fb653b85 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -1,18 +1,19 @@ package os2 import "core:strings" +import "core:runtime" -user_cache_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) { +user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { #partial switch ODIN_OS { case .Windows: dir = get_env("LocalAppData") if dir != "" { - dir = strings.clone(dir, allocator) + dir = strings.clone_safe(dir, allocator) or_return } case .Darwin: dir = get_env("HOME") if dir != "" { - dir = strings.concatenate({dir, "/Library/Caches"}, allocator) + dir = strings.concatenate_safe({dir, "/Library/Caches"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME") @@ -21,24 +22,26 @@ user_cache_dir :: proc(allocator := context.allocator) -> (dir: string, is_defin if dir == "" { return } - dir = strings.concatenate({dir, "/.cache"}, allocator) + dir = strings.concatenate_safe({dir, "/.cache"}, allocator) or_return } } - is_defined = dir != "" + if dir == "" { + err = .Invalid_Path + } return } -user_config_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) { +user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { #partial switch ODIN_OS { case .Windows: dir = get_env("AppData") if dir != "" { - dir = strings.clone(dir, allocator) + dir = strings.clone_safe(dir, allocator) or_return } case .Darwin: dir = get_env("HOME") if dir != "" { - dir = strings.concatenate({dir, "/Library/Application Support"}, allocator) + dir = strings.concatenate_safe({dir, "/Library/Application Support"}, allocator) or_return } case: // All other UNIX systems dir = get_env("XDG_CACHE_HOME") @@ -47,22 +50,24 @@ user_config_dir :: proc(allocator := context.allocator) -> (dir: string, is_defi if dir == "" { return } - dir = strings.concatenate({dir, "/.config"}, allocator) + dir = strings.concatenate_safe({dir, "/.config"}, allocator) or_return } } - is_defined = dir != "" + if dir == "" { + err = .Invalid_Path + } return } -user_home_dir :: proc() -> (dir: string, is_defined: bool) { +user_home_dir :: proc() -> (dir: string, err: Error) { env := "HOME" #partial switch ODIN_OS { case .Windows: env = "USERPROFILE" } if v := get_env(env); v != "" { - return v, true + return v, nil } - return "", false + return "", .Invalid_Path } diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index 395d2e73e..dae7ab2fb 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -119,7 +119,6 @@ lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, e } stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { - context.allocator = allocator s: OS_Stat diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 6770e7a95..4f594e22d 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -1140,6 +1140,7 @@ ERROR_BROKEN_PIPE: DWORD : 109 ERROR_CALL_NOT_IMPLEMENTED: DWORD : 120 ERROR_INSUFFICIENT_BUFFER: DWORD : 122 ERROR_INVALID_NAME: DWORD : 123 +ERROR_BAD_ARGUMENTS: DWORD: 160 ERROR_LOCK_FAILED: DWORD : 167 ERROR_ALREADY_EXISTS: DWORD : 183 ERROR_NO_DATA: DWORD : 232 From eef44b11f3b26cde6086251ce1b9f661557e4d5c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 13:17:58 +0100 Subject: [PATCH 040/254] Make the utf16 conversion procedures in `core:sys/windows` safer by checking for memory leaks --- core/os/dir_windows.odin | 2 +- core/os/env_windows.odin | 4 +-- core/os/file_windows.odin | 2 +- core/os/os2/env_windows.odin | 4 +-- core/os/os2/file.odin | 18 ++++++++++++ core/os/os2/file_stream.odin | 10 ++++++- core/os/os2/file_windows.odin | 12 ++++---- core/os/os2/stat_windows.odin | 14 ++++----- core/os/os2/temp_file.odin | 2 +- core/os/os2/temp_file_windows.odin | 4 +-- core/os/stat_windows.odin | 6 ++-- core/sys/win32/comdlg32.odin | 2 +- core/sys/win32/crt.odin | 2 +- core/sys/win32/general.odin | 18 ++++++------ core/sys/win32/tests/general.odin | 46 +++++++++++++++--------------- core/sys/windows/kernel32.odin | 5 ++++ core/sys/windows/util.odin | 26 ++++++++--------- 17 files changed, 105 insertions(+), 72 deletions(-) diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 3261b8cb3..89a09d403 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -13,7 +13,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { return } - path := strings.concatenate({base_path, `\`, win32.utf16_to_utf8(d.cFileName[:])}) + path := strings.concatenate({base_path, `\`, win32.utf16_to_utf8(d.cFileName[:]) or_else ""}) fi.fullpath = path fi.name = basename(path) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index 74981bc6e..9a33a0611 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -22,7 +22,7 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } if n <= u32(len(b)) { - value = win32.utf16_to_utf8(b[:n], allocator) + value, _ = win32.utf16_to_utf8(b[:n], allocator) found = true return } @@ -76,7 +76,7 @@ environ :: proc(allocator := context.allocator) -> []string { if i <= from { break } - append(&r, win32.utf16_to_utf8(envs[from:i], allocator)) + append(&r, win32.utf16_to_utf8(envs[from:i], allocator) or_else "") from = i + 1 } } diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index a9f78070f..daabe60f0 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -365,7 +365,7 @@ get_current_directory :: proc(allocator := context.allocator) -> string { win32.ReleaseSRWLockExclusive(&cwd_lock) - return win32.utf16_to_utf8(dir_buf_wstr, allocator) + return win32.utf16_to_utf8(dir_buf_wstr, allocator) or_else "" } set_current_directory :: proc(path: string) -> (err: Errno) { diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 52c6c0f55..f58922fac 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -29,7 +29,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", false } - value = win32.utf16_to_utf8(b[:n], allocator) + value = win32.utf16_to_utf8(b[:n], allocator) or_else "" found = true return } @@ -73,7 +73,7 @@ _environ :: proc(allocator: runtime.Allocator) -> []string { break } w := ([^]u16)(p)[from:i] - append(&r, win32.utf16_to_utf8(w, allocator)) + append(&r, win32.utf16_to_utf8(w, allocator) or_else "") from = i + 1 } } diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 268631dd6..4b271b9ea 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -21,6 +21,7 @@ File_Mode_Device :: File_Mode(1<<18) File_Mode_Char_Device :: File_Mode(1<<19) File_Mode_Sym_Link :: File_Mode(1<<20) +File_Mode_Perm :: File_Mode(0o777) // Unix permision bits File_Flags :: distinct bit_set[File_Flag; uint] File_Flag :: enum { @@ -194,3 +195,20 @@ is_dir :: proc(path: string) -> bool { return _is_dir(path) } + +copy_file :: proc(dst_path, src_path: string) -> Error { + src := open(src_path) or_return + defer close(src) + + info := fstat(src, _file_allocator()) or_return + defer file_info_delete(info, _file_allocator()) + if info.is_dir { + return .Invalid_File + } + + dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & File_Mode_Perm) or_return + defer close(dst) + + _, err := io.copy(to_writer(dst), to_reader(src)) + return err +} \ No newline at end of file diff --git a/core/os/os2/file_stream.odin b/core/os/os2/file_stream.odin index 7b68d0e21..5e3db9370 100644 --- a/core/os/os2/file_stream.odin +++ b/core/os/os2/file_stream.odin @@ -2,12 +2,20 @@ package os2 import "core:io" -file_to_stream :: proc(f: ^File) -> (s: io.Stream) { +to_stream :: proc(f: ^File) -> (s: io.Stream) { s.stream_data = f s.stream_vtable = _file_stream_vtable return } +to_writer :: proc(f: ^File) -> (s: io.Writer) { + return {to_stream(f)} +} +to_reader :: proc(f: ^File) -> (s: io.Reader) { + return {to_stream(f)} +} + + @(private) error_to_io_error :: proc(ferr: Error) -> io.Error { if ferr == nil { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index b9ebfe10e..7589ed799 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -529,16 +529,16 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st } if !has_unc_prefix(p) { - return win32.utf16_to_utf8(p, allocator), nil + return win32.utf16_to_utf8(p, allocator) } ws := p[4:] switch { case len(ws) >= 2 && ws[1] == ':': - return win32.utf16_to_utf8(ws, allocator), nil + return win32.utf16_to_utf8(ws, allocator) case has_prefix(ws, `UNC\`): ws[3] = '\\' // override data in buffer - return win32.utf16_to_utf8(ws[3:], allocator), nil + return win32.utf16_to_utf8(ws[3:], allocator) } @@ -560,9 +560,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st ws = ws[4:] if len(ws) > 3 && has_prefix(ws, `UNC`) { ws[2] = '\\' - return win32.utf16_to_utf8(ws[2:], allocator), nil + return win32.utf16_to_utf8(ws[2:], allocator) } - return win32.utf16_to_utf8(ws, allocator), nil + return win32.utf16_to_utf8(ws, allocator) } return "", .Invalid_Path } @@ -593,7 +593,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { - return win32.utf16_to_utf8(p, allocator), nil + return win32.utf16_to_utf8(p, allocator) } return _normalize_link_path(p, allocator) diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 603343a18..5de5269d7 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -57,7 +57,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path if n == 0 { return "", _get_platform_error() } - return win32.utf16_to_utf8(buf[:n], allocator), nil + return win32.utf16_to_utf8(buf[:n], allocator) } @@ -131,7 +131,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin } buf := make([]u16, max(n, 260)+1, _temp_allocator()) n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) - return _cleanpath_from_buf(buf[:n], allocator), nil + return _cleanpath_from_buf(buf[:n], allocator) } _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { @@ -149,7 +149,7 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { return _cleanpath_strip_prefix(buf[:n]), nil } -_cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> string { +_cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { buf := buf buf = _cleanpath_strip_prefix(buf) return win32.utf16_to_utf8(buf, allocator) @@ -194,15 +194,15 @@ file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { -_file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { - if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { +_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { + if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { mode |= 0o444 } else { mode |= 0o666 } is_sym := false - if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + if file_attributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { is_sym = false } else { is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT @@ -211,7 +211,7 @@ _file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HA if is_sym { mode |= File_Mode_Sym_Link } else { - if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { mode |= 0o111 | File_Mode_Dir } diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index faf176de1..b05c186a0 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -10,6 +10,6 @@ mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (strin return _mkdir_temp(dir, pattern, allocator) } -temp_dir :: proc(allocator: runtime.Allocator) -> string { +temp_dir :: proc(allocator: runtime.Allocator) -> (string, Error) { return _temp_dir(allocator) } diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index a7587988b..08837f7f0 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -12,10 +12,10 @@ _mkdir_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (stri return "", nil } -_temp_dir :: proc(allocator: runtime.Allocator) -> string { +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { n := win32.GetTempPathW(0, nil) if n == 0 { - return "" + return "", nil } b := make([]u16, max(win32.MAX_PATH, n), _temp_allocator()) n = win32.GetTempPathW(u32(len(b)), raw_data(b)) diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index 5da925560..79bb8c42e 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -20,7 +20,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa return "", Errno(win32.GetLastError()) } if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n], allocator), ERROR_NONE + return win32.utf16_to_utf8(buf[:n], allocator) or_else "", ERROR_NONE } resize(&buf, len(buf)*2) } @@ -136,7 +136,7 @@ cleanpath_from_handle :: proc(fd: Handle) -> (string, Errno) { if err != 0 { return "", err } - return win32.utf16_to_utf8(buf, context.allocator), err + return win32.utf16_to_utf8(buf, context.allocator) or_else "", err } @(private) cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) { @@ -157,7 +157,7 @@ cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Errno) { cleanpath_from_buf :: proc(buf: []u16) -> string { buf := buf buf = cleanpath_strip_prefix(buf) - return win32.utf16_to_utf8(buf, context.allocator) + return win32.utf16_to_utf8(buf, context.allocator) or_else "" } @(private) diff --git a/core/sys/win32/comdlg32.odin b/core/sys/win32/comdlg32.odin index 1e0d5c3ca..815def7b6 100644 --- a/core/sys/win32/comdlg32.odin +++ b/core/sys/win32/comdlg32.odin @@ -126,7 +126,7 @@ _open_file_dialog :: proc(title: string, dir: string, } - file_name := utf16_to_utf8(file_buf[:], allocator) + file_name, _ := utf16_to_utf8(file_buf[:], allocator) path = strings.trim_right_null(file_name) return } diff --git a/core/sys/win32/crt.odin b/core/sys/win32/crt.odin index b39d35375..8584a27be 100644 --- a/core/sys/win32/crt.odin +++ b/core/sys/win32/crt.odin @@ -10,6 +10,6 @@ foreign { get_cwd :: proc(allocator := context.temp_allocator) -> string { buffer := make([]u16, MAX_PATH_WIDE, allocator) _get_cwd_wide(Wstring(&buffer[0]), MAX_PATH_WIDE) - file := utf16_to_utf8(buffer[:], allocator) + file, _ := utf16_to_utf8(buffer[:], allocator) return strings.trim_right_null(file) } diff --git a/core/sys/win32/general.odin b/core/sys/win32/general.odin index 1baad8b11..64ee952ce 100644 --- a/core/sys/win32/general.odin +++ b/core/sys/win32/general.odin @@ -1,6 +1,8 @@ // +build windows package win32 +import "core:runtime" + Uint_Ptr :: distinct uintptr Int_Ptr :: distinct int Long_Ptr :: distinct int @@ -858,14 +860,14 @@ utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> Wstri return nil } -wstring_to_utf8 :: proc(s: Wstring, N: int, allocator := context.temp_allocator) -> string { +wstring_to_utf8 :: proc(s: Wstring, N: int, allocator := context.temp_allocator) -> (str: string, err: runtime.Allocator_Error) { if N == 0 { - return "" + return } n := wide_char_to_multi_byte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), nil, 0, nil, nil) if n == 0 { - return "" + return } // If N == -1 the call to wide_char_to_multi_byte assume the wide string is null terminated @@ -873,11 +875,11 @@ wstring_to_utf8 :: proc(s: Wstring, N: int, allocator := context.temp_allocator) // also null terminated. // If N != -1 it assumes the wide string is not null terminated and the resulting string // will not be null terminated, we therefore have to force it to be null terminated manually. - text := make([]byte, n+1 if N != -1 else n, allocator) + text := make([]byte, n+1 if N != -1 else n, allocator) or_return if n1 := wide_char_to_multi_byte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), cstring(&text[0]), n, nil, nil); n1 == 0 { delete(text, allocator) - return "" + return "", nil } for i in 0.. string { +utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> (string, runtime.Allocator_Error) { if len(s) == 0 { - return "" + return "", nil } return wstring_to_utf8(cast(Wstring)&s[0], len(s), allocator) } diff --git a/core/sys/win32/tests/general.odin b/core/sys/win32/tests/general.odin index 1a5fc911a..9a78dec11 100644 --- a/core/sys/win32/tests/general.odin +++ b/core/sys/win32/tests/general.odin @@ -1,41 +1,41 @@ package win32_tests -import "core:sys/win32" +import win32 "core:sys/windows" import "core:testing" utf16_to_utf8 :: proc(t: ^testing.T, str: []u16, comparison: string, expected_result: bool, loc := #caller_location) { - result := win32.utf16_to_utf8(str[:]); - testing.expect(t, (result == comparison) == expected_result, "Incorrect utf16_to_utf8 conversion", loc); + result, _ := win32.utf16_to_utf8(str[:]) + testing.expect(t, (result == comparison) == expected_result, "Incorrect utf16_to_utf8 conversion", loc) } wstring_to_utf8 :: proc(t: ^testing.T, str: []u16, comparison: string, expected_result: bool, loc := #caller_location) { - result := win32.wstring_to_utf8(nil if len(str) == 0 else cast(win32.Wstring)&str[0], -1); - testing.expect(t, (result == comparison) == expected_result, "Incorrect wstring_to_utf8 conversion", loc); + result, _ := win32.wstring_to_utf8(nil if len(str) == 0 else cast(win32.Wstring)&str[0], -1) + testing.expect(t, (result == comparison) == expected_result, "Incorrect wstring_to_utf8 conversion", loc) } @test test_utf :: proc(t: ^testing.T) { - utf16_to_utf8(t, []u16{}, "", true); - utf16_to_utf8(t, []u16{0}, "", true); - utf16_to_utf8(t, []u16{0, 't', 'e', 's', 't'}, "", true); - utf16_to_utf8(t, []u16{0, 't', 'e', 's', 't', 0}, "", true); - utf16_to_utf8(t, []u16{'t', 'e', 's', 't'}, "test", true); - utf16_to_utf8(t, []u16{'t', 'e', 's', 't', 0}, "test", true); - utf16_to_utf8(t, []u16{'t', 'e', 0, 's', 't'}, "te", true); - utf16_to_utf8(t, []u16{'t', 'e', 0, 's', 't', 0}, "te", true); + utf16_to_utf8(t, []u16{}, "", true) + utf16_to_utf8(t, []u16{0}, "", true) + utf16_to_utf8(t, []u16{0, 't', 'e', 's', 't'}, "", true) + utf16_to_utf8(t, []u16{0, 't', 'e', 's', 't', 0}, "", true) + utf16_to_utf8(t, []u16{'t', 'e', 's', 't'}, "test", true) + utf16_to_utf8(t, []u16{'t', 'e', 's', 't', 0}, "test", true) + utf16_to_utf8(t, []u16{'t', 'e', 0, 's', 't'}, "te", true) + utf16_to_utf8(t, []u16{'t', 'e', 0, 's', 't', 0}, "te", true) - wstring_to_utf8(t, []u16{}, "", true); - wstring_to_utf8(t, []u16{0}, "", true); - wstring_to_utf8(t, []u16{0, 't', 'e', 's', 't'}, "", true); - wstring_to_utf8(t, []u16{0, 't', 'e', 's', 't', 0}, "", true); - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 0}, "test", true); - wstring_to_utf8(t, []u16{'t', 'e', 0, 's', 't'}, "te", true); - wstring_to_utf8(t, []u16{'t', 'e', 0, 's', 't', 0}, "te", true); + wstring_to_utf8(t, []u16{}, "", true) + wstring_to_utf8(t, []u16{0}, "", true) + wstring_to_utf8(t, []u16{0, 't', 'e', 's', 't'}, "", true) + wstring_to_utf8(t, []u16{0, 't', 'e', 's', 't', 0}, "", true) + wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 0}, "test", true) + wstring_to_utf8(t, []u16{'t', 'e', 0, 's', 't'}, "te", true) + wstring_to_utf8(t, []u16{'t', 'e', 0, 's', 't', 0}, "te", true) // WARNING: Passing a non-zero-terminated string to wstring_to_utf8 is dangerous, // as it will go out of bounds looking for a zero. // It will "fail" or "succeed" by having a zero just after the end of the input string or not. - wstring_to_utf8(t, []u16{'t', 'e', 's', 't'}, "test", false); - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 0}[:4], "test", true); - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 'q'}[:4], "test", false); + wstring_to_utf8(t, []u16{'t', 'e', 's', 't'}, "test", false) + wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 0}[:4], "test", true) + wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 'q'}[:4], "test", false) } \ No newline at end of file diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 1f8927a16..ac959db2d 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -272,6 +272,11 @@ foreign kernel32 { HeapReAlloc :: proc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID, dwBytes: SIZE_T) -> LPVOID --- HeapFree :: proc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL --- + LocalAlloc :: proc(flags: UINT, bytes: SIZE_T) -> LPVOID --- + LocalReAlloc :: proc(mem: LPVOID, bytes: SIZE_T, flags: UINT) -> LPVOID --- + LocalFree :: proc(mem: LPVOID) -> LPVOID --- + + ReadDirectoryChangesW :: proc( hDirectory: HANDLE, lpBuffer: LPVOID, diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index f448f6bc5..5c8f35bef 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -2,7 +2,7 @@ package sys_windows import "core:strings" -import "core:sys/win32" +import "core:runtime" import "core:intrinsics" L :: intrinsics.constant_utf16_cstring @@ -56,16 +56,16 @@ utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstri return nil } -wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string) { +wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { context.allocator = allocator if N <= 0 { - return "" + return } n := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), nil, 0, nil, nil) if n == 0 { - return "" + return } // If N == -1 the call to WideCharToMultiByte assume the wide string is null terminated @@ -73,12 +73,12 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) // also null terminated. // If N != -1 it assumes the wide string is not null terminated and the resulting string // will not be null terminated, we therefore have to force it to be null terminated manually. - text := make([]byte, n+1 if N != -1 else n) + text := make([]byte, n+1 if N != -1 else n) or_return n1 := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), raw_data(text), n, nil, nil) if n1 == 0 { delete(text, allocator) - return "" + return } for i in 0.. string { +utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { if len(s) == 0 { - return "" + return "", nil } return wstring_to_utf8(raw_data(s), len(s), allocator) } @@ -216,7 +216,7 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s if !res { return "", {}, false } - computer_name = utf16_to_utf8(cname_w, context.temp_allocator) + computer_name = utf16_to_utf8(cname_w, context.temp_allocator) or_else "" ok = true return @@ -306,7 +306,7 @@ add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) { if res == false { return false, "" } - defer win32.local_free(sb) + defer LocalFree(sb) pszProfilePath := make([]u16, 257, context.temp_allocator) res2 := CreateProfile( @@ -318,7 +318,7 @@ add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) { if res2 != 0 { return false, "" } - profile_path = wstring_to_utf8(&pszProfilePath[0], 257) + profile_path = wstring_to_utf8(&pszProfilePath[0], 257) or_else "" return true, profile_path } @@ -336,7 +336,7 @@ delete_user_profile :: proc(username: string) -> (ok: bool) { if res == false { return false } - defer win32.local_free(sb) + defer LocalFree(sb) res2 := DeleteProfileW( sb, From dc832ad49fa01b083b08a2e09102a66f31b025c1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 13:20:55 +0100 Subject: [PATCH 041/254] Minor fix --- core/path/filepath/path_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 25b2ae500..28238dd6e 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -68,7 +68,7 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Errno) { return "", os.Errno(win32.GetLastError()) } if n <= u32(len(buf)) { - return win32.utf16_to_utf8(buf[:n], ta), os.ERROR_NONE + return win32.utf16_to_utf8(buf[:n], ta) or_else "", os.ERROR_NONE } resize(&buf, len(buf)*2) } From 2fb351bf04ee2af9c43858f7178d1bee1e1c7a06 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 13:45:17 +0100 Subject: [PATCH 042/254] Update sys/windows to be closer to the soon to be deleted sys/win32 --- core/sys/windows/bluetooth.odin | 2 +- core/sys/windows/comdlg32.odin | 168 ++++++++++++++++++++++++++++++++ core/sys/windows/kernel32.odin | 4 +- core/sys/windows/ntdll.odin | 4 +- core/sys/windows/shell32.odin | 2 +- core/sys/windows/wgl.odin | 87 +++++++++++++++++ 6 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 core/sys/windows/comdlg32.odin create mode 100644 core/sys/windows/wgl.odin diff --git a/core/sys/windows/bluetooth.odin b/core/sys/windows/bluetooth.odin index c9f6bcc93..dad44892a 100644 --- a/core/sys/windows/bluetooth.odin +++ b/core/sys/windows/bluetooth.odin @@ -51,7 +51,7 @@ BLUETOOTH_DEVICE_INFO :: struct { name: [BLUETOOTH_MAX_NAME_SIZE]u16, // Name of the device } -@(default_calling_convention = "std") +@(default_calling_convention="stdcall") foreign bthprops { /* Version diff --git a/core/sys/windows/comdlg32.odin b/core/sys/windows/comdlg32.odin new file mode 100644 index 000000000..a3709cba7 --- /dev/null +++ b/core/sys/windows/comdlg32.odin @@ -0,0 +1,168 @@ +// +build windows +package sys_windows + +foreign import "system:Comdlg32.lib" +import "core:strings" + +LPOFNHOOKPROC :: #type proc "stdcall" (hdlg: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> UINT_PTR + +OPENFILENAMEW :: struct { + lStructSize: DWORD, + hwndOwner: HWND, + hInstance: HINSTANCE, + lpstrFilter: wstring, + lpstrCustomFilter: wstring, + nMaxCustFilter: DWORD, + nFilterIndex: DWORD, + lpstrFile: wstring, + nMaxFile: DWORD, + lpstrFileTitle: wstring, + nMaxFileTitle: DWORD, + lpstrInitialDir: wstring, + lpstrTitle: wstring, + Flags: DWORD, + nFileOffset: WORD, + nFileExtension: WORD, + lpstrDefExt: wstring, + lCustData: LPARAM, + lpfnHook: LPOFNHOOKPROC, + lpTemplateName: wstring, + lpEditInfo: rawptr, // LPEDITMENU, + lpstrPrompt: wstring, + pvReserved: rawptr, + dwReserved: DWORD, + FlagsEx: DWORD, +} + +@(default_calling_convention="stdcall") +foreign Comdlg32 { + GetOpenFileNameW :: proc(arg1: ^OPENFILENAMEW) -> BOOL --- + GetSaveFileNameW :: proc(arg1: ^OPENFILENAMEW) -> BOOL --- + CommDlgExtendedError :: proc() -> u32 --- +} + +OPEN_TITLE :: "Select file to open" +OPEN_FLAGS :: u32(OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST) +OPEN_FLAGS_MULTI :: OPEN_FLAGS | OFN_ALLOWMULTISELECT | OFN_EXPLORER + +SAVE_TITLE :: "Select file to save" +SAVE_FLAGS :: u32(OFN_OVERWRITEPROMPT | OFN_EXPLORER) +SAVE_EXT :: "txt" + +Open_Save_Mode :: enum { + Open = 0, + Save = 1, +} + +_open_file_dialog :: proc(title: string, dir: string, + filters: []string, default_filter: u32, + flags: u32, default_ext: string, + mode: Open_Save_Mode, allocator := context.temp_allocator) -> (path: string, ok: bool = true) { + context.allocator = allocator + file_buf := make([]u16, MAX_PATH_WIDE) + defer if !ok { + delete(file_buf) + } + + // Filters need to be passed as a pair of strings (title, filter) + filter_len := u32(len(filters)) + if filter_len % 2 != 0 { + return "", false + } + + filter: string + filter = strings.join(filters, "\u0000", context.temp_allocator) + filter = strings.concatenate({filter, "\u0000"}, context.temp_allocator) + + ofn := OPENFILENAMEW{ + lStructSize = size_of(OPENFILENAMEW), + lpstrFile = wstring(&file_buf[0]), + nMaxFile = MAX_PATH_WIDE, + lpstrTitle = utf8_to_wstring(title, context.temp_allocator), + lpstrFilter = utf8_to_wstring(filter, context.temp_allocator), + lpstrInitialDir = utf8_to_wstring(dir, context.temp_allocator), + nFilterIndex = u32(clamp(default_filter, 1, filter_len / 2)), + lpstrDefExt = utf8_to_wstring(default_ext, context.temp_allocator), + Flags = u32(flags), + } + + switch mode { + case .Open: + ok = bool(GetOpenFileNameW(&ofn)) + case .Save: + ok = bool(GetSaveFileNameW(&ofn)) + case: + ok = false + } + + if !ok { + return + } + + + file_name, _ := utf16_to_utf8(file_buf[:], allocator) + path = strings.trim_right_null(file_name) + return +} + +select_file_to_open :: proc(title := OPEN_TITLE, dir := ".", + filters := []string{"All Files", "*.*"}, default_filter := u32(1), + flags := OPEN_FLAGS, allocator := context.temp_allocator) -> (path: string, ok: bool) { + + path, ok = _open_file_dialog(title, dir, filters, default_filter, flags, "", Open_Save_Mode.Open, allocator) + return +} + +select_file_to_save :: proc(title := SAVE_TITLE, dir := ".", + filters := []string{"All Files", "*.*"}, default_filter := u32(1), + flags := SAVE_FLAGS, default_ext := SAVE_EXT, + allocator := context.temp_allocator) -> (path: string, ok: bool) { + + path, ok = _open_file_dialog(title, dir, filters, default_filter, flags, default_ext, Open_Save_Mode.Save, allocator) + return +} + +// TODO: Implement convenience function for select_file_to_open with ALLOW_MULTI_SELECT that takes +// it output of the form "path\u0000\file1u\0000file2" and turns it into []string with the path + file pre-concatenated for you. + +OFN_ALLOWMULTISELECT :: 0x00000200 // NOTE(Jeroen): Without OFN_EXPLORER it uses the Win3 dialog. +OFN_CREATEPROMPT :: 0x00002000 +OFN_DONTADDTORECENT :: 0x02000000 +OFN_ENABLEHOOK :: 0x00000020 +OFN_ENABLEINCLUDENOTIFY :: 0x00400000 +OFN_ENABLESIZING :: 0x00800000 +OFN_ENABLETEMPLATE :: 0x00000040 +OFN_ENABLETEMPLATEHANDLE :: 0x00000080 +OFN_EXPLORER :: 0x00080000 +OFN_EXTENSIONDIFFERENT :: 0x00000400 +OFN_FILEMUSTEXIST :: 0x00001000 +OFN_FORCESHOWHIDDEN :: 0x10000000 +OFN_HIDEREADONLY :: 0x00000004 +OFN_LONGNAMES :: 0x00200000 +OFN_NOCHANGEDIR :: 0x00000008 +OFN_NODEREFERENCELINKS :: 0x00100000 +OFN_NOLONGNAMES :: 0x00040000 +OFN_NONETWORKBUTTON :: 0x00020000 +OFN_NOREADONLYRETURN :: 0x00008000 +OFN_NOTESTFILECREATE :: 0x00010000 +OFN_NOVALIDATE :: 0x00000100 +OFN_OVERWRITEPROMPT :: 0x00000002 +OFN_PATHMUSTEXIST :: 0x00000800 +OFN_READONLY :: 0x00000001 +OFN_SHAREAWARE :: 0x00004000 +OFN_SHOWHELP :: 0x00000010 + +CDERR_DIALOGFAILURE :: 0x0000FFFF +CDERR_GENERALCODES :: 0x00000000 +CDERR_STRUCTSIZE :: 0x00000001 +CDERR_INITIALIZATION :: 0x00000002 +CDERR_NOTEMPLATE :: 0x00000003 +CDERR_NOHINSTANCE :: 0x00000004 +CDERR_LOADSTRFAILURE :: 0x00000005 +CDERR_FINDRESFAILURE :: 0x00000006 +CDERR_LOADRESFAILURE :: 0x00000007 +CDERR_LOCKRESFAILURE :: 0x00000008 +CDERR_MEMALLOCFAILURE :: 0x00000009 +CDERR_MEMLOCKFAILURE :: 0x0000000A +CDERR_NOHOOK :: 0x0000000B +CDERR_REGISTERMSGFAIL :: 0x0000000C diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index ac959db2d..e235b45ed 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -3,8 +3,6 @@ package sys_windows foreign import kernel32 "system:Kernel32.lib" - - @(default_calling_convention="stdcall") foreign kernel32 { OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- @@ -781,7 +779,7 @@ foreign kernel32 { ) -> BOOL --- } -@(default_calling_convention = "std") +@(default_calling_convention="stdcall") foreign kernel32 { @(link_name="SetConsoleCtrlHandler") set_console_ctrl_handler :: proc(handler: Handler_Routine, add: BOOL) -> BOOL --- } diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 5deffd9f9..dda5b9711 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -3,7 +3,7 @@ package sys_windows foreign import ntdll_lib "system:ntdll.lib" -@(default_calling_convention="std") +@(default_calling_convention="stdcall") foreign ntdll_lib { - RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- + RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- } \ No newline at end of file diff --git a/core/sys/windows/shell32.odin b/core/sys/windows/shell32.odin index 70d8943bd..a6ecefc32 100644 --- a/core/sys/windows/shell32.odin +++ b/core/sys/windows/shell32.odin @@ -3,7 +3,7 @@ package sys_windows foreign import shell32 "system:Shell32.lib" -@(default_calling_convention = "std") +@(default_calling_convention="stdcall") foreign shell32 { CommandLineToArgvW :: proc(cmd_list: wstring, num_args: ^c_int) -> ^wstring --- } diff --git a/core/sys/windows/wgl.odin b/core/sys/windows/wgl.odin new file mode 100644 index 000000000..689a41dea --- /dev/null +++ b/core/sys/windows/wgl.odin @@ -0,0 +1,87 @@ +// +build windows +package sys_windows + +import "core:c" + +foreign import "system:Opengl32.lib" + +CONTEXT_MAJOR_VERSION_ARB :: 0x2091 +CONTEXT_MINOR_VERSION_ARB :: 0x2092 +CONTEXT_FLAGS_ARB :: 0x2094 +CONTEXT_PROFILE_MASK_ARB :: 0x9126 +CONTEXT_FORWARD_COMPATIBLE_BIT_ARB :: 0x0002 +CONTEXT_CORE_PROFILE_BIT_ARB :: 0x00000001 +CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB :: 0x00000002 + +HGLRC :: distinct HANDLE + +LPLAYERPLANEDESCRIPTOR :: ^LAYERPLANEDESCRIPTOR +LAYERPLANEDESCRIPTOR :: struct { + nSize: WORD, + nVersion: WORD, + dwFlags: DWORD, + iPixelType: BYTE, + cColorBits: BYTE, + cRedBits: BYTE, + cRedShift: BYTE, + cGreenBits: BYTE, + cGreenShift: BYTE, + cBlueBits: BYTE, + cBlueShift: BYTE, + cAlphaBits: BYTE, + cAlphaShift: BYTE, + cAccumBits: BYTE, + cAccumRedBits: BYTE, + cAccumGreenBits: BYTE, + cAccumBlueBits: BYTE, + cAccumAlphaBits: BYTE, + cDepthBits: BYTE, + cStencilBits: BYTE, + cAuxBuffers: BYTE, + iLayerPlane: BYTE, + bReserved: BYTE, + crTransparent: COLORREF, +} + +POINTFLOAT :: struct {x, y: f32} + +LPGLYPHMETRICSFLOAT :: ^GLYPHMETRICSFLOAT +GLYPHMETRICSFLOAT :: struct { + gmfBlackBoxX: f32, + gmfBlackBoxY: f32, + gmfptGlyphOrigin: POINTFLOAT, + gmfCellIncX: f32, + gmfCellIncY: f32, +} + +CreateContextAttribsARBType :: #type proc "c" (hdc: HDC, hShareContext: rawptr, attribList: [^]c.int) -> HGLRC +ChoosePixelFormatARBType :: #type proc "c" (hdc: HDC, attribIList: [^]c.int, attribFList: [^]f32, maxFormats: DWORD, formats: [^]c.int, numFormats: [^]DWORD) -> BOOL +SwapIntervalEXTType :: #type proc "c" (interval: c.int) -> bool +GetExtensionsStringARBType :: #type proc "c" (HDC) -> cstring + +// Procedures + wglCreateContextAttribsARB: CreateContextAttribsARBType + wglChoosePixelFormatARB: ChoosePixelFormatARBType + wglSwapIntervalExt: SwapIntervalEXTType + wglGetExtensionsStringARB: GetExtensionsStringARBType + + +@(default_calling_convention="stdcall") +foreign Opengl32 { + wglCreateContext :: proc(hdc: HDC) -> HGLRC --- + wglMakeCurrent :: proc(hdc: HDC, HGLRC: HGLRC) -> BOOL --- + wglGetProcAddress :: proc(c_str: cstring) -> rawptr --- + wglDeleteContext :: proc(HGLRC: HGLRC) -> BOOL --- + wglCopyContext :: proc(src, dst: HGLRC, mask: UINT) -> BOOL --- + wglCreateLayerContext :: proc(hdc: HDC, layer_plane: c.int) -> HGLRC --- + wglDescribeLayerPlane :: proc(hdc: HDC, pixel_format, layer_plane: c.int, bytes: UINT, pd: LPLAYERPLANEDESCRIPTOR) -> BOOL --- + wglGetCurrentContext :: proc() -> HGLRC --- + wglGetCurrentDC :: proc() -> HDC --- + wglGetLayerPaletteEntries :: proc(hdc: HDC, layer_plane, start, entries: c.int, cr: ^COLORREF) -> c.int --- + wglRealizeLayerPalette :: proc(hdc: HDC, layer_plane: c.int, realize: BOOL) -> BOOL --- + wglSetLayerPaletteEntries :: proc(hdc: HDC, layer_plane, start, entries: c.int, cr: ^COLORREF) -> c.int --- + wglShareLists :: proc(HGLRC1, HGLRC2: HGLRC) -> BOOL --- + wglSwapLayerBuffers :: proc(hdc: HDC, planes: DWORD) -> BOOL --- + wglUseFontBitmaps :: proc(hdc: HDC, first, count, list_base: DWORD) -> BOOL --- + wglUseFontOutlines :: proc(hdc: HDC, first, count, list_base: DWORD, deviation, extrusion: f32, format: c.int, gmf: LPGLYPHMETRICSFLOAT) -> BOOL --- +} From d1fc9d3073ac85e643199d7558c6fce548084ba3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 13:54:40 +0100 Subject: [PATCH 043/254] Add more libraries --- core/sys/windows/advapi32.odin | 6 ++ core/sys/windows/kernel32.odin | 3 +- core/sys/windows/ole32.odin | 18 +++++ core/sys/windows/shlwapi.odin | 11 +++ core/sys/windows/types.odin | 143 ++++++++++++++++++++++++++++++--- 5 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 core/sys/windows/ole32.odin create mode 100644 core/sys/windows/shlwapi.odin diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index a2a24242f..20badb5da 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -3,6 +3,8 @@ package sys_windows foreign import advapi32 "system:Advapi32.lib" +HCRYPTPROV :: distinct HANDLE + @(default_calling_convention="stdcall") foreign advapi32 { @(link_name = "SystemFunction036") @@ -10,6 +12,10 @@ foreign advapi32 { OpenProcessToken :: proc(ProcessHandle: HANDLE, DesiredAccess: DWORD, TokenHandle: ^HANDLE) -> BOOL --- + + CryptAcquireContextW :: proc(hProv: ^HCRYPTPROV, szContainer, szProvider: wstring, dwProvType, dwFlags: DWORD) -> DWORD --- + CryptGenRandom :: proc(hProv: HCRYPTPROV, dwLen: DWORD, buf: LPVOID) -> DWORD --- + CryptReleaseContext :: proc(hProv: HCRYPTPROV, dwFlags: DWORD) -> DWORD --- } // Necessary to create a token to impersonate a user with for CreateProcessAsUser diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index e235b45ed..f52fb46e6 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -21,7 +21,8 @@ foreign kernel32 { GetConsoleMode :: proc(hConsoleHandle: HANDLE, lpMode: LPDWORD) -> BOOL --- - + SetConsoleMode :: proc(hConsoleHandle: HANDLE, + dwMode: DWORD) -> BOOL --- GetFileInformationByHandle :: proc(hFile: HANDLE, lpFileInformation: LPBY_HANDLE_FILE_INFORMATION) -> BOOL --- SetHandleInformation :: proc(hObject: HANDLE, diff --git a/core/sys/windows/ole32.odin b/core/sys/windows/ole32.odin new file mode 100644 index 000000000..23fe888d2 --- /dev/null +++ b/core/sys/windows/ole32.odin @@ -0,0 +1,18 @@ +// +build windows +package sys_windows + +foreign import "system:Ole32.lib" + +//objbase.h +COINIT :: enum DWORD { + APARTMENTTHREADED = 0x2, + MULTITHREADED, + DISABLE_OLE1DDE = 0x4, + SPEED_OVER_MEMORY = 0x8, +} + +@(default_calling_convention="stdcall") +foreign Ole32 { + CoInitializeEx :: proc(reserved: rawptr, co_init: COINIT) -> HRESULT --- + CoUninitialize :: proc() --- +} diff --git a/core/sys/windows/shlwapi.odin b/core/sys/windows/shlwapi.odin new file mode 100644 index 000000000..1852d536f --- /dev/null +++ b/core/sys/windows/shlwapi.odin @@ -0,0 +1,11 @@ +// +build windows +package sys_windows + +foreign import shlwapi "system:shlwapi.lib" + +@(default_calling_convention="stdcall") +foreign shlwapi { + PathFileExistsW :: proc(pszPath: wstring) -> BOOL --- + PathFindExtensionW :: proc(pszPath: wstring) -> wstring --- + PathFindFileNameW :: proc(pszPath: wstring) -> wstring --- +} diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 4f594e22d..b497f181e 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -3,19 +3,21 @@ package sys_windows import "core:c" -c_char :: c.char -c_uchar :: c.uchar -c_int :: c.int -c_uint :: c.uint -c_long :: c.long -c_longlong :: c.longlong -c_ulong :: c.ulong -c_short :: c.short -c_ushort :: c.ushort -size_t :: c.size_t -wchar_t :: c.wchar_t +c_char :: c.char +c_uchar :: c.uchar +c_int :: c.int +c_uint :: c.uint +c_long :: c.long +c_longlong :: c.longlong +c_ulong :: c.ulong +c_ulonglong :: c.ulonglong +c_short :: c.short +c_ushort :: c.ushort +size_t :: c.size_t +wchar_t :: c.wchar_t DWORD :: c_ulong +QWORD :: c.ulonglong HANDLE :: distinct LPVOID HINSTANCE :: HANDLE HMODULE :: distinct HINSTANCE @@ -2170,3 +2172,122 @@ SYSTEMTIME :: struct { second: WORD, milliseconds: WORD, } + + +@(private="file") +IMAGE_DOS_HEADER :: struct { + e_magic: WORD, + e_cblp: WORD, + e_cp: WORD, + e_crlc: WORD, + e_cparhdr: WORD, + e_minalloc: WORD, + e_maxalloc: WORD, + e_ss: WORD, + e_sp: WORD, + e_csum: WORD, + e_ip: WORD, + e_cs: WORD, + e_lfarlc: WORD, + e_ovno: WORD, + e_res_0: WORD, + e_res_1: WORD, + e_res_2: WORD, + e_res_3: WORD, + e_oemid: WORD, + e_oeminfo: WORD, + e_res2_0: WORD, + e_res2_1: WORD, + e_res2_2: WORD, + e_res2_3: WORD, + e_res2_4: WORD, + e_res2_5: WORD, + e_res2_6: WORD, + e_res2_7: WORD, + e_res2_8: WORD, + e_res2_9: WORD, + e_lfanew: DWORD, +} + +IMAGE_DATA_DIRECTORY :: struct { + VirtualAddress: DWORD, + Size: DWORD, +} + +IMAGE_FILE_HEADER :: struct { + Machine: WORD, + NumberOfSections: WORD, + TimeDateStamp: DWORD, + PointerToSymbolTable: DWORD, + NumberOfSymbols: DWORD, + SizeOfOptionalHeader: WORD, + Characteristics: WORD, +} + +IMAGE_OPTIONAL_HEADER64 :: struct { + Magic: WORD, + MajorLinkerVersion: BYTE, + MinorLinkerVersion: BYTE, + SizeOfCode: DWORD, + SizeOfInitializedData: DWORD, + SizeOfUninitializedData: DWORD, + AddressOfEntryPoint: DWORD, + BaseOfCode: DWORD, + ImageBase: QWORD, + SectionAlignment: DWORD, + FileAlignment: DWORD, + MajorOperatingSystemVersion: WORD, + MinorOperatingSystemVersion: WORD, + MajorImageVersion: WORD, + MinorImageVersion: WORD, + MajorSubsystemVersion: WORD, + MinorSubsystemVersion: WORD, + Win32VersionValue: DWORD, + SizeOfImage: DWORD, + SizeOfHeaders: DWORD, + CheckSum: DWORD, + Subsystem: WORD, + DllCharacteristics: WORD, + SizeOfStackReserve: QWORD, + SizeOfStackCommit: QWORD, + SizeOfHeapReserve: QWORD, + SizeOfHeapCommit: QWORD, + LoaderFlags: DWORD, + NumberOfRvaAndSizes: DWORD, + ExportTable: IMAGE_DATA_DIRECTORY, + ImportTable: IMAGE_DATA_DIRECTORY, + ResourceTable: IMAGE_DATA_DIRECTORY, + ExceptionTable: IMAGE_DATA_DIRECTORY, + CertificateTable: IMAGE_DATA_DIRECTORY, + BaseRelocationTable: IMAGE_DATA_DIRECTORY, + Debug: IMAGE_DATA_DIRECTORY, + Architecture: IMAGE_DATA_DIRECTORY, + GlobalPtr: IMAGE_DATA_DIRECTORY, + TLSTable: IMAGE_DATA_DIRECTORY, + LoadConfigTable: IMAGE_DATA_DIRECTORY, + BoundImport: IMAGE_DATA_DIRECTORY, + IAT: IMAGE_DATA_DIRECTORY, + DelayImportDescriptor: IMAGE_DATA_DIRECTORY, + CLRRuntimeHeader: IMAGE_DATA_DIRECTORY, + Reserved: IMAGE_DATA_DIRECTORY, +} + +IMAGE_NT_HEADERS64 :: struct { + Signature: DWORD, + FileHeader: IMAGE_FILE_HEADER, + OptionalHeader: IMAGE_OPTIONAL_HEADER64, +} + +IMAGE_EXPORT_DIRECTORY :: struct { + Characteristics: DWORD, + TimeDateStamp: DWORD, + MajorVersion: WORD, + MinorVersion: WORD, + Name: DWORD, + Base: DWORD, + NumberOfFunctions: DWORD, + NumberOfNames: DWORD, + AddressOfFunctions: DWORD, // RVA from base of image + AddressOfNames: DWORD, // RVA from base of image + AddressOfNameOrdinals: DWORD, // RVA from base of image +} \ No newline at end of file From 0c45a46aab99fdd924afd8e0385fd27e40c60dec Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 14:21:25 +0100 Subject: [PATCH 044/254] Remove `A` calls in favour of `W` calls --- core/sys/windows/bluetooth.odin | 47 ++++-------------- core/sys/windows/kernel32.odin | 3 +- core/sys/windows/synchronization.odin | 4 +- core/sys/windows/user32.odin | 68 +-------------------------- core/sys/windows/util.odin | 32 ++++++------- core/sys/windows/winmm.odin | 2 +- 6 files changed, 33 insertions(+), 123 deletions(-) diff --git a/core/sys/windows/bluetooth.odin b/core/sys/windows/bluetooth.odin index dad44892a..c2534896b 100644 --- a/core/sys/windows/bluetooth.odin +++ b/core/sys/windows/bluetooth.odin @@ -56,49 +56,22 @@ foreign bthprops { /* Version */ - @(link_name="BluetoothIsVersionAvailable") bluetooth_is_version_available :: proc( - major: u8, minor: u8, - ) -> BOOL --- + BluetoothIsVersionAvailable :: proc(major: u8, minor: u8) -> BOOL --- /* Radio enumeration */ - @(link_name="BluetoothFindFirstRadio") bluetooth_find_first_radio :: proc( - find_radio_params: ^BLUETOOTH_FIND_RADIO_PARAMS, radio: ^HANDLE, - ) -> HBLUETOOTH_RADIO_FIND --- - - @(link_name="BluetoothFindNextRadio") bluetooth_find_next_radio :: proc( - handle: HBLUETOOTH_RADIO_FIND, radio: ^HANDLE, - ) -> BOOL --- - - @(link_name="BluetoothFindRadioClose") bluetooth_find_radio_close :: proc( - handle: HBLUETOOTH_RADIO_FIND, - ) -> BOOL --- - - @(link_name="BluetoothGetRadioInfo") bluetooth_get_radio_info :: proc( - radio: HANDLE, radio_info: ^BLUETOOTH_RADIO_INFO, - ) -> DWORD --- + BluetoothFindFirstRadio :: proc(find_radio_params: ^BLUETOOTH_FIND_RADIO_PARAMS, radio: ^HANDLE) -> HBLUETOOTH_RADIO_FIND --- + BluetoothFindNextRadio :: proc(handle: HBLUETOOTH_RADIO_FIND, radio: ^HANDLE) -> BOOL --- + BluetoothFindRadioClose :: proc(handle: HBLUETOOTH_RADIO_FIND) -> BOOL --- + BluetoothGetRadioInfo :: proc(radio: HANDLE, radio_info: ^BLUETOOTH_RADIO_INFO) -> DWORD --- /* Device enumeration */ - @(link_name="BluetoothFindFirstDevice") bluetooth_find_first_device :: proc( - search_params: ^BLUETOOTH_DEVICE_SEARCH_PARAMS, device_info: ^BLUETOOTH_DEVICE_INFO, - ) -> HBLUETOOTH_DEVICE_FIND --- - - @(link_name="BluetoothFindNextDevice") bluetooth_find_next_device :: proc( - handle: HBLUETOOTH_DEVICE_FIND, device_info: ^BLUETOOTH_DEVICE_INFO, - ) -> BOOL --- - - @(link_name="BluetoothFindDeviceClose") bluetooth_find_device_close :: proc( - handle: HBLUETOOTH_DEVICE_FIND, - ) -> BOOL --- - - @(link_name="BluetoothGetDeviceInfo") bluetooth_get_device_info :: proc( - radio: HANDLE, device_info: ^BLUETOOTH_DEVICE_INFO, - ) -> DWORD --- - - @(link_name="BluetoothDisplayDeviceProperties") bluetooth_display_device_properties :: proc( - hwnd_parent: HWND, device_info: ^BLUETOOTH_DEVICE_INFO, - ) -> BOOL --- + BluetoothFindFirstDevice :: proc(search_params: ^BLUETOOTH_DEVICE_SEARCH_PARAMS, device_info: ^BLUETOOTH_DEVICE_INFO) -> HBLUETOOTH_DEVICE_FIND --- + BluetoothFindNextDevice :: proc(handle: HBLUETOOTH_DEVICE_FIND, device_info: ^BLUETOOTH_DEVICE_INFO) -> BOOL --- + BluetoothFindDeviceClose :: proc(handle: HBLUETOOTH_DEVICE_FIND) -> BOOL --- + BluetoothGetDeviceInfo :: proc(radio: HANDLE, device_info: ^BLUETOOTH_DEVICE_INFO) -> DWORD --- + BluetoothDisplayDeviceProperties :: proc(hwnd_parent: HWND, device_info: ^BLUETOOTH_DEVICE_INFO) -> BOOL --- } \ No newline at end of file diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index f52fb46e6..ad637db82 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -5,7 +5,8 @@ foreign import kernel32 "system:Kernel32.lib" @(default_calling_convention="stdcall") foreign kernel32 { - OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- + OutputDebugStringA :: proc(lpOutputString: LPCSTR) --- // The only A thing that is allowed + OutputDebugStringW :: proc(lpOutputString: LPCSTR) --- ReadConsoleW :: proc(hConsoleInput: HANDLE, lpBuffer: LPVOID, diff --git a/core/sys/windows/synchronization.odin b/core/sys/windows/synchronization.odin index c4e1d2188..c98730aa0 100644 --- a/core/sys/windows/synchronization.odin +++ b/core/sys/windows/synchronization.odin @@ -5,7 +5,7 @@ foreign import Synchronization "system:Synchronization.lib" @(default_calling_convention="stdcall") foreign Synchronization { - WaitOnAddress :: proc(Address: PVOID, CompareAddress: PVOID, AddressSize: SIZE_T, dwMilliseconds: DWORD) -> BOOL --- + WaitOnAddress :: proc(Address: PVOID, CompareAddress: PVOID, AddressSize: SIZE_T, dwMilliseconds: DWORD) -> BOOL --- WakeByAddressSingle :: proc(Address: PVOID) --- - WakeByAddressAll :: proc(Address: PVOID) --- + WakeByAddressAll :: proc(Address: PVOID) --- } diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index 2010f0810..1b6d23ba4 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -5,43 +5,20 @@ foreign import user32 "system:User32.lib" @(default_calling_convention="stdcall") foreign user32 { - GetClassInfoA :: proc(hInstance: HINSTANCE, lpClassNAme: LPCSTR, lpWndClass: ^WNDCLASSA) -> BOOL --- GetClassInfoW :: proc(hInstance: HINSTANCE, lpClassNAme: LPCWSTR, lpWndClass: ^WNDCLASSW) -> BOOL --- - GetClassInfoExA :: proc(hInsatnce: HINSTANCE, lpszClass: LPCSTR, lpwcx: ^WNDCLASSEXA) -> BOOL --- GetClassInfoExW :: proc(hInsatnce: HINSTANCE, lpszClass: LPCWSTR, lpwcx: ^WNDCLASSEXW) -> BOOL --- - GetClassLongA :: proc(hWnd: HWND, nIndex: c_int) -> DWORD --- GetClassLongW :: proc(hWnd: HWND, nIndex: c_int) -> DWORD --- - SetClassLongA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> DWORD --- SetClassLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> DWORD --- - GetWindowLongA :: proc(hWnd: HWND, nIndex: c_int) -> LONG --- GetWindowLongW :: proc(hWnd: HWND, nIndex: c_int) -> LONG --- - SetWindowLongA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> LONG --- SetWindowLongW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG) -> LONG --- - GetClassNameA :: proc(hWnd: HWND, lpClassName: LPSTR, nMaxCount: c_int) -> c_int --- GetClassNameW :: proc(hWnd: HWND, lpClassName: LPWSTR, nMaxCount: c_int) -> c_int --- - RegisterClassA :: proc(lpWndClass: ^WNDCLASSA) -> ATOM --- RegisterClassW :: proc(lpWndClass: ^WNDCLASSW) -> ATOM --- - RegisterClassExA :: proc(^WNDCLASSEXA) -> ATOM --- RegisterClassExW :: proc(^WNDCLASSEXW) -> ATOM --- - CreateWindowExA :: proc( - dwExStyle: DWORD, - lpClassName: LPCSTR, - lpWindowName: LPCSTR, - dwStyle: DWORD, - X: c_int, - Y: c_int, - nWidth: c_int, - nHeight: c_int, - hWndParent: HWND, - hMenu: HMENU, - hInstance: HINSTANCE, - lpParam: LPVOID, - ) -> HWND --- CreateWindowExW :: proc( dwExStyle: DWORD, lpClassName: LPCWSTR, @@ -67,11 +44,9 @@ foreign user32 { SetActiveWindow :: proc(hWnd: HWND) -> HWND --- GetActiveWindow :: proc() -> HWND --- - GetMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- GetMessageW :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT) -> BOOL --- TranslateMessage :: proc(lpMsg: ^MSG) -> BOOL --- - DispatchMessageA :: proc(lpMsg: ^MSG) -> LRESULT --- DispatchMessageW :: proc(lpMsg: ^MSG) -> LRESULT --- PeekMessageA :: proc(lpMsg: ^MSG, hWnd: HWND, wMsgFilterMin: UINT, wMsgFilterMax: UINT, wRemoveMsg: UINT) -> BOOL --- @@ -141,10 +116,8 @@ foreign user32 { GetKeyState :: proc(nVirtKey: c_int) -> SHORT --- GetAsyncKeyState :: proc(vKey: c_int) -> SHORT --- - MapVirtualKeyA :: proc(uCode: UINT, uMapType: UINT) -> UINT --- MapVirtualKeyW :: proc(uCode: UINT, uMapType: UINT) -> UINT --- - SetWindowsHookExA :: proc(idHook: c_int, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK --- SetWindowsHookExW :: proc(idHook: c_int, lpfn: HOOKPROC, hmod: HINSTANCE, dwThreadId: DWORD) -> HHOOK --- UnhookWindowsHookEx :: proc(hhk: HHOOK) -> BOOL --- CallNextHookEx :: proc(hhk: HHOOK, nCode: c_int, wParam: WPARAM, lParam: LPARAM) -> LRESULT --- @@ -152,9 +125,9 @@ foreign user32 { SetTimer :: proc(hWnd: HWND, nIDEvent: UINT_PTR, uElapse: UINT, lpTimerFunc: TIMERPROC) -> UINT_PTR --- KillTimer :: proc(hWnd: HWND, uIDEvent: UINT_PTR) -> BOOL --- - MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> c_int --- + // MessageBoxA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT) -> c_int --- MessageBoxW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT) -> c_int --- - MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int --- + // MessageBoxExA :: proc(hWnd: HWND, lpText: LPCSTR, lpCaption: LPCSTR, uType: UINT, wLanguageId: WORD) -> c_int --- MessageBoxExW :: proc(hWnd: HWND, lpText: LPCWSTR, lpCaption: LPCWSTR, uType: UINT, wLanguageId: WORD) -> c_int --- ClipCursor :: proc(lpRect: LPRECT) -> BOOL --- @@ -163,35 +136,6 @@ foreign user32 { SetCursor :: proc(hCursor: HCURSOR) -> HCURSOR --- } -CreateWindowA :: #force_inline proc "stdcall" ( - lpClassName: LPCSTR, - lpWindowName: LPCSTR, - dwStyle: DWORD, - X: c_int, - Y: c_int, - nWidth: c_int, - nHeight: c_int, - hWndParent: HWND, - hMenu: HMENU, - hInstance: HINSTANCE, - lpParam: LPVOID, -) -> HWND { - return CreateWindowExA( - 0, - lpClassName, - lpWindowName, - dwStyle, - X, - Y, - nWidth, - nHeight, - hWndParent, - hMenu, - hInstance, - lpParam, - ) -} - CreateWindowW :: #force_inline proc "stdcall" ( lpClassName: LPCTSTR, lpWindowName: LPCTSTR, @@ -224,25 +168,17 @@ CreateWindowW :: #force_inline proc "stdcall" ( when ODIN_ARCH == .amd64 { @(default_calling_convention="stdcall") foreign user32 { - GetClassLongPtrA :: proc(hWnd: HWND, nIndex: c_int) -> ULONG_PTR --- GetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> ULONG_PTR --- - SetClassLongPtrA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR --- SetClassLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> ULONG_PTR --- - GetWindowLongPtrA :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR --- GetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int) -> LONG_PTR --- - SetWindowLongPtrA :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR --- SetWindowLongPtrW :: proc(hWnd: HWND, nIndex: c_int, dwNewLong: LONG_PTR) -> LONG_PTR --- } } else when ODIN_ARCH == .i386 { - GetClassLongPtrA :: GetClassLongA GetClassLongPtrW :: GetClassLongW - SetClassLongPtrA :: SetClassLongA SetClassLongPtrW :: SetClassLongW - GetWindowLongPtrA :: GetWindowLongA GetWindowLongPtrW :: GetWindowLongW - SetWindowLongPtrA :: GetWindowLongA SetWindowLongPtrW :: GetWindowLongW } diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 5c8f35bef..1c8b9175b 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -451,20 +451,20 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P nil, // lpProcessAttributes, nil, // lpThreadAttributes, false, // bInheritHandles, - 0, // creation flags - nil, // environment, - nil, // current directory: inherit from parent if nil - &si, - pi, - )) - if ok { - if wait { - WaitForSingleObject(pi.hProcess, INFINITE) - CloseHandle(pi.hProcess) - CloseHandle(pi.hThread) - } - return true - } else { - return false - } + 0, // creation flags + nil, // environment, + nil, // current directory: inherit from parent if nil + &si, + pi, + )) + if ok { + if wait { + WaitForSingleObject(pi.hProcess, INFINITE) + CloseHandle(pi.hProcess) + CloseHandle(pi.hThread) + } + return true + } else { + return false + } } diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin index 9edd56acc..17f4d8e86 100644 --- a/core/sys/windows/winmm.odin +++ b/core/sys/windows/winmm.odin @@ -6,5 +6,5 @@ foreign import winmm "system:Winmm.lib" @(default_calling_convention="stdcall") foreign winmm { timeBeginPeriod :: proc(uPeriod: UINT) -> MMRESULT --- - timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT --- + timeEndPeriod :: proc(uPeriod: UINT) -> MMRESULT --- } From 6c14586fff6361096edfb54582a6a96f8c50a23b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 14:27:46 +0100 Subject: [PATCH 045/254] Add `GetAddrInfoExW` --- core/sys/windows/types.odin | 27 +++++++++++++++++++++++++++ core/sys/windows/ws2_32.odin | 13 +++++++++++++ 2 files changed, 40 insertions(+) diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index b497f181e..c64207e92 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -1566,6 +1566,33 @@ ADDRINFOA :: struct { ai_next: ^ADDRINFOA, } +PADDRINFOEXW :: ^ADDRINFOEXW +LPADDRINFOEXW :: ^ADDRINFOEXW +ADDRINFOEXW :: struct { + ai_flags: c_int, + ai_family: c_int, + ai_socktype: c_int, + ai_protocol: c_int, + ai_addrlen: size_t, + ai_canonname: wstring, + ai_addr: ^sockaddr, + ai_blob: rawptr, + ai_bloblen: size_t, + ai_provider: LPGUID, + ai_next: ^ADDRINFOEXW, +} + +LPLOOKUPSERVICE_COMPLETION_ROUTINE :: #type proc "stdcall" ( + dwErrorCode: DWORD, + dwNumberOfBytesTransfered: DWORD, + lpOverlapped: LPOVERLAPPED, +) + +sockaddr :: struct { + sa_family: USHORT, + sa_data: [14]byte, +} + sockaddr_in :: struct { sin_family: ADDRESS_FAMILY, sin_port: USHORT, diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index 0cff5c2da..09af86bce 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -87,6 +87,19 @@ foreign ws2_32 { res: ^^ADDRINFOA, ) -> c_int --- freeaddrinfo :: proc(res: ^ADDRINFOA) --- + FreeAddrInfoExW :: proc(pAddrInfoEx: PADDRINFOEXW) --- + GetAddrInfoExW :: proc( + pName: PCWSTR, + pServiceName: PCWSTR, + dwNameSpace: DWORD, + lpNspId: LPGUID, + hints: ^ADDRINFOEXW, + ppResult: ^PADDRINFOEXW, + timeout: ^timeval, + lpOverlapped: LPOVERLAPPED, + lpCompletionRoutine: LPLOOKUPSERVICE_COMPLETION_ROUTINE, + lpHandle: LPHANDLE) -> INT --- + select :: proc( nfds: c_int, readfds: ^fd_set, From 97739da85a9812e37d1c349eb36f899b157dc8f5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 14:33:03 +0100 Subject: [PATCH 046/254] Remove `core:sys/win32` --- core/sys/win32/comdlg32.odin | 194 ----- core/sys/win32/crt.odin | 15 - core/sys/win32/gdi32.odin | 26 - core/sys/win32/general.odin | 1178 ----------------------------- core/sys/win32/helpers.odin | 29 - core/sys/win32/kernel32.odin | 237 ------ core/sys/win32/ole32.odin | 18 - core/sys/win32/removal.odin | 3 + core/sys/win32/shell32.odin | 9 - core/sys/win32/tests/general.odin | 41 - core/sys/win32/user32.odin | 289 ------- core/sys/win32/wgl.odin | 114 --- core/sys/win32/winmm.odin | 12 - 13 files changed, 3 insertions(+), 2162 deletions(-) delete mode 100644 core/sys/win32/comdlg32.odin delete mode 100644 core/sys/win32/crt.odin delete mode 100644 core/sys/win32/gdi32.odin delete mode 100644 core/sys/win32/general.odin delete mode 100644 core/sys/win32/helpers.odin delete mode 100644 core/sys/win32/kernel32.odin delete mode 100644 core/sys/win32/ole32.odin create mode 100644 core/sys/win32/removal.odin delete mode 100644 core/sys/win32/shell32.odin delete mode 100644 core/sys/win32/tests/general.odin delete mode 100644 core/sys/win32/user32.odin delete mode 100644 core/sys/win32/wgl.odin delete mode 100644 core/sys/win32/winmm.odin diff --git a/core/sys/win32/comdlg32.odin b/core/sys/win32/comdlg32.odin deleted file mode 100644 index 815def7b6..000000000 --- a/core/sys/win32/comdlg32.odin +++ /dev/null @@ -1,194 +0,0 @@ -// +build windows -package win32 - -foreign import "system:comdlg32.lib" -import "core:strings" - -OFN_Hook_Proc :: #type proc "stdcall" (hdlg: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Uint_Ptr - -Open_File_Name_A :: struct { - struct_size: u32, - hwnd_owner: Hwnd, - instance: Hinstance, - filter: cstring, - custom_filter: cstring, - max_cust_filter: u32, - filter_index: u32, - file: cstring, - max_file: u32, - file_title: cstring, - max_file_title: u32, - initial_dir: cstring, - title: cstring, - flags: u32, - file_offset: u16, - file_extension: u16, - def_ext: cstring, - cust_data: Lparam, - hook: OFN_Hook_Proc, - template_name: cstring, - pv_reserved: rawptr, - dw_reserved: u32, - flags_ex: u32, -} - -Open_File_Name_W :: struct { - struct_size: u32, - hwnd_owner: Hwnd, - instance: Hinstance, - filter: Wstring, - custom_filter: Wstring, - max_cust_filter: u32, - filter_index: u32, - file: Wstring, - max_file: u32, - file_title: Wstring, - max_file_title: u32, - initial_dir: Wstring, - title: Wstring, - flags: u32, - file_offset: u16, - file_extension: u16, - def_ext: Wstring, - cust_data: Lparam, - hook: OFN_Hook_Proc, - template_name: Wstring, - pv_reserved: rawptr, - dw_reserved: u32, - flags_ex: u32, -} - -@(default_calling_convention = "c") -foreign comdlg32 { - @(link_name="GetOpenFileNameA") get_open_file_name_a :: proc(arg1: ^Open_File_Name_A) -> Bool --- - @(link_name="GetOpenFileNameW") get_open_file_name_w :: proc(arg1: ^Open_File_Name_W) -> Bool --- - @(link_name="GetSaveFileNameA") get_save_file_name_a :: proc(arg1: ^Open_File_Name_A) -> Bool --- - @(link_name="GetSaveFileNameW") get_save_file_name_w :: proc(arg1: ^Open_File_Name_W) -> Bool --- - @(link_name="CommDlgExtendedError") comm_dlg_extended_error :: proc() -> u32 --- -} - -OPEN_TITLE :: "Select file to open" -OPEN_FLAGS :: u32(OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST) -OPEN_FLAGS_MULTI :: OPEN_FLAGS | OFN_ALLOWMULTISELECT | OFN_EXPLORER - -SAVE_TITLE :: "Select file to save" -SAVE_FLAGS :: u32(OFN_OVERWRITEPROMPT | OFN_EXPLORER) -SAVE_EXT :: "txt" - -Open_Save_Mode :: enum { - Open = 0, - Save = 1, -} - -_open_file_dialog :: proc(title: string, dir: string, - filters: []string, default_filter: u32, - flags: u32, default_ext: string, - mode: Open_Save_Mode, allocator := context.temp_allocator) -> (path: string, ok: bool = true) { - context.allocator = allocator - file_buf := make([]u16, MAX_PATH_WIDE) - defer if !ok { - delete(file_buf) - } - - // Filters need to be passed as a pair of strings (title, filter) - filter_len := u32(len(filters)) - if filter_len % 2 != 0 { - return "", false - } - - filter: string - filter = strings.join(filters, "\u0000", context.temp_allocator) - filter = strings.concatenate({filter, "\u0000"}, context.temp_allocator) - - ofn := Open_File_Name_W{ - struct_size = size_of(Open_File_Name_W), - file = Wstring(&file_buf[0]), - max_file = MAX_PATH_WIDE, - title = utf8_to_wstring(title, context.temp_allocator), - filter = utf8_to_wstring(filter, context.temp_allocator), - initial_dir = utf8_to_wstring(dir, context.temp_allocator), - filter_index = u32(clamp(default_filter, 1, filter_len / 2)), - def_ext = utf8_to_wstring(default_ext, context.temp_allocator), - flags = u32(flags), - } - - switch mode { - case .Open: - ok = bool(get_open_file_name_w(&ofn)) - case .Save: - ok = bool(get_save_file_name_w(&ofn)) - case: - ok = false - } - - if !ok { - return - } - - - file_name, _ := utf16_to_utf8(file_buf[:], allocator) - path = strings.trim_right_null(file_name) - return -} - -select_file_to_open :: proc(title := OPEN_TITLE, dir := ".", - filters := []string{"All Files", "*.*"}, default_filter := u32(1), - flags := OPEN_FLAGS, allocator := context.temp_allocator) -> (path: string, ok: bool) { - - path, ok = _open_file_dialog(title, dir, filters, default_filter, flags, "", Open_Save_Mode.Open, allocator) - return -} - -select_file_to_save :: proc(title := SAVE_TITLE, dir := ".", - filters := []string{"All Files", "*.*"}, default_filter := u32(1), - flags := SAVE_FLAGS, default_ext := SAVE_EXT, - allocator := context.temp_allocator) -> (path: string, ok: bool) { - - path, ok = _open_file_dialog(title, dir, filters, default_filter, flags, default_ext, Open_Save_Mode.Save, allocator) - return -} - -// TODO: Implement convenience function for select_file_to_open with ALLOW_MULTI_SELECT that takes -// it output of the form "path\u0000\file1u\0000file2" and turns it into []string with the path + file pre-concatenated for you. - -OFN_ALLOWMULTISELECT :: 0x00000200 // NOTE(Jeroen): Without OFN_EXPLORER it uses the Win3 dialog. -OFN_CREATEPROMPT :: 0x00002000 -OFN_DONTADDTORECENT :: 0x02000000 -OFN_ENABLEHOOK :: 0x00000020 -OFN_ENABLEINCLUDENOTIFY :: 0x00400000 -OFN_ENABLESIZING :: 0x00800000 -OFN_ENABLETEMPLATE :: 0x00000040 -OFN_ENABLETEMPLATEHANDLE :: 0x00000080 -OFN_EXPLORER :: 0x00080000 -OFN_EXTENSIONDIFFERENT :: 0x00000400 -OFN_FILEMUSTEXIST :: 0x00001000 -OFN_FORCESHOWHIDDEN :: 0x10000000 -OFN_HIDEREADONLY :: 0x00000004 -OFN_LONGNAMES :: 0x00200000 -OFN_NOCHANGEDIR :: 0x00000008 -OFN_NODEREFERENCELINKS :: 0x00100000 -OFN_NOLONGNAMES :: 0x00040000 -OFN_NONETWORKBUTTON :: 0x00020000 -OFN_NOREADONLYRETURN :: 0x00008000 -OFN_NOTESTFILECREATE :: 0x00010000 -OFN_NOVALIDATE :: 0x00000100 -OFN_OVERWRITEPROMPT :: 0x00000002 -OFN_PATHMUSTEXIST :: 0x00000800 -OFN_READONLY :: 0x00000001 -OFN_SHAREAWARE :: 0x00004000 -OFN_SHOWHELP :: 0x00000010 - -CDERR_DIALOGFAILURE :: 0x0000FFFF -CDERR_GENERALCODES :: 0x00000000 -CDERR_STRUCTSIZE :: 0x00000001 -CDERR_INITIALIZATION :: 0x00000002 -CDERR_NOTEMPLATE :: 0x00000003 -CDERR_NOHINSTANCE :: 0x00000004 -CDERR_LOADSTRFAILURE :: 0x00000005 -CDERR_FINDRESFAILURE :: 0x00000006 -CDERR_LOADRESFAILURE :: 0x00000007 -CDERR_LOCKRESFAILURE :: 0x00000008 -CDERR_MEMALLOCFAILURE :: 0x00000009 -CDERR_MEMLOCKFAILURE :: 0x0000000A -CDERR_NOHOOK :: 0x0000000B -CDERR_REGISTERMSGFAIL :: 0x0000000C diff --git a/core/sys/win32/crt.odin b/core/sys/win32/crt.odin deleted file mode 100644 index 8584a27be..000000000 --- a/core/sys/win32/crt.odin +++ /dev/null @@ -1,15 +0,0 @@ -// +build windows -package win32 - -import "core:strings" - -foreign { - @(link_name="_wgetcwd") _get_cwd_wide :: proc(buffer: Wstring, buf_len: int) -> ^Wstring --- -} - -get_cwd :: proc(allocator := context.temp_allocator) -> string { - buffer := make([]u16, MAX_PATH_WIDE, allocator) - _get_cwd_wide(Wstring(&buffer[0]), MAX_PATH_WIDE) - file, _ := utf16_to_utf8(buffer[:], allocator) - return strings.trim_right_null(file) -} diff --git a/core/sys/win32/gdi32.odin b/core/sys/win32/gdi32.odin deleted file mode 100644 index 13bc4796f..000000000 --- a/core/sys/win32/gdi32.odin +++ /dev/null @@ -1,26 +0,0 @@ -// +build windows -package win32 - -foreign import "system:gdi32.lib" - -WHITENESS :: 0x00FF0062 -BLACKNESS :: 0x00000042 - -@(default_calling_convention = "std") -foreign gdi32 { - @(link_name="GetStockObject") get_stock_object :: proc(fn_object: i32) -> Hgdiobj --- - - @(link_name="StretchDIBits") - stretch_dibits :: proc(hdc: Hdc, - x_dst, y_dst, width_dst, height_dst: i32, - x_src, y_src, width_src, header_src: i32, - bits: rawptr, bits_info: ^Bitmap_Info, - usage: u32, - rop: u32) -> i32 --- - - @(link_name="SetPixelFormat") set_pixel_format :: proc(hdc: Hdc, pixel_format: i32, pfd: ^Pixel_Format_Descriptor) -> Bool --- - @(link_name="ChoosePixelFormat") choose_pixel_format :: proc(hdc: Hdc, pfd: ^Pixel_Format_Descriptor) -> i32 --- - @(link_name="SwapBuffers") swap_buffers :: proc(hdc: Hdc) -> Bool --- - - @(link_name="PatBlt") pat_blt :: proc(hdc: Hdc, x, y, w, h: i32, rop: u32) -> Bool --- -} diff --git a/core/sys/win32/general.odin b/core/sys/win32/general.odin deleted file mode 100644 index 64ee952ce..000000000 --- a/core/sys/win32/general.odin +++ /dev/null @@ -1,1178 +0,0 @@ -// +build windows -package win32 - -import "core:runtime" - -Uint_Ptr :: distinct uintptr -Int_Ptr :: distinct int -Long_Ptr :: distinct int - -Handle :: distinct rawptr -Hwnd :: distinct Handle -Hdc :: distinct Handle -Hinstance :: distinct Handle -Hicon :: distinct Handle -Hcursor :: distinct Handle -Hmenu :: distinct Handle -Hbitmap :: distinct Handle -Hbrush :: distinct Handle -Hgdiobj :: distinct Handle -Hmodule :: distinct Handle -Hmonitor :: distinct Handle -Hrawinput :: distinct Handle -Hresult :: distinct i32 -HKL :: distinct Handle -Wparam :: distinct Uint_Ptr -Lparam :: distinct Long_Ptr -Lresult :: distinct Long_Ptr -Wnd_Proc :: distinct #type proc "std" (Hwnd, u32, Wparam, Lparam) -> Lresult -Monitor_Enum_Proc :: distinct #type proc "std" (Hmonitor, Hdc, ^Rect, Lparam) -> bool - - - -Bool :: distinct b32 - -Wstring :: distinct [^]u16 - -Point :: struct { - x, y: i32, -} - -Wnd_Class_A :: struct { - style: u32, - wnd_proc: Wnd_Proc, - cls_extra, wnd_extra: i32, - instance: Hinstance, - icon: Hicon, - cursor: Hcursor, - background: Hbrush, - menu_name, class_name: cstring, -} - -Wnd_Class_W :: struct { - style: u32, - wnd_proc: Wnd_Proc, - cls_extra, wnd_extra: i32, - instance: Hinstance, - icon: Hicon, - cursor: Hcursor, - background: Hbrush, - menu_name, class_name: Wstring, -} - -Wnd_Class_Ex_A :: struct { - size, style: u32, - wnd_proc: Wnd_Proc, - cls_extra, wnd_extra: i32, - instance: Hinstance, - icon: Hicon, - cursor: Hcursor, - background: Hbrush, - menu_name, class_name: cstring, - sm: Hicon, -} - -Wnd_Class_Ex_W :: struct { - size, style: u32, - wnd_proc: Wnd_Proc, - cls_extra, wnd_extra: i32, - instance: Hinstance, - icon: Hicon, - cursor: Hcursor, - background: Hbrush, - menu_name, class_name: Wstring, - sm: Hicon, -} - - -Msg :: struct { - hwnd: Hwnd, - message: u32, - wparam: Wparam, - lparam: Lparam, - time: u32, - pt: Point, -} - -Rect :: struct { - left: i32, - top: i32, - right: i32, - bottom: i32, -} - -Dev_Mode_A :: struct { - device_name: [32]u8, - spec_version: u16, - driver_version: u16, - size: u16, - driver_extra: u16, - fields: u32, - using _: struct #raw_union { - // Printer only fields. - using _: struct { - orientation: i16, - paper_size: i16, - paper_length: i16, - paper_width: i16, - scale: i16, - copies: i16, - default_source: i16, - print_quality: i16, - }, - // Display only fields. - using _: struct { - position: Point, - display_orientation: u32, - display_fixed_output: u32, - }, - }, - color: i16, - duplex: i16, - y_resolution: i16, - tt_option: i16, - collate: i16, - form_name: [32]u8, - log_pixels: u16, - bits_per_pel: u32, - pels_width: u32, - pels_height: u32, - using _: struct #raw_union { - display_flags: u32, - nup: u32, - }, - display_frequency: u32, - icm_method: u32, - icm_intent: u32, - media_type: u32, - dither_type: u32, - reserved_1: u32, - reserved_2: u32, - panning_width: u32, - panning_height: u32, -} - -Filetime :: struct { - lo, hi: u32, -} - -Systemtime :: struct { - year, month: u16, - day_of_week, day: u16, - hour, minute, second, millisecond: u16, -} - -By_Handle_File_Information :: struct { - file_attributes: u32, - creation_time, - last_access_time, - last_write_time: Filetime, - volume_serial_number, - file_size_high, - file_size_low, - number_of_links, - file_index_high, - file_index_low: u32, -} - -File_Attribute_Data :: struct { - file_attributes: u32, - creation_time, - last_access_time, - last_write_time: Filetime, - file_size_high, - file_size_low: u32, -} - -// NOTE(Jeroen): The widechar version might want at least the 32k MAX_PATH_WIDE -// https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-findfirstfilew#parameters -Find_Data_W :: struct{ - file_attributes: u32, - creation_time: Filetime, - last_access_time: Filetime, - last_write_time: Filetime, - file_size_high: u32, - file_size_low: u32, - reserved0: u32, - reserved1: u32, - file_name: [MAX_PATH]u16, - alternate_file_name: [14]u16, -} - -Find_Data_A :: struct{ - file_attributes: u32, - creation_time: Filetime, - last_access_time: Filetime, - last_write_time: Filetime, - file_size_high: u32, - file_size_low: u32, - reserved0: u32, - reserved1: u32, - file_name: [MAX_PATH]byte, - alternate_file_name: [14]byte, -} - -Security_Attributes :: struct { - length: u32, - security_descriptor: rawptr, - inherit_handle: Bool, -} - -Process_Information :: struct { - process: Handle, - thread: Handle, - process_id: u32, - thread_id: u32, -} - -Startup_Info :: struct { - cb: u32, - reserved: Wstring, - desktop: Wstring, - title: Wstring, - x: u32, - y: u32, - x_size: u32, - y_size: u32, - x_count_chars: u32, - y_count_chars: u32, - fill_attribute: u32, - flags: u32, - show_window: u16, - _: u16, - _: cstring, - stdin: Handle, - stdout: Handle, - stderr: Handle, -} - -Pixel_Format_Descriptor :: struct { - size, - version, - flags: u32, - - pixel_type, - color_bits, - red_bits, - red_shift, - green_bits, - green_shift, - blue_bits, - blue_shift, - alpha_bits, - alpha_shift, - accum_bits, - accum_red_bits, - accum_green_bits, - accum_blue_bits, - accum_alpha_bits, - depth_bits, - stencil_bits, - aux_buffers, - layer_type, - reserved: byte, - - layer_mask, - visible_mask, - damage_mask: u32, -} - -Critical_Section :: struct { - debug_info: ^Critical_Section_Debug, - - lock_count: i32, - recursion_count: i32, - owning_thread: Handle, - lock_semaphore: Handle, - spin_count: ^u32, -} - -Critical_Section_Debug :: struct { - typ: u16, - creator_back_trace_index: u16, - critical_section: ^Critical_Section, - process_locks_list: ^List_Entry, - entry_count: u32, - contention_count: u32, - flags: u32, - creator_back_trace_index_high: u16, - spare_word: u16, -} - -List_Entry :: struct {flink, blink: ^List_Entry} - - -Raw_Input_Device :: struct { - usage_page: u16, - usage: u16, - flags: u32, - wnd_target: Hwnd, -} - -Raw_Input_Header :: struct { - kind: u32, - size: u32, - device: Handle, - wparam: Wparam, -} - -Raw_HID :: struct { - size_hid: u32, - count: u32, - raw_data: [1]byte, -} - -Raw_Keyboard :: struct { - make_code: u16, - flags: u16, - reserved: u16, - vkey: u16, - message: u32, - extra_information: u32, -} - -Raw_Mouse :: struct { - flags: u16, - using data: struct #raw_union { - buttons: u32, - using _: struct { - button_flags: u16, - button_data: u16, - }, - }, - raw_buttons: u32, - last_x: i32, - last_y: i32, - extra_information: u32, -} - -Raw_Input :: struct { - using header: Raw_Input_Header, - data: struct #raw_union { - mouse: Raw_Mouse, - keyboard: Raw_Keyboard, - hid: Raw_HID, - }, -} - - -Overlapped :: struct { - internal: ^u64, - internal_high: ^u64, - using _: struct #raw_union { - using _: struct { - offset: u32, - offset_high: u32, - }, - pointer: rawptr, - }, - event: Handle, -} - -File_Notify_Information :: struct { - next_entry_offset: u32, - action: u32, - file_name_length: u32, - file_name: [1]u16, -} - -// https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info -System_Info :: struct { - using _: struct #raw_union { - oem_id: u32, - using _: struct #raw_union { - processor_architecture: u16, - _: u16, // reserved - }, - }, - page_size: u32, - minimum_application_address: rawptr, - maximum_application_address: rawptr, - active_processor_mask: u32, - number_of_processors: u32, - processor_type: u32, - allocation_granularity: u32, - processor_level: u16, - processor_revision: u16, -} - -// https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoexa -OS_Version_Info_Ex_A :: struct { - os_version_info_size: u32, - major_version: u32, - minor_version: u32, - build_number: u32, - platform_id : u32, - service_pack_string: [128]u8, - service_pack_major: u16, - service_pack_minor: u16, - suite_mask: u16, - product_type: u8, - reserved: u8, -} - -MAPVK_VK_TO_VSC :: 0 -MAPVK_VSC_TO_VK :: 1 -MAPVK_VK_TO_CHAR :: 2 -MAPVK_VSC_TO_VK_EX :: 3 - -//WinUser.h -ENUM_CURRENT_SETTINGS :: u32(4294967295) // (DWORD)-1 -ENUM_REGISTRY_SETTINGS :: u32(4294967294) // (DWORD)-2 - -VK_LBUTTON :: 0x01 -VK_RBUTTON :: 0x02 -VK_CANCEL :: 0x03 -VK_MBUTTON :: 0x04 /* NOT contiguous with L & RBUTTON */ -VK_XBUTTON1 :: 0x05 /* NOT contiguous with L & RBUTTON */ -VK_XBUTTON2 :: 0x06 /* NOT contiguous with L & RBUTTON */ - -/* - * :: 0x07 : reserved - */ - -VK_BACK :: 0x08 -VK_TAB :: 0x09 - -/* - * :: 0x0A - :: 0x0B : reserved - */ - -VK_CLEAR :: 0x0C -VK_RETURN :: 0x0D - -/* - * :: 0x0E - :: 0x0F : unassigned - */ - -VK_SHIFT :: 0x10 -VK_CONTROL :: 0x11 -VK_MENU :: 0x12 -VK_PAUSE :: 0x13 -VK_CAPITAL :: 0x14 - -VK_KANA :: 0x15 -VK_HANGEUL :: 0x15 /* old name - should be here for compatibility */ -VK_HANGUL :: 0x15 - -/* - * :: 0x16 : unassigned - */ - -VK_JUNJA :: 0x17 -VK_FINAL :: 0x18 -VK_HANJA :: 0x19 -VK_KANJI :: 0x19 - -/* - * :: 0x1A : unassigned - */ - -VK_ESCAPE :: 0x1B - -VK_CONVERT :: 0x1C -VK_NONCONVERT :: 0x1D -VK_ACCEPT :: 0x1E -VK_MODECHANGE :: 0x1F - -VK_SPACE :: 0x20 -VK_PRIOR :: 0x21 -VK_NEXT :: 0x22 -VK_END :: 0x23 -VK_HOME :: 0x24 -VK_LEFT :: 0x25 -VK_UP :: 0x26 -VK_RIGHT :: 0x27 -VK_DOWN :: 0x28 -VK_SELECT :: 0x29 -VK_PRINT :: 0x2A -VK_EXECUTE :: 0x2B -VK_SNAPSHOT :: 0x2C -VK_INSERT :: 0x2D -VK_DELETE :: 0x2E -VK_HELP :: 0x2F - -/* - * VK_0 - VK_9 are the same as ASCII '0' - '9' (:: 0x30 - :: 0x39) - * :: 0x3A - :: 0x40 : unassigned - * VK_A - VK_Z are the same as ASCII 'A' - 'Z' (:: 0x41 - :: 0x5A) - */ - -VK_LWIN :: 0x5B -VK_RWIN :: 0x5C -VK_APPS :: 0x5D - -/* - * :: 0x5E : reserved - */ - -VK_SLEEP :: 0x5F - -VK_NUMPAD0 :: 0x60 -VK_NUMPAD1 :: 0x61 -VK_NUMPAD2 :: 0x62 -VK_NUMPAD3 :: 0x63 -VK_NUMPAD4 :: 0x64 -VK_NUMPAD5 :: 0x65 -VK_NUMPAD6 :: 0x66 -VK_NUMPAD7 :: 0x67 -VK_NUMPAD8 :: 0x68 -VK_NUMPAD9 :: 0x69 -VK_MULTIPLY :: 0x6A -VK_ADD :: 0x6B -VK_SEPARATOR :: 0x6C -VK_SUBTRACT :: 0x6D -VK_DECIMAL :: 0x6E -VK_DIVIDE :: 0x6F -VK_F1 :: 0x70 -VK_F2 :: 0x71 -VK_F3 :: 0x72 -VK_F4 :: 0x73 -VK_F5 :: 0x74 -VK_F6 :: 0x75 -VK_F7 :: 0x76 -VK_F8 :: 0x77 -VK_F9 :: 0x78 -VK_F10 :: 0x79 -VK_F11 :: 0x7A -VK_F12 :: 0x7B -VK_F13 :: 0x7C -VK_F14 :: 0x7D -VK_F15 :: 0x7E -VK_F16 :: 0x7F -VK_F17 :: 0x80 -VK_F18 :: 0x81 -VK_F19 :: 0x82 -VK_F20 :: 0x83 -VK_F21 :: 0x84 -VK_F22 :: 0x85 -VK_F23 :: 0x86 -VK_F24 :: 0x87 - -INVALID_HANDLE :: Handle(~uintptr(0)) - -CREATE_SUSPENDED :: 0x00000004 -STACK_SIZE_PARAM_IS_A_RESERVATION :: 0x00010000 -WAIT_ABANDONED :: 0x00000080 -WAIT_OBJECT_0 :: 0 -WAIT_TIMEOUT :: 0x00000102 -WAIT_FAILED :: 0xffffffff - -CS_VREDRAW :: 0x0001 -CS_HREDRAW :: 0x0002 -CS_OWNDC :: 0x0020 -CW_USEDEFAULT :: -0x80000000 - -WS_OVERLAPPED :: 0 -WS_MAXIMIZEBOX :: 0x00010000 -WS_MINIMIZEBOX :: 0x00020000 -WS_THICKFRAME :: 0x00040000 -WS_SYSMENU :: 0x00080000 -WS_BORDER :: 0x00800000 -WS_CAPTION :: 0x00C00000 -WS_VISIBLE :: 0x10000000 -WS_POPUP :: 0x80000000 -WS_MAXIMIZE :: 0x01000000 -WS_MINIMIZE :: 0x20000000 -WS_OVERLAPPEDWINDOW :: WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_THICKFRAME|WS_MINIMIZEBOX|WS_MAXIMIZEBOX -WS_POPUPWINDOW :: WS_POPUP | WS_BORDER | WS_SYSMENU - -WS_EX_DLGMODALFRAME :: 0x00000001 -WS_EX_NOPARENTNOTIFY :: 0x00000004 -WS_EX_TOPMOST :: 0x00000008 -WS_EX_ACCEPTFILES :: 0x00000010 -WS_EX_TRANSPARENT :: 0x00000020 -WS_EX_MDICHILD :: 0x00000040 -WS_EX_TOOLWINDOW :: 0x00000080 -WS_EX_WINDOWEDGE :: 0x00000100 -WS_EX_CLIENTEDGE :: 0x00000200 -WS_EX_CONTEXTHELP :: 0x00000400 -WS_EX_RIGHT :: 0x00001000 -WS_EX_LEFT :: 0x00000000 -WS_EX_RTLREADING :: 0x00002000 -WS_EX_LTRREADING :: 0x00000000 -WS_EX_LEFTSCROLLBAR :: 0x00004000 -WS_EX_RIGHTSCROLLBAR :: 0x00000000 -WS_EX_CONTROLPARENT :: 0x00010000 -WS_EX_STATICEDGE :: 0x00020000 -WS_EX_APPWINDOW :: 0x00040000 -WS_EX_OVERLAPPEDWINDOW :: WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE -WS_EX_PALETTEWINDOW :: WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST -WS_EX_LAYERED :: 0x00080000 -WS_EX_NOINHERITLAYOUT :: 0x00100000 // Disable inheritence of mirroring by children -WS_EX_NOREDIRECTIONBITMAP :: 0x00200000 -WS_EX_LAYOUTRTL :: 0x00400000 // Right to left mirroring -WS_EX_COMPOSITED :: 0x02000000 -WS_EX_NOACTIVATE :: 0x08000000 - -WM_ACTIVATE :: 0x0006 -WM_ACTIVATEAPP :: 0x001C -WM_CHAR :: 0x0102 -WM_CLOSE :: 0x0010 -WM_CREATE :: 0x0001 -WM_DESTROY :: 0x0002 -WM_INPUT :: 0x00FF -WM_KEYDOWN :: 0x0100 -WM_KEYUP :: 0x0101 -WM_KILLFOCUS :: 0x0008 -WM_QUIT :: 0x0012 -WM_SETCURSOR :: 0x0020 -WM_SETFOCUS :: 0x0007 -WM_SIZE :: 0x0005 -WM_SIZING :: 0x0214 -WM_SYSKEYDOWN :: 0x0104 -WM_SYSKEYUP :: 0x0105 -WM_USER :: 0x0400 -WM_WINDOWPOSCHANGED :: 0x0047 -WM_COMMAND :: 0x0111 -WM_PAINT :: 0x000F - -WM_MOUSEWHEEL :: 0x020A -WM_MOUSEMOVE :: 0x0200 -WM_LBUTTONDOWN :: 0x0201 -WM_LBUTTONUP :: 0x0202 -WM_LBUTTONDBLCLK :: 0x0203 -WM_RBUTTONDOWN :: 0x0204 -WM_RBUTTONUP :: 0x0205 -WM_RBUTTONDBLCLK :: 0x0206 -WM_MBUTTONDOWN :: 0x0207 -WM_MBUTTONUP :: 0x0208 -WM_MBUTTONDBLCLK :: 0x0209 - -PM_NOREMOVE :: 0x0000 -PM_REMOVE :: 0x0001 -PM_NOYIELD :: 0x0002 - -BLACK_BRUSH :: 4 - -SM_CXSCREEN :: 0 -SM_CYSCREEN :: 1 - -SW_SHOW :: 5 - -COLOR_BACKGROUND :: Hbrush(uintptr(1)) - -INVALID_SET_FILE_POINTER :: ~u32(0) -HEAP_ZERO_MEMORY :: 0x00000008 -INFINITE :: 0xffffffff -GWL_EXSTYLE :: -20 -GWLP_HINSTANCE :: -6 -GWLP_ID :: -12 -GWL_STYLE :: -16 -GWLP_USERDATA :: -21 -GWLP_WNDPROC :: -4 -Hwnd_TOP :: Hwnd(uintptr(0)) - -BI_RGB :: 0 -DIB_RGB_COLORS :: 0x00 -SRCCOPY: u32 : 0x00cc0020 - - -MONITOR_DEFAULTTONULL :: 0x00000000 -MONITOR_DEFAULTTOPRIMARY :: 0x00000001 -MONITOR_DEFAULTTONEAREST :: 0x00000002 - -SWP_FRAMECHANGED :: 0x0020 -SWP_NOOWNERZORDER :: 0x0200 -SWP_NOZORDER :: 0x0004 -SWP_NOSIZE :: 0x0001 -SWP_NOMOVE :: 0x0002 - - -// Raw Input - - -RID_HEADER :: 0x10000005 -RID_INPUT :: 0x10000003 - - -RIDEV_APPKEYS :: 0x00000400 -RIDEV_CAPTUREMOUSE :: 0x00000200 -RIDEV_DEVNOTIFY :: 0x00002000 -RIDEV_EXCLUDE :: 0x00000010 -RIDEV_EXINPUTSINK :: 0x00001000 -RIDEV_INPUTSINK :: 0x00000100 -RIDEV_NOHOTKEYS :: 0x00000200 -RIDEV_NOLEGACY :: 0x00000030 -RIDEV_PAGEONLY :: 0x00000020 -RIDEV_REMOVE :: 0x00000001 - - -RIM_TYPEMOUSE :: 0 -RIM_TYPEKEYBOARD :: 1 -RIM_TYPEHID :: 2 - - -MOUSE_ATTRIBUTES_CHANGED :: 0x04 -MOUSE_MOVE_RELATIVE :: 0 -MOUSE_MOVE_ABSOLUTE :: 1 -MOUSE_VIRTUAL_DESKTOP :: 0x02 - - - -RI_MOUSE_BUTTON_1_DOWN :: 0x0001 -RI_MOUSE_BUTTON_1_UP :: 0x0002 -RI_MOUSE_BUTTON_2_DOWN :: 0x0004 -RI_MOUSE_BUTTON_2_UP :: 0x0008 -RI_MOUSE_BUTTON_3_DOWN :: 0x0010 -RI_MOUSE_BUTTON_3_UP :: 0x0020 -RI_MOUSE_BUTTON_4_DOWN :: 0x0040 -RI_MOUSE_BUTTON_4_UP :: 0x0080 -RI_MOUSE_BUTTON_5_DOWN :: 0x0100 -RI_MOUSE_BUTTON_5_UP :: 0x0200 -RI_MOUSE_LEFT_BUTTON_DOWN :: 0x0001 -RI_MOUSE_LEFT_BUTTON_UP :: 0x0002 -RI_MOUSE_MIDDLE_BUTTON_DOWN :: 0x0010 -RI_MOUSE_MIDDLE_BUTTON_UP :: 0x0020 -RI_MOUSE_RIGHT_BUTTON_DOWN :: 0x0004 -RI_MOUSE_RIGHT_BUTTON_UP :: 0x0008 -RI_MOUSE_WHEEL :: 0x0400 - - -RI_KEY_MAKE :: 0x00 -RI_KEY_BREAK :: 0x01 -RI_KEY_E0 :: 0x02 -RI_KEY_E1 :: 0x04 -RI_KEY_TERMSRV_SET_LED :: 0x08 -RI_KEY_TERMSRV_SHADOW :: 0x10 - -// Windows OpenGL - -PFD_TYPE_RGBA :: 0 -PFD_TYPE_COLORINDEX :: 1 -PFD_MAIN_PLANE :: 0 -PFD_OVERLAY_PLANE :: 1 -PFD_UNDERLAY_PLANE :: -1 -PFD_DOUBLEBUFFER :: 1 -PFD_STEREO :: 2 -PFD_DRAW_TO_WINDOW :: 4 -PFD_DRAW_TO_BITMAP :: 8 -PFD_SUPPORT_GDI :: 16 -PFD_SUPPORT_OPENGL :: 32 -PFD_GENERIC_FORMAT :: 64 -PFD_NEED_PALETTE :: 128 -PFD_NEED_SYSTEM_PALETTE :: 0x00000100 -PFD_SWAP_EXCHANGE :: 0x00000200 -PFD_SWAP_COPY :: 0x00000400 -PFD_SWAP_LAYER_BUFFERS :: 0x00000800 -PFD_GENERIC_ACCELERATED :: 0x00001000 -PFD_DEPTH_DONTCARE :: 0x20000000 -PFD_DOUBLEBUFFER_DONTCARE :: 0x40000000 -PFD_STEREO_DONTCARE :: 0x80000000 - -GET_FILEEX_INFO_LEVELS :: distinct i32 -GetFileExInfoStandard: GET_FILEEX_INFO_LEVELS : 0 -GetFileExMaxInfoLevel: GET_FILEEX_INFO_LEVELS : 1 - -STARTF_USESHOWWINDOW :: 0x00000001 -STARTF_USESIZE :: 0x00000002 -STARTF_USEPOSITION :: 0x00000004 -STARTF_USECOUNTCHARS :: 0x00000008 -STARTF_USEFILLATTRIBUTE :: 0x00000010 -STARTF_RUNFULLSCREEN :: 0x00000020 // ignored for non-x86 platforms -STARTF_FORCEONFEEDBACK :: 0x00000040 -STARTF_FORCEOFFFEEDBACK :: 0x00000080 -STARTF_USESTDHANDLES :: 0x00000100 -STARTF_USEHOTKEY :: 0x00000200 -STARTF_TITLEISLINKNAME :: 0x00000800 -STARTF_TITLEISAPPID :: 0x00001000 -STARTF_PREVENTPINNING :: 0x00002000 -STARTF_UNTRUSTEDSOURCE :: 0x00008000 - - -MOVEFILE_REPLACE_EXISTING :: 0x00000001 -MOVEFILE_COPY_ALLOWED :: 0x00000002 -MOVEFILE_DELAY_UNTIL_REBOOT :: 0x00000004 -MOVEFILE_WRITE_THROUGH :: 0x00000008 -MOVEFILE_CREATE_HARDLINK :: 0x00000010 -MOVEFILE_FAIL_IF_NOT_TRACKABLE :: 0x00000020 - -FILE_NOTIFY_CHANGE_FILE_NAME :: 0x00000001 -FILE_NOTIFY_CHANGE_DIR_NAME :: 0x00000002 -FILE_NOTIFY_CHANGE_ATTRIBUTES :: 0x00000004 -FILE_NOTIFY_CHANGE_SIZE :: 0x00000008 -FILE_NOTIFY_CHANGE_LAST_WRITE :: 0x00000010 -FILE_NOTIFY_CHANGE_LAST_ACCESS :: 0x00000020 -FILE_NOTIFY_CHANGE_CREATION :: 0x00000040 -FILE_NOTIFY_CHANGE_SECURITY :: 0x00000100 - -FILE_FLAG_WRITE_THROUGH :: 0x80000000 -FILE_FLAG_OVERLAPPED :: 0x40000000 -FILE_FLAG_NO_BUFFERING :: 0x20000000 -FILE_FLAG_RANDOM_ACCESS :: 0x10000000 -FILE_FLAG_SEQUENTIAL_SCAN :: 0x08000000 -FILE_FLAG_DELETE_ON_CLOSE :: 0x04000000 -FILE_FLAG_BACKUP_SEMANTICS :: 0x02000000 -FILE_FLAG_POSIX_SEMANTICS :: 0x01000000 -FILE_FLAG_SESSION_AWARE :: 0x00800000 -FILE_FLAG_OPEN_REPARSE_POINT :: 0x00200000 -FILE_FLAG_OPEN_NO_RECALL :: 0x00100000 -FILE_FLAG_FIRST_PIPE_INSTANCE :: 0x00080000 - -FILE_ACTION_ADDED :: 0x00000001 -FILE_ACTION_REMOVED :: 0x00000002 -FILE_ACTION_MODIFIED :: 0x00000003 -FILE_ACTION_RENAMED_OLD_NAME :: 0x00000004 -FILE_ACTION_RENAMED_NEW_NAME :: 0x00000005 - -CP_ACP :: 0 // default to ANSI code page -CP_OEMCP :: 1 // default to OEM code page -CP_MACCP :: 2 // default to MAC code page -CP_THREAD_ACP :: 3 // current thread's ANSI code page -CP_SYMBOL :: 42 // SYMBOL translations -CP_UTF7 :: 65000 // UTF-7 translation -CP_UTF8 :: 65001 // UTF-8 translation - - -MB_ERR_INVALID_CHARS :: 8 -WC_ERR_INVALID_CHARS :: 128 - -utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { - if len(s) < 1 { - return nil - } - - b := transmute([]byte)s - cstr := cstring(&b[0]) - n := multi_byte_to_wide_char(CP_UTF8, MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0) - if n == 0 { - return nil - } - - text := make([]u16, n+1, allocator) - - n1 := multi_byte_to_wide_char(CP_UTF8, MB_ERR_INVALID_CHARS, cstr, i32(len(s)), Wstring(&text[0]), i32(n)) - if n1 == 0 { - delete(text, allocator) - return nil - } - - text[n] = 0 - for n >= 1 && text[n-1] == 0 { - n -= 1 - } - return text[:n] -} -utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> Wstring { - if res := utf8_to_utf16(s, allocator); res != nil { - return Wstring(&res[0]) - } - return nil -} - -wstring_to_utf8 :: proc(s: Wstring, N: int, allocator := context.temp_allocator) -> (str: string, err: runtime.Allocator_Error) { - if N == 0 { - return - } - - n := wide_char_to_multi_byte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), nil, 0, nil, nil) - if n == 0 { - return - } - - // If N == -1 the call to wide_char_to_multi_byte assume the wide string is null terminated - // and will scan it to find the first null terminated character. The resulting string will - // also null terminated. - // If N != -1 it assumes the wide string is not null terminated and the resulting string - // will not be null terminated, we therefore have to force it to be null terminated manually. - text := make([]byte, n+1 if N != -1 else n, allocator) or_return - - if n1 := wide_char_to_multi_byte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), cstring(&text[0]), n, nil, nil); n1 == 0 { - delete(text, allocator) - return "", nil - } - - for i in 0.. (string, runtime.Allocator_Error) { - if len(s) == 0 { - return "", nil - } - return wstring_to_utf8(cast(Wstring)&s[0], len(s), allocator) -} - -get_query_performance_frequency :: proc() -> i64 { - r: i64 - query_performance_frequency(&r) - return r -} - -HIWORD_W :: proc(wParam: Wparam) -> u16 { return u16((u32(wParam) >> 16) & 0xffff) } -HIWORD_L :: proc(lParam: Lparam) -> u16 { return u16((u32(lParam) >> 16) & 0xffff) } -LOWORD_W :: proc(wParam: Wparam) -> u16 { return u16(wParam) } -LOWORD_L :: proc(lParam: Lparam) -> u16 { return u16(lParam) } - -is_key_down :: #force_inline proc(key: Key_Code) -> bool { return get_async_key_state(i32(key)) < 0 } - - - - -MAX_PATH :: 0x00000104 -MAX_PATH_WIDE :: 0x8000 - -HANDLE_FLAG_INHERIT :: 1 -HANDLE_FLAG_PROTECT_FROM_CLOSE :: 2 - -FILE_BEGIN :: 0 -FILE_CURRENT :: 1 -FILE_END :: 2 - -FILE_SHARE_READ :: 0x00000001 -FILE_SHARE_WRITE :: 0x00000002 -FILE_SHARE_DELETE :: 0x00000004 -FILE_GENERIC_ALL :: 0x10000000 -FILE_GENERIC_EXECUTE :: 0x20000000 -FILE_GENERIC_WRITE :: 0x40000000 -FILE_GENERIC_READ :: 0x80000000 - -FILE_READ_DATA :: 0x0001 -FILE_LIST_DIRECTORY :: 0x0001 -FILE_WRITE_DATA :: 0x0002 -FILE_ADD_FILE :: 0x0002 -FILE_APPEND_DATA :: 0x0004 -FILE_ADD_SUBDIRECTORY :: 0x0004 -FILE_CREATE_PIPE_INSTANCE :: 0x0004 -FILE_READ_EA :: 0x0008 -FILE_WRITE_EA :: 0x0010 -FILE_EXECUTE :: 0x0020 -FILE_TRAVERSE :: 0x0020 -FILE_DELETE_CHILD :: 0x0040 -FILE_READ_ATTRIBUTES :: 0x0080 -FILE_WRITE_ATTRIBUTES :: 0x0100 - -STD_INPUT_HANDLE :: -10 -STD_OUTPUT_HANDLE :: -11 -STD_ERROR_HANDLE :: -12 - -CREATE_NEW :: 1 -CREATE_ALWAYS :: 2 -OPEN_EXISTING :: 3 -OPEN_ALWAYS :: 4 -TRUNCATE_EXISTING :: 5 - -INVALID_FILE_ATTRIBUTES :: -1 - -FILE_ATTRIBUTE_READONLY :: 0x00000001 -FILE_ATTRIBUTE_HIDDEN :: 0x00000002 -FILE_ATTRIBUTE_SYSTEM :: 0x00000004 -FILE_ATTRIBUTE_DIRECTORY :: 0x00000010 -FILE_ATTRIBUTE_ARCHIVE :: 0x00000020 -FILE_ATTRIBUTE_DEVICE :: 0x00000040 -FILE_ATTRIBUTE_NORMAL :: 0x00000080 -FILE_ATTRIBUTE_TEMPORARY :: 0x00000100 -FILE_ATTRIBUTE_SPARSE_FILE :: 0x00000200 -FILE_ATTRIBUTE_REPARSE_Point :: 0x00000400 -FILE_ATTRIBUTE_COMPRESSED :: 0x00000800 -FILE_ATTRIBUTE_OFFLINE :: 0x00001000 -FILE_ATTRIBUTE_NOT_CONTENT_INDEXED :: 0x00002000 -FILE_ATTRIBUTE_ENCRYPTED :: 0x00004000 - -FILE_TYPE_DISK :: 0x0001 -FILE_TYPE_CHAR :: 0x0002 -FILE_TYPE_PIPE :: 0x0003 - - -Monitor_Info :: struct { - size: u32, - monitor: Rect, - work: Rect, - flags: u32, -} - -Window_Placement :: struct { - length: u32, - flags: u32, - show_cmd: u32, - min_pos: Point, - max_pos: Point, - normal_pos: Rect, -} - -Bitmap_Info_Header :: struct { - size: u32, - width, height: i32, - planes, bit_count: i16, - compression: u32, - size_image: u32, - x_pels_per_meter: i32, - y_pels_per_meter: i32, - clr_used: u32, - clr_important: u32, -} -Bitmap_Info :: struct { - using header: Bitmap_Info_Header, - colors: [1]Rgb_Quad, -} - -Paint_Struct :: struct { - hdc: Hdc, - erase: Bool, - rc_paint: Rect, - restore: Bool, - inc_update: Bool, - rgb_reserved: [32]byte, -} - - -Rgb_Quad :: struct {blue, green, red, reserved: byte} - - -Key_Code :: enum i32 { - Unknown = 0x00, - - Lbutton = 0x01, - Rbutton = 0x02, - Cancel = 0x03, - Mbutton = 0x04, - Xbutton1 = 0x05, - Xbutton2 = 0x06, - Back = 0x08, - Tab = 0x09, - Clear = 0x0C, - Return = 0x0D, - - Shift = 0x10, - Control = 0x11, - Menu = 0x12, - Pause = 0x13, - Capital = 0x14, - Kana = 0x15, - Hangeul = 0x15, - Hangul = 0x15, - Junja = 0x17, - Final = 0x18, - Hanja = 0x19, - Kanji = 0x19, - Escape = 0x1B, - Convert = 0x1C, - NonConvert = 0x1D, - Accept = 0x1E, - ModeChange = 0x1F, - Space = 0x20, - Prior = 0x21, - Next = 0x22, - End = 0x23, - Home = 0x24, - Left = 0x25, - Up = 0x26, - Right = 0x27, - Down = 0x28, - Select = 0x29, - Print = 0x2A, - Execute = 0x2B, - Snapshot = 0x2C, - Insert = 0x2D, - Delete = 0x2E, - Help = 0x2F, - - Num0 = '0', - Num1 = '1', - Num2 = '2', - Num3 = '3', - Num4 = '4', - Num5 = '5', - Num6 = '6', - Num7 = '7', - Num8 = '8', - Num9 = '9', - A = 'A', - B = 'B', - C = 'C', - D = 'D', - E = 'E', - F = 'F', - G = 'G', - H = 'H', - I = 'I', - J = 'J', - K = 'K', - L = 'L', - M = 'M', - N = 'N', - O = 'O', - P = 'P', - Q = 'Q', - R = 'R', - S = 'S', - T = 'T', - U = 'U', - V = 'V', - W = 'W', - X = 'X', - Y = 'Y', - Z = 'Z', - - Lwin = 0x5B, - Rwin = 0x5C, - Apps = 0x5D, - - Numpad0 = 0x60, - Numpad1 = 0x61, - Numpad2 = 0x62, - Numpad3 = 0x63, - Numpad4 = 0x64, - Numpad5 = 0x65, - Numpad6 = 0x66, - Numpad7 = 0x67, - Numpad8 = 0x68, - Numpad9 = 0x69, - Multiply = 0x6A, - Add = 0x6B, - Separator = 0x6C, - Subtract = 0x6D, - Decimal = 0x6E, - Divide = 0x6F, - - F1 = 0x70, - F2 = 0x71, - F3 = 0x72, - F4 = 0x73, - F5 = 0x74, - F6 = 0x75, - F7 = 0x76, - F8 = 0x77, - F9 = 0x78, - F10 = 0x79, - F11 = 0x7A, - F12 = 0x7B, - F13 = 0x7C, - F14 = 0x7D, - F15 = 0x7E, - F16 = 0x7F, - F17 = 0x80, - F18 = 0x81, - F19 = 0x82, - F20 = 0x83, - F21 = 0x84, - F22 = 0x85, - F23 = 0x86, - F24 = 0x87, - - Numlock = 0x90, - Scroll = 0x91, - Lshift = 0xA0, - Rshift = 0xA1, - Lcontrol = 0xA2, - Rcontrol = 0xA3, - Lmenu = 0xA4, - Rmenu = 0xA5, - ProcessKey = 0xE5, - Attn = 0xF6, - Crsel = 0xF7, - Exsel = 0xF8, - Ereof = 0xF9, - Play = 0xFA, - Zoom = 0xFB, - Noname = 0xFC, - Pa1 = 0xFD, - OemClear = 0xFE, -} - diff --git a/core/sys/win32/helpers.odin b/core/sys/win32/helpers.odin deleted file mode 100644 index b04e5db95..000000000 --- a/core/sys/win32/helpers.odin +++ /dev/null @@ -1,29 +0,0 @@ -// +build windows -package win32 - -import "core:strings" - -call_external_process :: proc(program, command_line: string) -> bool { - si := Startup_Info{ cb=size_of(Startup_Info) } - pi := Process_Information{} - - return cast(bool)create_process_w( - utf8_to_wstring(program), - utf8_to_wstring(command_line), - nil, - nil, - Bool(false), - u32(0x10), - nil, - nil, - &si, - &pi, - ) -} - -open_website :: proc(url: string) -> bool { - p :: "C:\\Windows\\System32\\cmd.exe" - arg := []string{"/C", "start", url} - args := strings.join(arg, " ", context.temp_allocator) - return call_external_process(p, args) -} diff --git a/core/sys/win32/kernel32.odin b/core/sys/win32/kernel32.odin deleted file mode 100644 index ca6122690..000000000 --- a/core/sys/win32/kernel32.odin +++ /dev/null @@ -1,237 +0,0 @@ -// +build windows -package win32 - -foreign import "system:kernel32.lib" - -@(default_calling_convention = "std") -foreign kernel32 { - @(link_name="CreateProcessA") create_process_a :: proc(application_name, command_line: cstring, - process_attributes, thread_attributes: ^Security_Attributes, - inherit_handle: Bool, creation_flags: u32, environment: rawptr, - current_directory: cstring, startup_info: ^Startup_Info, - process_information: ^Process_Information) -> Bool --- - @(link_name="CreateProcessW") create_process_w :: proc(application_name, command_line: Wstring, - process_attributes, thread_attributes: ^Security_Attributes, - inherit_handle: Bool, creation_flags: u32, environment: rawptr, - current_directory: Wstring, startup_info: ^Startup_Info, - process_information: ^Process_Information) -> Bool --- - @(link_name="GetExitCodeProcess") get_exit_code_process :: proc(process: Handle, exit: ^u32) -> Bool --- - @(link_name="ExitProcess") exit_process :: proc(exit_code: u32) --- - @(link_name="GetModuleHandleA") get_module_handle_a :: proc(module_name: cstring) -> Hmodule --- - @(link_name="GetModuleHandleW") get_module_handle_w :: proc(module_name: Wstring) -> Hmodule --- - - @(link_name="GetModuleFileNameA") get_module_file_name_a :: proc(module: Hmodule, filename: cstring, size: u32) -> u32 --- - @(link_name="GetModuleFileNameW") get_module_file_name_w :: proc(module: Hmodule, filename: Wstring, size: u32) -> u32 --- - - @(link_name="Sleep") sleep :: proc(ms: u32) --- - @(link_name="QueryPerformanceFrequency") query_performance_frequency :: proc(result: ^i64) -> i32 --- - @(link_name="QueryPerformanceCounter") query_performance_counter :: proc(result: ^i64) -> i32 --- - @(link_name="OutputDebugStringA") output_debug_string_a :: proc(c_str: cstring) --- - - @(link_name="GetCommandLineA") get_command_line_a :: proc() -> cstring --- - @(link_name="GetCommandLineW") get_command_line_w :: proc() -> Wstring --- - @(link_name="GetSystemMetrics") get_system_metrics :: proc(index: i32) -> i32 --- - @(link_name="GetSystemInfo") get_system_info :: proc(info: ^System_Info) --- - @(link_name="GetVersionExA") get_version :: proc(osvi: ^OS_Version_Info_Ex_A) --- - @(link_name="GetCurrentThreadId") get_current_thread_id :: proc() -> u32 --- - - // NOTE(tetra): Not thread safe with SetCurrentDirectory and GetFullPathName; - // The current directory is stored as a global variable in the process. - @(link_name="GetCurrentDirectoryW") get_current_directory_w :: proc(len: u32, buf: Wstring) -> u32 --- - @(link_name="SetCurrentDirectoryW") set_current_directory_w :: proc(buf: Wstring) -> u32 --- - - @(link_name="GetSystemTimeAsFileTime") get_system_time_as_file_time :: proc(system_time_as_file_time: ^Filetime) --- - @(link_name="FileTimeToLocalFileTime") file_time_to_local_file_time :: proc(file_time: ^Filetime, local_file_time: ^Filetime) -> Bool --- - @(link_name="FileTimeToSystemTime") file_time_to_system_time :: proc(file_time: ^Filetime, system_time: ^Systemtime) -> Bool --- - @(link_name="SystemTimeToFileTime") system_time_to_file_time :: proc(system_time: ^Systemtime, file_time: ^Filetime) -> Bool --- - - @(link_name="GetStdHandle") get_std_handle :: proc(h: i32) -> Handle --- - - @(link_name="CreateFileA") - create_file_a :: proc(filename: cstring, desired_access, share_module: u32, - security: rawptr, - creation, flags_and_attribs: u32, template_file: Handle) -> Handle --- - - @(link_name="CreateFileW") - create_file_w :: proc(filename: Wstring, desired_access, share_module: u32, - security: rawptr, - creation, flags_and_attribs: u32, template_file: Handle) -> Handle --- - - - @(link_name="ReadFile") read_file :: proc(h: Handle, buf: rawptr, to_read: u32, bytes_read: ^i32, overlapped: rawptr) -> Bool --- - @(link_name="WriteFile") write_file :: proc(h: Handle, buf: rawptr, len: i32, written_result: ^i32, overlapped: rawptr) -> Bool --- - - @(link_name="GetFileSizeEx") get_file_size_ex :: proc(file_handle: Handle, file_size: ^i64) -> Bool --- - @(link_name="GetFileInformationByHandle") get_file_information_by_handle :: proc(file_handle: Handle, file_info: ^By_Handle_File_Information) -> Bool --- - - @(link_name="CreateDirectoryA") create_directory_a :: proc(path: cstring, security_attributes: ^Security_Attributes) -> Bool --- - @(link_name="CreateDirectoryW") create_directory_w :: proc(path: Wstring, security_attributes: ^Security_Attributes) -> Bool --- - - @(link_name="GetFileType") get_file_type :: proc(file_handle: Handle) -> u32 --- - @(link_name="SetFilePointer") set_file_pointer :: proc(file_handle: Handle, distance_to_move: i32, distance_to_move_high: ^i32, move_method: u32) -> u32 --- - - @(link_name="SetHandleInformation") set_handle_information :: proc(obj: Handle, mask, flags: u32) -> Bool --- - - @(link_name="FindFirstFileA") find_first_file_a :: proc(file_name: cstring, data: ^Find_Data_A) -> Handle --- - @(link_name="FindNextFileA") find_next_file_a :: proc(file: Handle, data: ^Find_Data_A) -> Bool --- - - @(link_name="FindFirstFileW") find_first_file_w :: proc(file_name: Wstring, data: ^Find_Data_W) -> Handle --- - @(link_name="FindNextFileW") find_next_file_w :: proc(file: Handle, data: ^Find_Data_W) -> Bool --- - - @(link_name="FindClose") find_close :: proc(file: Handle) -> Bool --- - - @(link_name="MoveFileExA") move_file_ex_a :: proc(existing, new: cstring, flags: u32) -> Bool --- - @(link_name="DeleteFileA") delete_file_a :: proc(file_name: cstring) -> Bool --- - @(link_name="CopyFileA") copy_file_a :: proc(existing, new: cstring, fail_if_exists: Bool) -> Bool --- - - @(link_name="MoveFileExW") move_file_ex_w :: proc(existing, new: Wstring, flags: u32) -> Bool --- - @(link_name="DeleteFileW") delete_file_w :: proc(file_name: Wstring) -> Bool --- - @(link_name="CopyFileW") copy_file_w :: proc(existing, new: Wstring, fail_if_exists: Bool) -> Bool --- - - @(link_name="HeapAlloc") heap_alloc :: proc(h: Handle, flags: u32, bytes: int) -> rawptr --- - @(link_name="HeapReAlloc") heap_realloc :: proc(h: Handle, flags: u32, memory: rawptr, bytes: int) -> rawptr --- - @(link_name="HeapFree") heap_free :: proc(h: Handle, flags: u32, memory: rawptr) -> Bool --- - @(link_name="GetProcessHeap") get_process_heap :: proc() -> Handle --- - - @(link_name="LocalAlloc") local_alloc :: proc(flags: u32, bytes: int) -> rawptr --- - @(link_name="LocalReAlloc") local_realloc :: proc(mem: rawptr, bytes: int, flags: uint) -> rawptr --- - @(link_name="LocalFree") local_free :: proc(mem: rawptr) -> rawptr --- - - @(link_name="FindFirstChangeNotificationA") find_first_change_notification_a :: proc(path: cstring, watch_subtree: Bool, filter: u32) -> Handle --- - @(link_name="FindNextChangeNotification") find_next_change_notification :: proc(h: Handle) -> Bool --- - @(link_name="FindCloseChangeNotification") find_close_change_notification :: proc(h: Handle) -> Bool --- - - @(link_name="ReadDirectoryChangesW") read_directory_changes_w :: proc(dir: Handle, buf: rawptr, buf_length: u32, - watch_subtree: Bool, notify_filter: u32, - bytes_returned: ^u32, overlapped: ^Overlapped, - completion: rawptr) -> Bool --- - - @(link_name="GetOverlappedResult") get_overlapped_result :: proc(file: Handle, overlapped: ^Overlapped, number_of_bytes_transferred: ^u32, wait: Bool) -> Bool --- - - @(link_name="WideCharToMultiByte") wide_char_to_multi_byte :: proc(code_page: u32, flags: u32, - wchar_str: Wstring, wchar: i32, - multi_str: cstring, multi: i32, - default_char: cstring, used_default_char: ^Bool) -> i32 --- - - @(link_name="MultiByteToWideChar") multi_byte_to_wide_char :: proc(code_page: u32, flags: u32, - mb_str: cstring, mb: i32, - wc_str: Wstring, wc: i32) -> i32 --- - - @(link_name="CreateSemaphoreA") create_semaphore_a :: proc(attributes: ^Security_Attributes, initial_count, maximum_count: i32, name: cstring) -> Handle --- - @(link_name="CreateSemaphoreW") create_semaphore_w :: proc(attributes: ^Security_Attributes, initial_count, maximum_count: i32, name: cstring) -> Handle --- - @(link_name="ReleaseSemaphore") release_semaphore :: proc(semaphore: Handle, release_count: i32, previous_count: ^i32) -> Bool --- - @(link_name="WaitForSingleObject") wait_for_single_object :: proc(handle: Handle, milliseconds: u32) -> u32 --- -} - -// @(default_calling_convention = "c") -foreign kernel32 { - @(link_name="GetLastError") get_last_error :: proc() -> i32 --- - @(link_name="CloseHandle") close_handle :: proc(h: Handle) -> i32 --- - - @(link_name="GetFileAttributesA") get_file_attributes_a :: proc(filename: cstring) -> u32 --- - @(link_name="GetFileAttributesW") get_file_attributes_w :: proc(filename: Wstring) -> u32 --- - @(link_name="GetFileAttributesExA") get_file_attributes_ex_a :: proc(filename: cstring, info_level_id: GET_FILEEX_INFO_LEVELS, file_info: ^File_Attribute_Data) -> Bool --- - @(link_name="GetFileAttributesExW") get_file_attributes_ex_w :: proc(filename: Wstring, info_level_id: GET_FILEEX_INFO_LEVELS, file_info: ^File_Attribute_Data) -> Bool --- - @(link_name="CompareFileTime") compare_file_time :: proc(a, b: ^Filetime) -> i32 --- -} - -@(default_calling_convention = "c") -foreign kernel32 { - @(link_name="InterlockedCompareExchange") interlocked_compare_exchange :: proc(dst: ^i32, exchange, comparand: i32) -> i32 --- - @(link_name="InterlockedExchange") interlocked_exchange :: proc(dst: ^i32, desired: i32) -> i32 --- - @(link_name="InterlockedExchangeAdd") interlocked_exchange_add :: proc(dst: ^i32, desired: i32) -> i32 --- - @(link_name="InterlockedAnd") interlocked_and :: proc(dst: ^i32, desired: i32) -> i32 --- - @(link_name="InterlockedOr") interlocked_or :: proc(dst: ^i32, desired: i32) -> i32 --- - - @(link_name="InterlockedCompareExchange64") interlocked_compare_exchange64 :: proc(dst: ^i64, exchange, comparand: i64) -> i64 --- - @(link_name="InterlockedExchange64") interlocked_exchange64 :: proc(dst: ^i64, desired: i64) -> i64 --- - @(link_name="InterlockedExchangeAdd64") interlocked_exchange_add64 :: proc(dst: ^i64, desired: i64) -> i64 --- - @(link_name="InterlockedAnd64") interlocked_and64 :: proc(dst: ^i64, desired: i64) -> i64 --- - @(link_name="InterlockedOr64") interlocked_or64 :: proc(dst: ^i64, desired: i64) -> i64 --- -} - -@(default_calling_convention = "std") -foreign kernel32 { - @(link_name="_mm_pause") mm_pause :: proc() --- - @(link_name="ReadWriteBarrier") read_write_barrier :: proc() --- - @(link_name="WriteBarrier") write_barrier :: proc() --- - @(link_name="ReadBarrier") read_barrier :: proc() --- - - @(link_name="CreateThread") - create_thread :: proc(thread_attributes: ^Security_Attributes, stack_size: uint, start_routine: proc "stdcall" (rawptr) -> u32, - parameter: rawptr, creation_flags: u32, thread_id: ^u32) -> Handle --- - @(link_name="ResumeThread") resume_thread :: proc(thread: Handle) -> u32 --- - @(link_name="GetThreadPriority") get_thread_priority :: proc(thread: Handle) -> i32 --- - @(link_name="SetThreadPriority") set_thread_priority :: proc(thread: Handle, priority: i32) -> Bool --- - @(link_name="GetExitCodeThread") get_exit_code_thread :: proc(thread: Handle, exit_code: ^u32) -> Bool --- - @(link_name="TerminateThread") terminate_thread :: proc(thread: Handle, exit_code: u32) -> Bool --- - - @(link_name="InitializeCriticalSection") initialize_critical_section :: proc(critical_section: ^Critical_Section) --- - @(link_name="InitializeCriticalSectionAndSpinCount") initialize_critical_section_and_spin_count :: proc(critical_section: ^Critical_Section, spin_count: u32) -> b32 --- - @(link_name="DeleteCriticalSection") delete_critical_section :: proc(critical_section: ^Critical_Section) --- - @(link_name="SetCriticalSectionSpinCount") set_critical_section_spin_count :: proc(critical_section: ^Critical_Section, spin_count: u32) -> u32 --- - @(link_name="TryEnterCriticalSection") try_enter_critical_section :: proc(critical_section: ^Critical_Section) -> b8 --- - @(link_name="EnterCriticalSection") enter_critical_section :: proc(critical_section: ^Critical_Section) --- - @(link_name="LeaveCriticalSection") leave_critical_section :: proc(critical_section: ^Critical_Section) --- - - @(link_name="CreateEventA") create_event_a :: proc(event_attributes: ^Security_Attributes, manual_reset, initial_state: Bool, name: cstring) -> Handle --- - @(link_name="CreateEventW") create_event_w :: proc(event_attributes: ^Security_Attributes, manual_reset, initial_state: Bool, name: Wstring) -> Handle --- - @(link_name="PulseEvent") pulse_event :: proc(event: Handle) -> Bool --- - @(link_name="SetEvent") set_event :: proc(event: Handle) -> Bool --- - @(link_name="ResetEvent") reset_event :: proc(event: Handle) -> Bool --- - - @(link_name="LoadLibraryA") load_library_a :: proc(c_str: cstring) -> Hmodule --- - @(link_name="LoadLibraryW") load_library_w :: proc(c_str: Wstring) -> Hmodule --- - @(link_name="FreeLibrary") free_library :: proc(h: Hmodule) -> Bool --- - @(link_name="GetProcAddress") get_proc_address :: proc(h: Hmodule, c_str: cstring) -> rawptr --- - - @(link_name="GetFullPathNameA") get_full_path_name_a :: proc(filename: cstring, buffer_length: u32, buffer: cstring, file_part: ^Wstring) -> u32 --- - @(link_name="GetFullPathNameW") get_full_path_name_w :: proc(filename: Wstring, buffer_length: u32, buffer: Wstring, file_part: ^Wstring) -> u32 --- - @(link_name="GetLongPathNameA") get_long_path_name_a :: proc(short, long: cstring, len: u32) -> u32 --- - @(link_name="GetLongPathNameW") get_long_path_name_w :: proc(short, long: Wstring, len: u32) -> u32 --- - @(link_name="GetShortPathNameA") get_short_path_name_a :: proc(long, short: cstring, len: u32) -> u32 --- - @(link_name="GetShortPathNameW") get_short_path_name_w :: proc(long, short: Wstring, len: u32) -> u32 --- - - @(link_name="GetCurrentDirectoryA") get_current_directory_a :: proc(buffer_length: u32, buffer: cstring) -> u32 --- -} - -Memory_Basic_Information :: struct { - base_address: rawptr, - allocation_base: rawptr, - allocation_protect: u32, - region_size: uint, - state: u32, - protect: u32, - type: u32, -} - -@(default_calling_convention = "std") -foreign kernel32 { - @(link_name="VirtualAlloc") virtual_alloc :: proc(address: rawptr, size: uint, allocation_type: u32, protect: u32) -> rawptr --- - @(link_name="VirtualAllocEx") virtual_alloc_ex :: proc(process: Handle, address: rawptr, size: uint, allocation_type: u32, protect: u32) -> rawptr --- - @(link_name="VirtualFree") virtual_free :: proc(address: rawptr, size: uint, free_type: u32) -> Bool --- - @(link_name="VirtualLock") virtual_lock :: proc(address: rawptr, size: uint) -> Bool --- - @(link_name="VirtualProtect") virtual_protect :: proc(address: rawptr, size: uint, new_protect: u32, old_protect: ^u32) -> Bool --- - @(link_name="VirtualQuery") virtual_query :: proc(address: rawptr, buffer: ^Memory_Basic_Information, length: uint) -> uint --- -} - -MEM_COMMIT :: 0x00001000 -MEM_RESERVE :: 0x00002000 -MEM_DECOMMIT :: 0x00004000 -MEM_RELEASE :: 0x00008000 -MEM_RESET :: 0x00080000 -MEM_RESET_UNDO :: 0x01000000 - -MEM_LARGE_PAGES :: 0x20000000 -MEM_PHYSICAL :: 0x00400000 -MEM_TOP_DOWN :: 0x00100000 -MEM_WRITE_WATCH :: 0x00200000 - -PAGE_NOACCESS :: 0x01 -PAGE_READONLY :: 0x02 -PAGE_READWRITE :: 0x04 -PAGE_WRITECOPY :: 0x08 -PAGE_EXECUTE :: 0x10 -PAGE_EXECUTE_READ :: 0x20 -PAGE_EXECUTE_READWRITE :: 0x40 -PAGE_EXECUTE_WRITECOPY :: 0x80 \ No newline at end of file diff --git a/core/sys/win32/ole32.odin b/core/sys/win32/ole32.odin deleted file mode 100644 index f4ee52399..000000000 --- a/core/sys/win32/ole32.odin +++ /dev/null @@ -1,18 +0,0 @@ -// +build windows -package win32 - -foreign import "system:ole32.lib" - -//objbase.h -Com_Init :: enum { - Multi_Threaded = 0x0, - Apartment_Threaded = 0x2, - Disable_OLE1_DDE = 0x4, - Speed_Over_Memory = 0x8, -} - -@(default_calling_convention = "std") -foreign ole32 { - @(link_name ="CoInitializeEx") com_init_ex :: proc(reserved: rawptr, co_init: Com_Init) ->Hresult --- - @(link_name = "CoUninitialize") com_shutdown :: proc() --- -} diff --git a/core/sys/win32/removal.odin b/core/sys/win32/removal.odin new file mode 100644 index 000000000..09c16aa05 --- /dev/null +++ b/core/sys/win32/removal.odin @@ -0,0 +1,3 @@ +package sys_win32 + +#panic(`"core:sys/win32" has been removed. Please use "core:sys/windows"`) \ No newline at end of file diff --git a/core/sys/win32/shell32.odin b/core/sys/win32/shell32.odin deleted file mode 100644 index 3cedf0527..000000000 --- a/core/sys/win32/shell32.odin +++ /dev/null @@ -1,9 +0,0 @@ -// +build windows -package win32 - -foreign import "system:shell32.lib" - -@(default_calling_convention = "std") -foreign shell32 { - @(link_name="CommandLineToArgvW") command_line_to_argv_w :: proc(cmd_list: Wstring, num_args: ^i32) -> ^Wstring --- -} diff --git a/core/sys/win32/tests/general.odin b/core/sys/win32/tests/general.odin deleted file mode 100644 index 9a78dec11..000000000 --- a/core/sys/win32/tests/general.odin +++ /dev/null @@ -1,41 +0,0 @@ -package win32_tests - -import win32 "core:sys/windows" -import "core:testing" - -utf16_to_utf8 :: proc(t: ^testing.T, str: []u16, comparison: string, expected_result: bool, loc := #caller_location) { - result, _ := win32.utf16_to_utf8(str[:]) - testing.expect(t, (result == comparison) == expected_result, "Incorrect utf16_to_utf8 conversion", loc) -} - -wstring_to_utf8 :: proc(t: ^testing.T, str: []u16, comparison: string, expected_result: bool, loc := #caller_location) { - result, _ := win32.wstring_to_utf8(nil if len(str) == 0 else cast(win32.Wstring)&str[0], -1) - testing.expect(t, (result == comparison) == expected_result, "Incorrect wstring_to_utf8 conversion", loc) -} - -@test -test_utf :: proc(t: ^testing.T) { - utf16_to_utf8(t, []u16{}, "", true) - utf16_to_utf8(t, []u16{0}, "", true) - utf16_to_utf8(t, []u16{0, 't', 'e', 's', 't'}, "", true) - utf16_to_utf8(t, []u16{0, 't', 'e', 's', 't', 0}, "", true) - utf16_to_utf8(t, []u16{'t', 'e', 's', 't'}, "test", true) - utf16_to_utf8(t, []u16{'t', 'e', 's', 't', 0}, "test", true) - utf16_to_utf8(t, []u16{'t', 'e', 0, 's', 't'}, "te", true) - utf16_to_utf8(t, []u16{'t', 'e', 0, 's', 't', 0}, "te", true) - - wstring_to_utf8(t, []u16{}, "", true) - wstring_to_utf8(t, []u16{0}, "", true) - wstring_to_utf8(t, []u16{0, 't', 'e', 's', 't'}, "", true) - wstring_to_utf8(t, []u16{0, 't', 'e', 's', 't', 0}, "", true) - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 0}, "test", true) - wstring_to_utf8(t, []u16{'t', 'e', 0, 's', 't'}, "te", true) - wstring_to_utf8(t, []u16{'t', 'e', 0, 's', 't', 0}, "te", true) - - // WARNING: Passing a non-zero-terminated string to wstring_to_utf8 is dangerous, - // as it will go out of bounds looking for a zero. - // It will "fail" or "succeed" by having a zero just after the end of the input string or not. - wstring_to_utf8(t, []u16{'t', 'e', 's', 't'}, "test", false) - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 0}[:4], "test", true) - wstring_to_utf8(t, []u16{'t', 'e', 's', 't', 'q'}[:4], "test", false) -} \ No newline at end of file diff --git a/core/sys/win32/user32.odin b/core/sys/win32/user32.odin deleted file mode 100644 index 44d1f7004..000000000 --- a/core/sys/win32/user32.odin +++ /dev/null @@ -1,289 +0,0 @@ -// +build windows -package win32 - -foreign import "system:user32.lib" - -import "core:intrinsics" - - -Menu_Bar_Info :: struct { - size: u32, - bar: Rect, - menu: Hmenu, - wnd_menu: Hwnd, - fields: u8, - // field.bar_focused: 1, - // field.focuses: 1, -} - -Menu_Item_Info_A :: struct { - size: u32, - mask: u32, - type: u32, - state: u32, - id: u32, - submenu: Hmenu, - bmp_checked: Hbitmap, - bmp_unchecked: Hbitmap, - item_data: u32, - type_data: cstring, - cch: u32, -} -Menu_Item_Info_W :: struct { - size: u32, - mask: u32, - type: u32, - state: u32, - id: u32, - submenu: Hmenu, - bmp_checked: Hbitmap, - bmp_unchecked: Hbitmap, - item_data: u32, - type_data: Wstring, - cch: u32, -} - -MF_BYCOMMAND :: 0x00000000 -MF_BYPOSITION :: 0x00000400 -MF_BITMAP :: 0x00000004 -MF_CHECKED :: 0x00000008 -MF_DISABLED :: 0x00000002 -MF_ENABLED :: 0x00000000 -MF_GRAYED :: 0x00000001 -MF_MENUBARBREAK :: 0x00000020 -MF_MENUBREAK :: 0x00000040 -MF_OWNERDRAW :: 0x00000100 -MF_POPUP :: 0x00000010 -MF_SEPARATOR :: 0x00000800 -MF_STRING :: 0x00000000 -MF_UNCHECKED :: 0x00000000 - -MB_ABORTRETRYIGNORE :: 0x00000002 -MB_CANCELTRYCONTINUE :: 0x00000006 -MB_HELP :: 0x00004000 -MB_OK :: 0x00000000 -MB_OKCANCEL :: 0x00000001 -MB_RETRYCANCEL :: 0x00000005 -MB_YESNO :: 0x00000004 -MB_YESNOCANCEL :: 0x00000003 - -MB_ICONEXCLAMATION :: 0x00000030 -MB_ICONWARNING :: 0x00000030 -MB_ICONINFORMATION :: 0x00000040 -MB_ICONASTERISK :: 0x00000040 -MB_ICONQUESTION :: 0x00000020 -MB_ICONSTOP :: 0x00000010 -MB_ICONERROR :: 0x00000010 -MB_ICONHAND :: 0x00000010 - -MB_DEFBUTTON1 :: 0x00000000 -MB_DEFBUTTON2 :: 0x00000100 -MB_DEFBUTTON3 :: 0x00000200 -MB_DEFBUTTON4 :: 0x00000300 - -MB_APPLMODAL :: 0x00000000 -MB_SYSTEMMODAL :: 0x00001000 -MB_TASKMODAL :: 0x00002000 - -MB_DEFAULT_DESKTOP_ONLY :: 0x00020000 -MB_RIGHT :: 0x00080000 -MB_RTLREADING :: 0x00100000 -MB_SETFOREGROUND :: 0x00010000 -MB_TOPMOST :: 0x00040000 -MB_SERVICE_NOTIFICATION :: 0x00200000 - - -@(default_calling_convention = "std") -foreign user32 { - @(link_name="GetDesktopWindow") get_desktop_window :: proc() -> Hwnd --- - when !intrinsics.is_package_imported("raylib") { // NOTE(bill): this is a bit of hack but it's to get around the namespace collisions - @(link_name="ShowCursor")show_cursor :: proc(show: Bool) -> i32 --- - } - @(link_name="GetCursorPos") get_cursor_pos :: proc(p: ^Point) -> Bool --- - @(link_name="SetCursorPos") set_cursor_pos :: proc(x, y: i32) -> Bool --- - @(link_name="GetCapure") get_capture :: proc(hwnd: Hwnd) -> Hwnd --- - @(link_name="SetCapture") set_capture :: proc(hwnd: Hwnd) -> Hwnd --- - @(link_name="ReleaseCapture") release_capture :: proc() -> Bool --- - @(link_name="ScreenToClient") screen_to_client :: proc(h: Hwnd, p: ^Point) -> Bool --- - @(link_name="ClientToScreen") client_to_screen :: proc(h: Hwnd, p: ^Point) -> Bool --- - @(link_name="PostQuitMessage") post_quit_message :: proc(exit_code: i32) --- - @(link_name="SetWindowTextA") set_window_text_a :: proc(hwnd: Hwnd, c_string: cstring) -> Bool --- - @(link_name="SetWindowTextW") set_window_text_w :: proc(hwnd: Hwnd, c_string: Wstring) -> Bool --- - @(link_name="RegisterClassA") register_class_a :: proc(wc: ^Wnd_Class_A) -> i16 --- - @(link_name="RegisterClassW") register_class_w :: proc(wc: ^Wnd_Class_W) -> i16 --- - @(link_name="UnregisterClassA") unregister_class_a :: proc(class_name: cstring, instance: Hinstance) -> Bool --- - @(link_name="UnregisterClassW") unregister_class_w :: proc(class_name: Wstring, instance: Hinstance) -> Bool --- - @(link_name="RegisterClassExA") register_class_ex_a :: proc(wc: ^Wnd_Class_Ex_A) -> i16 --- - @(link_name="RegisterClassExW") register_class_ex_w :: proc(wc: ^Wnd_Class_Ex_W) -> i16 --- - - @(link_name="CreateWindowExA") - create_window_ex_a :: proc(ex_style: u32, - class_name, title: cstring, - style: u32, - x, y, w, h: i32, - parent: Hwnd, menu: Hmenu, instance: Hinstance, - param: rawptr) -> Hwnd --- - - @(link_name="CreateWindowExW") - create_window_ex_w :: proc(ex_style: u32, - class_name, title: Wstring, - style: u32, - x, y, w, h: i32, - parent: Hwnd, menu: Hmenu, instance: Hinstance, - param: rawptr) -> Hwnd --- - - @(link_name="ShowWindow") show_window :: proc(hwnd: Hwnd, cmd_show: i32) -> Bool --- - @(link_name="TranslateMessage") translate_message :: proc(msg: ^Msg) -> Bool --- - @(link_name="DispatchMessageA") dispatch_message_a :: proc(msg: ^Msg) -> Lresult --- - @(link_name="DispatchMessageW") dispatch_message_w :: proc(msg: ^Msg) -> Lresult --- - @(link_name="UpdateWindow") update_window :: proc(hwnd: Hwnd) -> Bool --- - @(link_name="GetMessageA") get_message_a :: proc(msg: ^Msg, hwnd: Hwnd, msg_filter_min, msg_filter_max: u32) -> Bool --- - @(link_name="GetMessageW") get_message_w :: proc(msg: ^Msg, hwnd: Hwnd, msg_filter_min, msg_filter_max: u32) -> Bool --- - - @(link_name="PeekMessageA") peek_message_a :: proc(msg: ^Msg, hwnd: Hwnd, msg_filter_min, msg_filter_max, remove_msg: u32) -> Bool --- - @(link_name="PeekMessageW") peek_message_w :: proc(msg: ^Msg, hwnd: Hwnd, msg_filter_min, msg_filter_max, remove_msg: u32) -> Bool --- - - - @(link_name="PostMessageA") post_message_a :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Bool --- - @(link_name="PostMessageW") post_message_w :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Bool --- - @(link_name="SendMessageA") send_message_a :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Lresult --- - @(link_name="SendMessageW") send_message_w :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Lresult --- - - @(link_name="DefWindowProcA") def_window_proc_a :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Lresult --- - @(link_name="DefWindowProcW") def_window_proc_w :: proc(hwnd: Hwnd, msg: u32, wparam: Wparam, lparam: Lparam) -> Lresult --- - - @(link_name="AdjustWindowRect") adjust_window_rect :: proc(rect: ^Rect, style: u32, menu: Bool) -> Bool --- - @(link_name="GetActiveWindow") get_active_window :: proc() -> Hwnd --- - - @(link_name="DestroyWindow") destroy_window :: proc(wnd: Hwnd) -> Bool --- - @(link_name="DescribePixelFormat") describe_pixel_format :: proc(dc: Hdc, pixel_format: i32, bytes: u32, pfd: ^Pixel_Format_Descriptor) -> i32 --- - - @(link_name="GetMonitorInfoA") get_monitor_info_a :: proc(monitor: Hmonitor, mi: ^Monitor_Info) -> Bool --- - @(link_name="MonitorFromWindow") monitor_from_window :: proc(wnd: Hwnd, flags: u32) -> Hmonitor --- - - @(link_name="SetWindowPos") set_window_pos :: proc(wnd: Hwnd, wndInsertAfter: Hwnd, x, y, width, height: i32, flags: u32) -> Bool --- - - @(link_name="GetWindowPlacement") get_window_placement :: proc(wnd: Hwnd, wndpl: ^Window_Placement) -> Bool --- - @(link_name="SetWindowPlacement") set_window_placement :: proc(wnd: Hwnd, wndpl: ^Window_Placement) -> Bool --- - @(link_name="GetWindowRect") get_window_rect :: proc(wnd: Hwnd, rect: ^Rect) -> Bool --- - - @(link_name="GetWindowLongPtrA") get_window_long_ptr_a :: proc(wnd: Hwnd, index: i32) -> Long_Ptr --- - @(link_name="SetWindowLongPtrA") set_window_long_ptr_a :: proc(wnd: Hwnd, index: i32, new: Long_Ptr) -> Long_Ptr --- - @(link_name="GetWindowLongPtrW") get_window_long_ptr_w :: proc(wnd: Hwnd, index: i32) -> Long_Ptr --- - @(link_name="SetWindowLongPtrW") set_window_long_ptr_w :: proc(wnd: Hwnd, index: i32, new: Long_Ptr) -> Long_Ptr --- - - @(link_name="GetWindowText") get_window_text :: proc(wnd: Hwnd, str: cstring, maxCount: i32) -> i32 --- - - @(link_name="GetClientRect") get_client_rect :: proc(hwnd: Hwnd, rect: ^Rect) -> Bool --- - - @(link_name="GetDC") get_dc :: proc(h: Hwnd) -> Hdc --- - @(link_name="ReleaseDC") release_dc :: proc(wnd: Hwnd, hdc: Hdc) -> i32 --- - - @(link_name="MapVirtualKeyA") map_virtual_key_a :: proc(scancode: u32, map_type: u32) -> u32 --- - @(link_name="MapVirtualKeyW") map_virtual_key_w :: proc(scancode: u32, map_type: u32) -> u32 --- - - @(link_name="GetKeyState") get_key_state :: proc(v_key: i32) -> i16 --- - @(link_name="GetAsyncKeyState") get_async_key_state :: proc(v_key: i32) -> i16 --- - - @(link_name="SetForegroundWindow") set_foreground_window :: proc(h: Hwnd) -> Bool --- - @(link_name="SetFocus") set_focus :: proc(h: Hwnd) -> Hwnd --- - - - @(link_name="LoadImageA") load_image_a :: proc(instance: Hinstance, name: cstring, type_: u32, x_desired, y_desired : i32, load : u32) -> Handle --- - @(link_name="LoadIconA") load_icon_a :: proc(instance: Hinstance, icon_name: cstring) -> Hicon --- - @(link_name="DestroyIcon") destroy_icon :: proc(icon: Hicon) -> Bool --- - - @(link_name="LoadCursorA") load_cursor_a :: proc(instance: Hinstance, cursor_name: cstring) -> Hcursor --- - @(link_name="LoadCursorW") load_cursor_w :: proc(instance: Hinstance, cursor_name: Wstring) -> Hcursor --- - @(link_name="GetCursor") get_cursor :: proc() -> Hcursor --- - @(link_name="SetCursor") set_cursor :: proc(cursor: Hcursor) -> Hcursor --- - - @(link_name="RegisterRawInputDevices") register_raw_input_devices :: proc(raw_input_device: ^Raw_Input_Device, num_devices, size: u32) -> Bool --- - - @(link_name="GetRawInputData") get_raw_input_data :: proc(raw_input: Hrawinput, command: u32, data: rawptr, size: ^u32, size_header: u32) -> u32 --- - - @(link_name="MapVirtualKeyExW") map_virtual_key_ex_w :: proc(code, map_type: u32, hkl: HKL) -> u32 --- - @(link_name="MapVirtualKeyExA") map_virtual_key_ex_a :: proc(code, map_type: u32, hkl: HKL) -> u32 --- - - @(link_name="EnumDisplayMonitors") enum_display_monitors :: proc(hdc: Hdc, rect: ^Rect, enum_proc: Monitor_Enum_Proc, lparam: Lparam) -> bool --- - - @(link_name="EnumDisplaySettingsA") enum_display_settings_a :: proc(device_name: cstring, mode_number: u32, mode: ^Dev_Mode_A) -> Bool --- -} - -@(default_calling_convention = "std") -foreign user32 { - @(link_name="CreateMenu") create_menu :: proc() -> Hmenu --- - @(link_name="CreatePopupMenu") create_popup_menu :: proc() -> Hmenu --- - @(link_name="DestroyMenu") destroy_menu :: proc(menu: Hmenu) -> Bool --- - @(link_name="DeleteMenu") delete_menu :: proc(menu: Hmenu, position: u32, flags: u32) -> Bool --- - - @(link_name="EnableMenuItem") enable_menu_item :: proc(menu: Hmenu, id_enable_itme: i32, enable: u32) -> Bool --- - @(link_name="EndMenu") end_menu :: proc(menu: Hmenu, flags: u32, id_new_item: Uint_Ptr, new_item: cstring) -> Bool --- - @(link_name="GetMenu") get_menu :: proc(wnd: Hwnd) -> Hmenu --- - @(link_name="GetMenuBarInfo") get_menu_bar_info :: proc(wnd: Hwnd, id_object, id_item: u32, mbi: ^Menu_Bar_Info) -> Hmenu --- - @(link_name="GetMenuStringA") get_menu_string_a :: proc(menu: Hmenu, id_item: u32, s: cstring, cch_max: i32, flags: u32) -> i32 --- - @(link_name="GetMenuStringW") get_menu_string_w :: proc(menu: Hmenu, id_item: u32, s: Wstring, cch_max: i32, flags: u32) -> i32 --- - @(link_name="GetMenuState") get_menu_state :: proc(menu: Hmenu, id: u32, flags: u32) -> u32 --- - @(link_name="GetMenuItemRect") get_menu_item_rect :: proc(wnd: Hwnd, menu: Hmenu, id_item: u32, item: ^Rect) -> Bool --- - - @(link_name="SetMenu") set_menu :: proc(wnd: Hwnd, menu: Hmenu) -> Bool --- - - @(link_name="DrawMenuBar") draw_menu_bar :: proc(wnd: Hwnd) -> Bool --- - @(link_name="InsertMenuA") insert_menu_a :: proc(menu: Hmenu, position: u32, flags: u32, id_new_item: Uint_Ptr, new_item: cstring) -> Bool --- - @(link_name="InsertMenuW") insert_menu_w :: proc(menu: Hmenu, position: u32, flags: u32, id_new_item: Uint_Ptr, new_item: Wstring) -> Bool --- - - @(link_name="InsertMenuItemA") insert_menu_item_a :: proc(hmenu: Hmenu, item: u32, fByPosition: Bool, lpmi: ^Menu_Item_Info_A) -> Bool --- - @(link_name="InsertMenuItemW") insert_menu_item_w :: proc(hmenu: Hmenu, item: u32, fByPosition: Bool, lpmi: ^Menu_Item_Info_W) -> Bool --- - - @(link_name="AppendMenuA") append_menu_a :: proc(menu: Hmenu, flags: u32, id_new_item: Uint_Ptr, new_item: cstring) -> Bool --- - @(link_name="AppendMenuW") append_menu_w :: proc(menu: Hmenu, flags: u32, id_new_item: Uint_Ptr, new_item: Wstring) -> Bool --- - - @(link_name="CheckMenuItem") check_menu_item :: proc(menu: Hmenu, id_check_item: u32, check: u32) -> u32 --- - @(link_name="CheckMenuRadioItem") check_menu_radio_item :: proc(menu: Hmenu, first, last: u32, check: u32, flags: u32) -> Bool --- - - @(link_name="GetPropA") get_prop_a :: proc(wnd: Hwnd, s: cstring) -> Handle --- - @(link_name="GetPropW") get_prop_w :: proc(wnd: Hwnd, s: Wstring) -> Handle --- - - @(link_name="MessageBoxA") message_box_a :: proc(wnd: Hwnd, text, caption: cstring, type: u32) -> i32 --- - @(link_name="MessageBoxW") message_box_w :: proc(wnd: Hwnd, text, caption: Wstring, type: u32) -> i32 --- - - @(link_name="MessageBoxExA") message_box_ex_a :: proc(wnd: Hwnd, text, caption: cstring, type: u32, language_id: u16) -> i32 --- - @(link_name="MessageBoxExW") message_box_ex_w :: proc(wnd: Hwnd, text, caption: Wstring, type: u32, language_id: u16) -> i32 --- - - @(link_name="BeginPaint") begin_paint :: proc(wnd: Hwnd, paint: ^Paint_Struct) -> Hdc --- - @(link_name="EndPaint") end_paint :: proc(wnd: Hwnd, paint: ^Paint_Struct) -> Bool --- -} - - -_IDC_APPSTARTING := rawptr(uintptr(32650)) -_IDC_ARROW := rawptr(uintptr(32512)) -_IDC_CROSS := rawptr(uintptr(32515)) -_IDC_HAND := rawptr(uintptr(32649)) -_IDC_HELP := rawptr(uintptr(32651)) -_IDC_IBEAM := rawptr(uintptr(32513)) -_IDC_ICON := rawptr(uintptr(32641)) -_IDC_NO := rawptr(uintptr(32648)) -_IDC_SIZE := rawptr(uintptr(32640)) -_IDC_SIZEALL := rawptr(uintptr(32646)) -_IDC_SIZENESW := rawptr(uintptr(32643)) -_IDC_SIZENS := rawptr(uintptr(32645)) -_IDC_SIZENWSE := rawptr(uintptr(32642)) -_IDC_SIZEWE := rawptr(uintptr(32644)) -_IDC_UPARROW := rawptr(uintptr(32516)) -_IDC_WAIT := rawptr(uintptr(32514)) -IDC_APPSTARTING := cstring(_IDC_APPSTARTING) -IDC_ARROW := cstring(_IDC_ARROW) -IDC_CROSS := cstring(_IDC_CROSS) -IDC_HAND := cstring(_IDC_HAND) -IDC_HELP := cstring(_IDC_HELP) -IDC_IBEAM := cstring(_IDC_IBEAM) -IDC_ICON := cstring(_IDC_ICON) -IDC_NO := cstring(_IDC_NO) -IDC_SIZE := cstring(_IDC_SIZE) -IDC_SIZEALL := cstring(_IDC_SIZEALL) -IDC_SIZENESW := cstring(_IDC_SIZENESW) -IDC_SIZENS := cstring(_IDC_SIZENS) -IDC_SIZENWSE := cstring(_IDC_SIZENWSE) -IDC_SIZEWE := cstring(_IDC_SIZEWE) -IDC_UPARROW := cstring(_IDC_UPARROW) -IDC_WAIT := cstring(_IDC_WAIT) diff --git a/core/sys/win32/wgl.odin b/core/sys/win32/wgl.odin deleted file mode 100644 index e6c414b0e..000000000 --- a/core/sys/win32/wgl.odin +++ /dev/null @@ -1,114 +0,0 @@ -// +build windows -package win32 - -foreign import "system:opengl32.lib" - -CONTEXT_MAJOR_VERSION_ARB :: 0x2091 -CONTEXT_MINOR_VERSION_ARB :: 0x2092 -CONTEXT_FLAGS_ARB :: 0x2094 -CONTEXT_PROFILE_MASK_ARB :: 0x9126 -CONTEXT_FORWARD_COMPATIBLE_BIT_ARB :: 0x0002 -CONTEXT_CORE_PROFILE_BIT_ARB :: 0x00000001 -CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB :: 0x00000002 - -Hglrc :: distinct Handle -Color_Ref :: distinct u32 - -Layer_Plane_Descriptor :: struct { - size: u16, - version: u16, - flags: u32, - pixel_type: u8, - color_bits: u8, - red_bits: u8, - red_shift: u8, - green_bits: u8, - green_shift: u8, - blue_bits: u8, - blue_shift: u8, - alpha_bits: u8, - alpha_shift: u8, - accum_bits: u8, - accum_red_bits: u8, - accum_green_bits: u8, - accum_blue_bits: u8, - accum_alpha_bits: u8, - depth_bits: u8, - stencil_bits: u8, - aux_buffers: u8, - layer_type: u8, - reserved: u8, - transparent: Color_Ref, -} - -Point_Float :: struct {x, y: f32} - -Glyph_Metrics_Float :: struct { - black_box_x: f32, - black_box_y: f32, - glyph_origin: Point_Float, - cell_inc_x: f32, - cell_inc_y: f32, -} - -Create_Context_Attribs_ARB_Type :: #type proc "c" (hdc: Hdc, h_share_context: rawptr, attribList: ^i32) -> Hglrc -Choose_Pixel_Format_ARB_Type :: #type proc "c" (hdc: Hdc, attrib_i_list: ^i32, attrib_f_list: ^f32, max_formats: u32, formats: ^i32, num_formats : ^u32) -> Bool -Swap_Interval_EXT_Type :: #type proc "c" (interval: i32) -> bool -Get_Extensions_String_ARB_Type :: #type proc "c" (Hdc) -> cstring - -// Procedures - create_context_attribs_arb: Create_Context_Attribs_ARB_Type - choose_pixel_format_arb: Choose_Pixel_Format_ARB_Type - swap_interval_ext: Swap_Interval_EXT_Type - get_extensions_string_arb: Get_Extensions_String_ARB_Type - - -foreign opengl32 { - @(link_name="wglCreateContext") - create_context :: proc(hdc: Hdc) -> Hglrc --- - - @(link_name="wglMakeCurrent") - make_current :: proc(hdc: Hdc, hglrc: Hglrc) -> Bool --- - - @(link_name="wglGetProcAddress") - get_gl_proc_address :: proc(c_str: cstring) -> rawptr --- - - @(link_name="wglDeleteContext") - delete_context :: proc(hglrc: Hglrc) -> Bool --- - - @(link_name="wglCopyContext") - copy_context :: proc(src, dst: Hglrc, mask: u32) -> Bool --- - - @(link_name="wglCreateLayerContext") - create_layer_context :: proc(hdc: Hdc, layer_plane: i32) -> Hglrc --- - - @(link_name="wglDescribeLayerPlane") - describe_layer_plane :: proc(hdc: Hdc, pixel_format, layer_plane: i32, bytes: u32, pd: ^Layer_Plane_Descriptor) -> Bool --- - - @(link_name="wglGetCurrentContext") - get_current_context :: proc() -> Hglrc --- - - @(link_name="wglGetCurrentDC") - get_current_dc :: proc() -> Hdc --- - - @(link_name="wglGetLayerPaletteEntries") - get_layer_palette_entries :: proc(hdc: Hdc, layer_plane, start, entries: i32, cr: ^Color_Ref) -> i32 --- - - @(link_name="wglRealizeLayerPalette") - realize_layer_palette :: proc(hdc: Hdc, layer_plane: i32, realize: Bool) -> Bool --- - - @(link_name="wglSetLayerPaletteEntries") - set_layer_palette_entries :: proc(hdc: Hdc, layer_plane, start, entries: i32, cr: ^Color_Ref) -> i32 --- - - @(link_name="wglShareLists") - share_lists :: proc(hglrc1, hglrc2: Hglrc) -> Bool --- - - @(link_name="wglSwapLayerBuffers") - swap_layer_buffers :: proc(hdc: Hdc, planes: u32) -> Bool --- - - @(link_name="wglUseFontBitmaps") - use_font_bitmaps :: proc(hdc: Hdc, first, count, list_base: u32) -> Bool --- - - @(link_name="wglUseFontOutlines") - use_font_outlines :: proc(hdc: Hdc, first, count, list_base: u32, deviation, extrusion: f32, format: i32, gmf: ^Glyph_Metrics_Float) -> Bool --- -} diff --git a/core/sys/win32/winmm.odin b/core/sys/win32/winmm.odin deleted file mode 100644 index 0f567fbcc..000000000 --- a/core/sys/win32/winmm.odin +++ /dev/null @@ -1,12 +0,0 @@ -// +build windows -package win32 - -foreign import "system:winmm.lib" - - -@(default_calling_convention = "std") -foreign winmm { - @(link_name="timeBeginPeriod") time_begin_period :: proc(period: u32) -> u32 --- - - @(link_name="timeGetTime") time_get_time :: proc() -> u32 --- -} From f002857edce5ea53561608fa555bbbd94b7fa42a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 15:47:24 +0100 Subject: [PATCH 047/254] Clean up `core:time` to be consistent across all platforms --- core/os/os2/errors.odin | 2 + core/runtime/procs.odin | 2 +- core/sys/unix/pthread_unix.odin | 5 +- core/sys/unix/time_unix.odin | 83 ++++++++++++++++++++++ core/time/time.odin | 42 +++++++---- core/time/time_essence.odin | 13 ++-- core/time/time_freestanding.odin | 9 ++- core/time/time_js.odin | 7 +- core/time/time_unix.odin | 118 +++++-------------------------- core/time/time_wasi.odin | 7 +- core/time/time_windows.odin | 9 ++- 11 files changed, 159 insertions(+), 138 deletions(-) create mode 100644 core/sys/unix/time_unix.odin diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index f42f92eb9..2cff73ebd 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -14,6 +14,7 @@ General_Error :: enum u32 { Timeout, Invalid_File, + Invalid_Dir, Invalid_Path, Unsupported, @@ -51,6 +52,7 @@ error_string :: proc(ferr: Error) -> string { case .Closed: return "file already closed" case .Timeout: return "i/o timeout" case .Invalid_File: return "invalid file" + case .Invalid_Dir: return "invalid directory" case .Invalid_Path: return "invalid path" case .Unsupported: return "unsupported" } diff --git a/core/runtime/procs.odin b/core/runtime/procs.odin index 5a1d11fe0..782efa773 100644 --- a/core/runtime/procs.odin +++ b/core/runtime/procs.odin @@ -4,7 +4,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { foreign import lib "system:NtDll.lib" @(private="file") - @(default_calling_convention="std") + @(default_calling_convention="stdcall") foreign lib { RtlMoveMemory :: proc(dst, src: rawptr, length: int) --- RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 62e3701ab..8bf397647 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -4,7 +4,6 @@ package unix foreign import "system:pthread" import "core:c" -import "core:time" // // On success, these functions return 0. @@ -72,7 +71,7 @@ foreign pthread { // assumes the mutex is pre-locked pthread_cond_wait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t) -> c.int --- - pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int --- + pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- pthread_condattr_init :: proc(attrs: ^pthread_condattr_t) -> c.int --- pthread_condattr_destroy :: proc(attrs: ^pthread_condattr_t) -> c.int --- @@ -95,7 +94,7 @@ foreign pthread { pthread_mutex_lock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int --- + pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- pthread_mutex_unlock :: proc(mutex: ^pthread_mutex_t) -> c.int --- diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin new file mode 100644 index 000000000..d9452c216 --- /dev/null +++ b/core/sys/unix/time_unix.odin @@ -0,0 +1,83 @@ +//+build linux, darwin, freebsd, openbsd +package unix + +when ODIN_OS == .Darwin { + foreign import libc "System.framework" +} else { + foreign import libc "system:c" +} + +@(default_calling_convention="c") +foreign libc { + clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 --- + sleep :: proc(seconds: u32) -> i32 --- + nanosleep :: proc(requested, remaining: ^timespec) -> i32 --- +} + +foreign import "system:pthread" + +import "core:c" + +@(private="file") +@(default_calling_convention="c") +foreign pthread { + sched_yield :: proc() -> c.int --- +} + +timespec :: struct { + tv_sec: i64, // seconds + tv_nsec: i64, // nanoseconds +} + +when ODIN_OS == .OpenBSD { + CLOCK_REALTIME :: 0 + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_MONOTONIC :: 3 + CLOCK_THREAD_CPUTIME_ID :: 4 + CLOCK_UPTIME :: 5 + CLOCK_BOOTTIME :: 6 + + // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC + CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC +} else { + CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. + CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_THREAD_CPUTIME_ID :: 3 + CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. + CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." + CLOCK_MONOTONIC_COARSE :: 6 + CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. + CLOCK_REALTIME_ALARM :: 8 + CLOCK_BOOTTIME_ALARM :: 9 +} + +// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. +// I do not know if Darwin programmers are used to the existance of these constants or not, so +// I'm leaving aliases to them for now. +CLOCK_SYSTEM :: CLOCK_REALTIME +CLOCK_CALENDAR :: CLOCK_MONOTONIC + +boot_time_in_nanoseconds :: proc "c" () -> i64 { + ts_now, ts_boottime: timespec + clock_gettime(CLOCK_REALTIME, &ts_now) + clock_gettime(CLOCK_BOOTTIME, &ts_boottime) + + ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec + return i64(ns) +} + +seconds_since_boot :: proc "c" () -> f64 { + ts_boottime: timespec + clock_gettime(CLOCK_BOOTTIME, &ts_boottime) + return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 +} + + +inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) { + s, ns := nanoseconds / 1e9, nanoseconds % 1e9 + requested := timespec{tv_sec=s, tv_nsec=ns} + res = nanosleep(&requested, &remaining) + return +} + diff --git a/core/time/time.odin b/core/time/time.odin index 1f778a8de..6c6e47dc0 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -14,6 +14,8 @@ Hour :: 60 * Minute MIN_DURATION :: Duration(-1 << 63) MAX_DURATION :: Duration(1<<63 - 1) +IS_SUPPORTED :: _IS_SUPPORTED + Time :: struct { _nsec: i64, // zero is 1970-01-01 00:00:00 } @@ -49,6 +51,14 @@ Stopwatch :: struct { _accumulation: Duration, } +now :: proc "contextless" () -> Time { + return _now() +} + +sleep :: proc "contextless" (d: Duration) { + _sleep(d) +} + stopwatch_start :: proc(using stopwatch: ^Stopwatch) { if !running { _start_time = tick_now() @@ -82,36 +92,36 @@ since :: proc(start: Time) -> Duration { return diff(start, now()) } -duration_nanoseconds :: proc(d: Duration) -> i64 { +duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { return i64(d) } -duration_microseconds :: proc(d: Duration) -> f64 { +duration_microseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e6 } -duration_milliseconds :: proc(d: Duration) -> f64 { +duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e3 } -duration_seconds :: proc(d: Duration) -> f64 { +duration_seconds :: proc "contextless" (d: Duration) -> f64 { sec := d / Second nsec := d % Second return f64(sec) + f64(nsec)/1e9 } -duration_minutes :: proc(d: Duration) -> f64 { +duration_minutes :: proc "contextless" (d: Duration) -> f64 { min := d / Minute nsec := d % Minute return f64(min) + f64(nsec)/(60*1e9) } -duration_hours :: proc(d: Duration) -> f64 { +duration_hours :: proc "contextless" (d: Duration) -> f64 { hour := d / Hour nsec := d % Hour return f64(hour) + f64(nsec)/(60*60*1e9) } -_less_than_half :: #force_inline proc(x, y: Duration) -> bool { - return u64(x)+u64(x) < u64(y) -} - duration_round :: proc(d, m: Duration) -> Duration { + _less_than_half :: #force_inline proc(x, y: Duration) -> bool { + return u64(x)+u64(x) < u64(y) + } + if m <= 0 { return d } @@ -201,10 +211,12 @@ unix :: proc(sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec) + UNIX_TO_INTERNAL} } +to_unix_seconds :: time_to_unix time_to_unix :: proc(t: Time) -> i64 { return t._nsec/1e9 } +to_unix_nanoseconds :: time_to_unix_nano time_to_unix_nano :: proc(t: Time) -> i64 { return t._nsec } @@ -265,20 +277,24 @@ INTERNAL_TO_WALL :: -WALL_TO_INTERNAL UNIX_TO_ABSOLUTE :: UNIX_TO_INTERNAL + INTERNAL_TO_ABSOLUTE ABSOLUTE_TO_UNIX :: -UNIX_TO_ABSOLUTE -_is_leap_year :: proc(year: int) -> bool { - return year%4 == 0 && (year%100 != 0 || year%400 == 0) -} +@(private) _date :: proc(t: Time, full: bool) -> (year: int, month: Month, day: int, yday: int) { year, month, day, yday = _abs_date(_time_abs(t), full) return } +@(private) _time_abs :: proc(t: Time) -> u64 { return u64(t._nsec/1e9 + UNIX_TO_ABSOLUTE) } +@(private) _abs_date :: proc(abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) { + _is_leap_year :: proc(year: int) -> bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) + } + d := abs / SECONDS_PER_DAY // 400 year cycles diff --git a/core/time/time_essence.odin b/core/time/time_essence.odin index 72efe9f8f..b7bc616d8 100644 --- a/core/time/time_essence.odin +++ b/core/time/time_essence.odin @@ -1,18 +1,19 @@ +//+private package time import "core:sys/es" -IS_SUPPORTED :: true; +_IS_SUPPORTED :: true -now :: proc "contextless" () -> Time { +_now :: proc "contextless" () -> Time { // TODO Replace once there's a proper time API. - return Time{_nsec = i64(es.TimeStampMs() * 1e6)}; + return Time{_nsec = i64(es.TimeStampMs() * 1e6)} } -sleep :: proc "contextless" (d: Duration) { - es.Sleep(u64(d/Millisecond)); +_sleep :: proc "contextless" (d: Duration) { + es.Sleep(u64(d/Millisecond)) } _tick_now :: proc "contextless" () -> Tick { - return Tick{_nsec = i64(es.TimeStampMs() * 1e6)}; + return Tick{_nsec = i64(es.TimeStampMs() * 1e6)} } diff --git a/core/time/time_freestanding.odin b/core/time/time_freestanding.odin index 17a21d79c..7c67cc5e8 100644 --- a/core/time/time_freestanding.odin +++ b/core/time/time_freestanding.odin @@ -1,16 +1,19 @@ +//+private //+build freestanding package time -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { return {} } +_yield :: proc "contextless" () { +} diff --git a/core/time/time_js.odin b/core/time/time_js.odin index cfe54b86b..9a7163f38 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -1,13 +1,14 @@ +//+private //+build js package time -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index 0cfa196a2..ba0d91527 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,118 +1,34 @@ +//+private //+build linux, darwin, freebsd, openbsd package time -IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. +import "core:sys/unix" -when ODIN_OS == .Darwin { - foreign import libc "System.framework" -} else { - foreign import libc "system:c" -} +_IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. - -@(default_calling_convention="c") -foreign libc { - @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) -> i32 --- - @(link_name="sleep") _unix_sleep :: proc(seconds: u32) -> i32 --- - @(link_name="nanosleep") _unix_nanosleep :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> i32 --- -} - -foreign import "system:pthread" - -import "core:c" - -@(private="file") -@(default_calling_convention="c") -foreign pthread { - sched_yield :: proc() -> c.int --- -} - -_yield :: proc "contextless" () { - sched_yield() -} - -TimeSpec :: struct { - tv_sec : i64, /* seconds */ - tv_nsec : i64, /* nanoseconds */ -} - -when ODIN_OS == .OpenBSD { - CLOCK_REALTIME :: 0 - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_MONOTONIC :: 3 - CLOCK_THREAD_CPUTIME_ID :: 4 - CLOCK_UPTIME :: 5 - CLOCK_BOOTTIME :: 6 - - // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC - CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC -} else { - CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. - CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_THREAD_CPUTIME_ID :: 3 - CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. - CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." - CLOCK_MONOTONIC_COARSE :: 6 - CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. - CLOCK_REALTIME_ALARM :: 8 - CLOCK_BOOTTIME_ALARM :: 9 -} - -// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. -// I do not know if Darwin programmers are used to the existance of these constants or not, so -// I'm leaving aliases to them for now. -CLOCK_SYSTEM :: CLOCK_REALTIME -CLOCK_CALENDAR :: CLOCK_MONOTONIC - - -clock_gettime :: proc "contextless" (clock_id: u64) -> TimeSpec { - ts : TimeSpec // NOTE(tetra): Do we need to initialize this? - _unix_clock_gettime(clock_id, &ts) - return ts -} - -now :: proc() -> Time { - time_spec_now := clock_gettime(CLOCK_REALTIME) +_now :: proc "contextless" () -> Time { + time_spec_now: unix.timespec + unix.clock_gettime(unix.CLOCK_REALTIME, &time_spec_now) ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec return Time{_nsec=ns} } -boot_time :: proc() -> Time { - ts_now := clock_gettime(CLOCK_REALTIME) - ts_boottime := clock_gettime(CLOCK_BOOTTIME) - - ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec - return Time{_nsec=ns} -} - -seconds_since_boot :: proc() -> f64 { - ts_boottime := clock_gettime(CLOCK_BOOTTIME) - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 -} - - -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { ds := duration_seconds(d) seconds := u32(ds) nanoseconds := i64((ds - f64(seconds)) * 1e9) - if seconds > 0 { _unix_sleep(seconds) } - if nanoseconds > 0 { nanosleep(nanoseconds) } + if seconds > 0 { unix.sleep(seconds) } + if nanoseconds > 0 { unix.inline_nanosleep(nanoseconds) } } -nanosleep :: proc(nanoseconds: i64) -> int { - // NOTE(tetra): Should we remove this assert? We are measuring nanoseconds after all... - assert(nanoseconds <= 999999999) - - requested := TimeSpec{tv_nsec = nanoseconds} - remaining: TimeSpec // NOTE(tetra): Do we need to initialize this? - return int(_unix_nanosleep(&requested, &remaining)) -} - - _tick_now :: proc "contextless" () -> Tick { - t := clock_gettime(CLOCK_MONOTONIC_RAW) - _nsec := t.tv_sec*1e9 + t.tv_nsec - return Tick{_nsec = _nsec} + t: unix.timespec + unix.clock_gettime(unix.CLOCK_MONOTONIC_RAW, &t) + return Tick{_nsec = t.tv_sec*1e9 + t.tv_nsec} } + +_yield :: proc "contextless" () { + unix.sched_yield() +} + diff --git a/core/time/time_wasi.odin b/core/time/time_wasi.odin index 4a6c8afc0..9360e3591 100644 --- a/core/time/time_wasi.odin +++ b/core/time/time_wasi.odin @@ -1,15 +1,16 @@ +//+private //+build wasi package time import wasi "core:sys/wasm/wasi" -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index 397741126..20863c323 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -1,22 +1,21 @@ +//+private package time import win32 "core:sys/windows" -IS_SUPPORTED :: true +_IS_SUPPORTED :: true -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { file_time: win32.FILETIME win32.GetSystemTimeAsFileTime(&file_time) ns := win32.FILETIME_as_unix_nanoseconds(file_time) return Time{_nsec=ns} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { win32.Sleep(win32.DWORD(d/Millisecond)) } - - _tick_now :: proc "contextless" () -> Tick { mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { q := val / den From 2dd181e66313ac3a017c7133084d482d0ed2e205 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 15:48:27 +0100 Subject: [PATCH 048/254] Remove duplication --- core/sys/unix/time_unix.odin | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin index d9452c216..fa3a7a29d 100644 --- a/core/sys/unix/time_unix.odin +++ b/core/sys/unix/time_unix.odin @@ -7,21 +7,13 @@ when ODIN_OS == .Darwin { foreign import libc "system:c" } -@(default_calling_convention="c") -foreign libc { - clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 --- - sleep :: proc(seconds: u32) -> i32 --- - nanosleep :: proc(requested, remaining: ^timespec) -> i32 --- -} - -foreign import "system:pthread" - import "core:c" -@(private="file") @(default_calling_convention="c") -foreign pthread { - sched_yield :: proc() -> c.int --- +foreign libc { + clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> c.int --- + sleep :: proc(seconds: c.uint) -> c.int --- + nanosleep :: proc(requested, remaining: ^timespec) -> c.int --- } timespec :: struct { From d224679619e4b8b41c62d3cf1909ea05a39f569e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 15:57:03 +0100 Subject: [PATCH 049/254] Minor name changes within `core:encoding/xml` for consistency --- core/encoding/xml/debug_print.odin | 2 +- core/encoding/xml/example/xml_example.odin | 2 +- core/encoding/xml/xml_reader.odin | 50 +++++++++++----------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/core/encoding/xml/debug_print.odin b/core/encoding/xml/debug_print.odin index 7c20ac123..e9a1cb160 100644 --- a/core/encoding/xml/debug_print.odin +++ b/core/encoding/xml/debug_print.odin @@ -23,7 +23,7 @@ print :: proc(writer: io.Writer, doc: ^Document) -> (written: int, err: io.Error written += wprintf(writer, "[XML Prolog]\n") - for attr in doc.prolog { + for attr in doc.prologue { written += wprintf(writer, "\t%v: %v\n", attr.key, attr.val) } diff --git a/core/encoding/xml/example/xml_example.odin b/core/encoding/xml/example/xml_example.odin index cadfcfb43..f7e74840e 100644 --- a/core/encoding/xml/example/xml_example.odin +++ b/core/encoding/xml/example/xml_example.odin @@ -35,7 +35,7 @@ example :: proc() { times[round] = time.tick_diff(start, end) } - fastest := time.Duration(max(i64)) + fastest := max(time.Duration) slowest := time.Duration(0) total := time.Duration(0) diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index 151d44e2a..b77ae97b3 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -36,10 +36,8 @@ import "core:strings" likely :: intrinsics.expect -DEFAULT_Options :: Options{ - flags = { - .Ignore_Unsupported, - }, +DEFAULT_OPTIONS :: Options{ + flags = {.Ignore_Unsupported}, expected_doctype = "", } @@ -51,7 +49,7 @@ Option_Flag :: enum { Input_May_Be_Modified, /* - Document MUST start with ` (doc: ^Document, err: Error) { +parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { data := data context.allocator = allocator @@ -411,10 +409,10 @@ parse_from_slice :: proc(data: []u8, options := DEFAULT_Options, path := "", err #partial switch next.kind { case .Ident: if len(next.text) == 3 && strings.to_lower(next.text, context.temp_allocator) == "xml" { - parse_prolog(doc) or_return - } else if len(doc.prolog) > 0 { + parse_prologue(doc) or_return + } else if len(doc.prologue) > 0 { /* - We've already seen a prolog. + We've already seen a prologue. */ return doc, .Too_Many_Prologs } else { @@ -481,7 +479,7 @@ parse_from_slice :: proc(data: []u8, options := DEFAULT_Options, path := "", err } } - if .Must_Have_Prolog in opts.flags && len(doc.prolog) == 0 { + if .Must_Have_Prolog in opts.flags && len(doc.prologue) == 0 { return doc, .No_Prolog } @@ -493,16 +491,16 @@ parse_from_slice :: proc(data: []u8, options := DEFAULT_Options, path := "", err return doc, .None } -parse_from_string :: proc(data: string, options := DEFAULT_Options, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { +parse_string :: proc(data: string, options := DEFAULT_OPTIONS, path := "", error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { _data := transmute([]u8)data - return parse_from_slice(_data, options, path, error_handler, allocator) + return parse_bytes(_data, options, path, error_handler, allocator) } -parse :: proc { parse_from_string, parse_from_slice } +parse :: proc { parse_string, parse_bytes } // Load an XML file -load_from_file :: proc(filename: string, options := DEFAULT_Options, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { +load_from_file :: proc(filename: string, options := DEFAULT_OPTIONS, error_handler := default_error_handler, allocator := context.allocator) -> (doc: ^Document, err: Error) { context.allocator = allocator options := options @@ -511,7 +509,7 @@ load_from_file :: proc(filename: string, options := DEFAULT_Options, error_handl options.flags += { .Input_May_Be_Modified } - return parse_from_slice(data, options, filename, error_handler, allocator) + return parse_bytes(data, options, filename, error_handler, allocator) } destroy :: proc(doc: ^Document) { @@ -523,7 +521,7 @@ destroy :: proc(doc: ^Document) { } delete(doc.elements) - delete(doc.prolog) + delete(doc.prologue) delete(doc.comments) delete(doc.input) @@ -556,7 +554,7 @@ expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) { return tok, .Unexpected_Token } -parse_attribute :: proc(doc: ^Document) -> (attr: Attr, offset: int, err: Error) { +parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: Error) { assert(doc != nil) context.allocator = doc.allocator t := doc.tokenizer @@ -574,7 +572,7 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attr, offset: int, err: Error) return } -check_duplicate_attributes :: proc(t: ^Tokenizer, attribs: Attributes, attr: Attr, offset: int) -> (err: Error) { +check_duplicate_attributes :: proc(t: ^Tokenizer, attribs: Attributes, attr: Attribute, offset: int) -> (err: Error) { for a in attribs { if attr.key == a.key { error(t, offset, "Duplicate attribute: %v\n", attr.key) @@ -598,21 +596,21 @@ parse_attributes :: proc(doc: ^Document, attribs: ^Attributes) -> (err: Error) { return .None } -parse_prolog :: proc(doc: ^Document) -> (err: Error) { +parse_prologue :: proc(doc: ^Document) -> (err: Error) { assert(doc != nil) context.allocator = doc.allocator t := doc.tokenizer offset := t.offset - parse_attributes(doc, &doc.prolog) or_return + parse_attributes(doc, &doc.prologue) or_return - for attr in doc.prolog { + for attr in doc.prologue { switch attr.key { case "version": switch attr.val { case "1.0", "1.1": case: - error(t, offset, "[parse_prolog] Warning: Unhandled XML version: %v\n", attr.val) + error(t, offset, "[parse_prologue] Warning: Unhandled XML version: %v\n", attr.val) } case "encoding": @@ -627,7 +625,7 @@ parse_prolog :: proc(doc: ^Document) -> (err: Error) { /* Unrecognized encoding, assume UTF-8. */ - error(t, offset, "[parse_prolog] Warning: Unrecognized encoding: %v\n", attr.val) + error(t, offset, "[parse_prologue] Warning: Unrecognized encoding: %v\n", attr.val) } case: From 3fdb3dd7676145091ab73ec83388c4958aebe859 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 15:59:15 +0100 Subject: [PATCH 050/254] Minor style change in leb128.odin --- core/encoding/varint/leb128.odin | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/encoding/varint/leb128.odin b/core/encoding/varint/leb128.odin index f8fcc7de5..1cdbb81b0 100644 --- a/core/encoding/varint/leb128.odin +++ b/core/encoding/varint/leb128.odin @@ -13,7 +13,7 @@ package varint // In theory we should use the bigint package. In practice, varints bigger than this indicate a corrupted file. // Instead we'll set limits on the values we'll encode/decode // 18 * 7 bits = 126, which means that a possible 19th byte may at most be `0b0000_0011`. -LEB128_MAX_BYTES :: 19 +LEB128_MAX_BYTES :: 19 Error :: enum { None = 0, @@ -132,14 +132,12 @@ encode_uleb128 :: proc(buf: []u8, val: u128) -> (size: int, err: Error) { return } -@(private) -SIGN_MASK :: (i128(1) << 121) // sign extend mask - // Encode `val` into `buf` as a signed LEB128 encoded series of bytes. // `buf` must be appropriately sized. encode_ileb128 :: proc(buf: []u8, val: i128) -> (size: int, err: Error) { - val := val - more := true + SIGN_MASK :: i128(1) << 121 // sign extend mask + + val, more := val, true for more { size += 1 From bc183101077b1e962ee647999fb462fad15376b1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 16:01:15 +0100 Subject: [PATCH 051/254] Correct xml test --- tests/core/encoding/xml/test_core_xml.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index c8b02b635..a17594b7e 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -218,7 +218,7 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { written += wprintf(writer, "[XML Prolog]\n") - for attr in doc.prolog { + for attr in doc.prologue { written += wprintf(writer, "\t%v: %v\n", attr.key, attr.val) } From 536e0a8c291bc6efe5d8d3f34e6cab483bf9b921 Mon Sep 17 00:00:00 2001 From: Aaron Glazer Date: Thu, 12 May 2022 08:12:36 -0700 Subject: [PATCH 052/254] Adding flag values for FormatMessageW and LocalAlloc/etc. --- core/sys/windows/types.odin | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index c64207e92..a0649c5f3 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -1159,9 +1159,21 @@ INVALID_HANDLE_VALUE :: INVALID_HANDLE FACILITY_NT_BIT: DWORD : 0x1000_0000 -FORMAT_MESSAGE_FROM_SYSTEM: DWORD : 0x00001000 -FORMAT_MESSAGE_FROM_HMODULE: DWORD : 0x00000800 -FORMAT_MESSAGE_IGNORE_INSERTS: DWORD : 0x00000200 +FORMAT_MESSAGE_ALLOCATE_BUFFER :: 0x00000100 +FORMAT_MESSAGE_IGNORE_INSERTS :: 0x00000200 +FORMAT_MESSAGE_FROM_STRING :: 0x00000400 +FORMAT_MESSAGE_FROM_HMODULE :: 0x00000800 +FORMAT_MESSAGE_FROM_SYSTEM :: 0x00001000 +FORMAT_MESSAGE_ARGUMENT_ARRAY :: 0x00002000 +FORMAT_MESSAGE_MAX_WIDTH_MASK :: 0x000000FF + +LMEM_FIXED :: 0x0000 +LMEM_MOVEABLE :: 0x0002 +LMEM_ZEROINIT :: 0x0040 +LHND :: 0x0042 +LPTR :: 0x0040 +NONZEROLHND :: LMEM_MOVEABLE +NONZEROLPTR :: LMEM_FIXED TLS_OUT_OF_INDEXES: DWORD : 0xFFFFFFFF From f27f5955495725b02be137ec896a4601ece7ff57 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 19:35:07 +0100 Subject: [PATCH 053/254] Add `core:encoding/endian` --- core/encoding/endian/doc.odin | 23 +++++ core/encoding/endian/endian.odin | 153 +++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 core/encoding/endian/doc.odin create mode 100644 core/encoding/endian/endian.odin diff --git a/core/encoding/endian/doc.odin b/core/encoding/endian/doc.odin new file mode 100644 index 000000000..754ffa583 --- /dev/null +++ b/core/encoding/endian/doc.odin @@ -0,0 +1,23 @@ +/* + Package endian implements sa simple translation between bytes and numbers with + specific endian encodings. + + buf: [100]u8 + put_u16(buf[:], .Little, 16) or_return + + You may ask yourself, why isn't `byte_order` platform Endianness by default, so we can write: + put_u16(buf[:], 16) or_return + + The answer is that very few file formats are written in native/platform endianness. Most of them specify the endianness of + each of their fields, or use a header field which specifies it for the entire file. + + e.g. a file which specifies it at the top for all fields could do this: + file_order := .Little if buf[0] == 0 else .Big + field := get_u16(buf[1:], file_order) or_return + + If on the other hand a field is *always* Big-Endian, you're wise to explicitly state it for the benefit of the reader, + be that your future self or someone else. + + field := get_u16(buf[:], .Big) or_return +*/ +package encoding_endian diff --git a/core/encoding/endian/endian.odin b/core/encoding/endian/endian.odin new file mode 100644 index 000000000..08bde3139 --- /dev/null +++ b/core/encoding/endian/endian.odin @@ -0,0 +1,153 @@ +package encoding_endian + +Byte_Order :: enum u8 { + Little, + Big, +} + +PLATFORM_BYTE_ORDER :: Byte_Order.Little when ODIN_ENDIAN == .Little else Byte_Order.Big + +get_u16 :: proc(b: []byte, order: Byte_Order) -> (v: u16, ok: bool) { + if len(b) < 2 { + return 0, false + } + #no_bounds_check if order == .Little { + v = u16(b[0]) | u16(b[1])<<8 + } else { + v = u16(b[1]) | u16(b[0])<<8 + } + return v, true +} +get_u32 :: proc(b: []byte, order: Byte_Order) -> (v: u32, ok: bool) { + if len(b) < 4 { + return 0, false + } + #no_bounds_check if order == .Little { + v = u32(b[0]) | u32(b[1])<<8 | u32(b[2])<<16 | u32(b[3])<<24 + } else { + v = u32(b[3]) | u32(b[2])<<8 | u32(b[1])<<16 | u32(b[0])<<24 + } + return v, true +} + +get_u64 :: proc(b: []byte, order: Byte_Order) -> (v: u64, ok: bool) { + if len(b) < 8 { + return 0, false + } + #no_bounds_check if order == .Little { + v = u64(b[0]) | u64(b[1])<<8 | u64(b[2])<<16 | u64(b[3])<<24 | + u64(b[4])<<32 | u64(b[5])<<40 | u64(b[6])<<48 | u64(b[7])<<56 + } else { + v = u64(b[7]) | u64(b[6])<<8 | u64(b[5])<<16 | u64(b[4])<<24 | + u64(b[3])<<32 | u64(b[2])<<40 | u64(b[1])<<48 | u64(b[0])<<56 + } + return v, true +} + +get_i16 :: proc(b: []byte, order: Byte_Order) -> (i16, bool) { + v, ok := get_u16(b, order) + return i16(v), ok +} +get_i32 :: proc(b: []byte, order: Byte_Order) -> (i32, bool) { + v, ok := get_u32(b, order) + return i32(v), ok +} +get_i64 :: proc(b: []byte, order: Byte_Order) -> (i64, bool) { + v, ok := get_u64(b, order) + return i64(v), ok +} + +get_f16 :: proc(b: []byte, order: Byte_Order) -> (f16, bool) { + v, ok := get_u16(b, order) + return transmute(f16)v, ok +} +get_f32 :: proc(b: []byte, order: Byte_Order) -> (f32, bool) { + v, ok := get_u32(b, order) + return transmute(f32)v, ok +} +get_f64 :: proc(b: []byte, order: Byte_Order) -> (f64, bool) { + v, ok := get_u64(b, order) + return transmute(f64)v, ok +} + + +put_u16 :: proc(b: []byte, order: Byte_Order, v: u16) -> bool { + if len(b) < 2 { + return false + } + #no_bounds_check if order == .Little { + b[0] = byte(v) + b[1] = byte(v >> 8) + } else { + b[0] = byte(v >> 8) + b[1] = byte(v) + } + return true +} +put_u32 :: proc(b: []byte, order: Byte_Order, v: u32) -> bool { + if len(b) < 4 { + return false + } + #no_bounds_check if order == .Little { + b[0] = byte(v) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + } else { + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) + } + return true +} +put_u64 :: proc(b: []byte, order: Byte_Order, v: u64) -> bool { + if len(b) < 8 { + return false + } + #no_bounds_check if order == .Little { + b[0] = byte(v >> 0) + b[1] = byte(v >> 8) + b[2] = byte(v >> 16) + b[3] = byte(v >> 24) + b[4] = byte(v >> 32) + b[5] = byte(v >> 40) + b[6] = byte(v >> 48) + b[7] = byte(v >> 56) + } else { + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) + } + return true +} + +put_i16 :: proc(b: []byte, order: Byte_Order, v: i16) -> bool { + return put_u16(b, order, u16(v)) +} + +put_i32 :: proc(b: []byte, order: Byte_Order, v: i32) -> bool { + return put_u32(b, order, u32(v)) +} + +put_i64 :: proc(b: []byte, order: Byte_Order, v: i64) -> bool { + return put_u64(b, order, u64(v)) +} + + +put_f16 :: proc(b: []byte, order: Byte_Order, v: f16) -> bool { + return put_u16(b, order, transmute(u16)v) +} + +put_f32 :: proc(b: []byte, order: Byte_Order, v: f32) -> bool { + return put_u32(b, order, transmute(u32)v) +} + +put_f64 :: proc(b: []byte, order: Byte_Order, v: f64) -> bool { + return put_u64(b, order, transmute(u64)v) +} From daef39a206657586f14c7ffc021cfa14a675bc27 Mon Sep 17 00:00:00 2001 From: Vitaly Kravchenko Date: Fri, 13 May 2022 09:27:15 +0100 Subject: [PATCH 054/254] os_darwin.odin fixes --- core/os/os_darwin.odin | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index 21e9e54f4..64e195422 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -276,7 +276,7 @@ foreign libc { @(link_name="__error") __error :: proc() -> ^int --- @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle --- - @(link_name="close") _unix_close :: proc(handle: Handle) --- + @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: int) -> int --- @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: int) -> int --- @(link_name="lseek") _unix_lseek :: proc(fs: Handle, offset: int, whence: int) -> int --- @@ -295,13 +295,13 @@ foreign libc { @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- - - @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, buf: ^byte) -> c.int --- + + @(link_name="__fcntl") _unix__fcntl :: proc(fd: Handle, cmd: c.int, buf: ^byte) -> c.int --- @(link_name="rename") _unix_rename :: proc(old: cstring, new: cstring) -> c.int --- @(link_name="remove") _unix_remove :: proc(path: cstring) -> c.int --- - @(link_name="fchmod") _unix_fchmod :: proc(fildes: Handle, mode: u16) -> c.int --- + @(link_name="fchmod") _unix_fchmod :: proc(fd: Handle, mode: u16) -> c.int --- @(link_name="malloc") _unix_malloc :: proc(size: int) -> rawptr --- @(link_name="calloc") _unix_calloc :: proc(num, size: int) -> rawptr --- @@ -361,12 +361,12 @@ when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { return handle, 0 } -fchmod :: proc(fildes: Handle, mode: u16) -> Errno { +fchmod :: proc(fd: Handle, mode: u16) -> Errno { return cast(Errno)_unix_fchmod(fildes, mode) } -close :: proc(fd: Handle) { - _unix_close(fd) +close :: proc(fd: Handle) -> bool { + return _unix_close(fd) == 0 } write :: proc(fd: Handle, data: []u8) -> (int, Errno) { @@ -477,12 +477,12 @@ is_dir :: proc {is_dir_path, is_dir_handle} rename :: proc(old: string, new: string) -> bool { old_cstr := strings.clone_to_cstring(old, context.temp_allocator) new_cstr := strings.clone_to_cstring(new, context.temp_allocator) - return _unix_rename(old_cstr, new_cstr) != -1 + return _unix_rename(old_cstr, new_cstr) != -1 } remove :: proc(path: string) -> bool { path_cstr := strings.clone_to_cstring(path, context.temp_allocator) - return _unix_remove(path_cstr) != -1 + return _unix_remove(path_cstr) != -1 } @private @@ -546,7 +546,7 @@ _rewinddir :: proc(dirp: Dir) { _readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { result: ^Dirent rc := _unix_readdir_r(dirp, &entry, &result) - + if rc != 0 { err = Errno(get_last_error()) return @@ -586,7 +586,7 @@ _readlink :: proc(path: string) -> (string, Errno) { absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { buf : [256]byte - res := _unix_fcntl(fd, F_GETPATH, &buf[0]) + res := _unix__fcntl(fd, F_GETPATH, &buf[0]) if res != 0 { return "", Errno(get_last_error()) } From 9e2a847ebce45cb16f3740419a07ecfd27026ca7 Mon Sep 17 00:00:00 2001 From: Vitaly Kravchenko Date: Fri, 13 May 2022 09:32:04 +0100 Subject: [PATCH 055/254] Typo fix --- core/os/os_darwin.odin | 2 +- demo.bin | Bin 0 -> 710357 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100755 demo.bin diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index 64e195422..c36823e3f 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -362,7 +362,7 @@ when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { } fchmod :: proc(fd: Handle, mode: u16) -> Errno { - return cast(Errno)_unix_fchmod(fildes, mode) + return cast(Errno)_unix_fchmod(fd, mode) } close :: proc(fd: Handle) -> bool { diff --git a/demo.bin b/demo.bin new file mode 100755 index 0000000000000000000000000000000000000000..5f8c90ed9a208aabdfee04741d4f2304c9c4a40b GIT binary patch literal 710357 zcmX^A>+L@t1_nk31_lNu1_lNp1_lNXHU@@Ph6NxY21yW~fq@}DKETo4HOe)}A1sVQ zfHk9tU~)j_++YNm6XF^Xf+`Qz-@u6~2<5XfFfhO{NI`sjNoqw2hy}s%@u=pl;evQ3 zMjB!sh!64?L^T5g0}Dd~oDKF@aY<=XFBH#a{ICV+0^>x6uo0m2<^fJ`KAwKwt}dRC)W*QTzzq&Jh67+_3=9rjV8+#HUGLhl!X!u3=9pPTnxvU7#J>;aWTB`1lhvC5CAcfp^AwCjCC0p z7?v|JFvvs2u~837xfpD)NboT*FeorEFo4Pr{nFwh{hZ7s{gld_6sTHh1_lO@J5<9S zedEkER__#gY{a&EQ>779AH+`}-8^7FLek1FCM^aA2C)C2=4dc5G=R)eNa28rKq!z- z3#c8Sa)UWVo)uJr!rhXSnG{@ET#}lr=cJcXnUk3W(WeGAzrdA?L4bvUK@-Xcg%?PL zER=uX02hO(7)WFkkA}c#2#kgR`62Kpf7i-i>5MCX#51k@7S6o#i$BZCztyZO|8qNB z{O@dbkwaoa(2ID+pB$_V6F3?zC%#B#{K+XfA?Rf~<4;bh2|+Kj8Gmw0PY8OM&-jy5 zXF|{`W=^eF#f(2+GIOlbWaR38S)tALeR@@#-E&wToYgRGydc>K+`*!@h7MKgrJwx8Gmv%a!q_W8$>frd^sOP zvrl}v7^GKZ;>+bAzSzWSJDc_+R(&iIqNQF`L*(~Lj08yP0P zJ`dsxPJDft@u&7dhKaAQGydde)R_4CHseoiM#hP+?}OweC%%3R5|^9!`Z-Qj9b>i#KAX;wX>+c|W<%zFw?R|TTV`&p*UTKNvKe{1Ukfw+fS6KDF#-GC-0&GeId3YwTa z(@*XdXkyAtKe;cUiK#RF+f^rk~t9(8Q9NesVuR6H90M$^8XQESu>kw@f>#dHGB~xn0o2 zia~L&HSu*hh}M|+x*A05PkdbuqID;}Zf5%F&B!wGbvx5fZAZq5ue+Iko(83vex{$@ zq6`yXPiFe5-N-oc^>n76+6NgYzMc(ATSgOK&u99n&B!$I^1VGh!^F48LHf)mzCF$KQ@4?6;@k5~KXnf>O?-Qq>8CCu^TfB;LFO|}e0v+D z&UE72`yg}8C%%0Q;@eDo`y51DO?>+rByKnH?R%!5zKpCB-+l(!$2{@vcc!0b7cx$K z`pSq3A6W_8k|IBq{p7@rV`6q9q z&BV9-%s+W$(9{Yu|K#OC6BB3t$*X`SCe8el*9A>Xp7|%Q37VKP^G{w6G%eK7P0XM9C+{9Kv0&z(ylc?J!kK^iCbCX^8_oPvmyu=S+j!=m zXBn9$zD;KS+3UzK@ohTu&s>mrHuF#31892lnSb(LK@%%x{>l3SO{|>xC+{CLv1(A* zH>1X9J@Zdq0W`5@=AXWetP|h1Gyl|eWSRK3oB8KiMW%^w`0wr2Tg1_^H1M}tP|g^2Bk5UiEr04|2zxI2b-CH z_Iff*e7hYKccAYn}DX~JSaZV#4a=c ziFq%s+WIpqa(Y@{{)pnixCFPu?SFV%#i0eH+;( zzU626sq4r#@vSh+&$Eim6W@xn{OnC-nD|zj<)N2uVe5=m# zllKLhS=t~o*eAZ#2kBv-_|_PthkfE(bC#dEAhWGme)8Tx(__!_llKprm@~^y-ZyAs z?kqofIUOdx^=A3WtI~p+9{pK<@`|8|1+)D0O=O?=HXP($j)`xhS$>`c<&SulpS_I? z6W=C-+{!WWZ92=(Tt<$GZ?jo`@@k-&k z@$GexIb0Lp-UjL6n)voUNS{^Zp`6BB0r z$(w>ECeHekHv~;gn)N4d1)7*V>rdVuG%;n?pS&4pV(P3vc^Ua9zSU;^>6^$o@vT1V zPhCcyiEoWrf9g8&Onht3`jfW?O^-F}Pv3={6W`ji{?u*cnfTV3^(XHHG&Sz5KY6#H ziFvdBrdT-JQLq0v;I8G z$Tsn9I_uBggA5bjW`q2~JMnEk$S=GT-xh=1%RBLHIY>S4#JANTdA^Bn>p}8-6W=z2 zB$60^+DsoSJdm7|l{)unTv;I5_D%UQv{_K6rF!Akmkem4@zP$}{ zqrk+s_d)IvnE3WFDE*+B^&Dh|z{Iz&S%2mp^tjE-W)Ws z-=K7aCiWkcX3)f#*?#gipoy`w{q#M^J@G9!+fUtt0u$fzv;EX%6rA{0nC&O;6g0Ks zY(ITL@hi>tQ`b>&;#+yPpS&y3)F`w4#))sU zLGBTn_%+g z*@GMt-*&V8?3HAk__iP9SK*0oC$s&`1%=IYwx7HvXlBf2`^g)DCN`h#C$9&Z*kVvP z^GMZ+C<27Mb{VKS;gE#J7h* z{uP?|_Bh*5-WW8qPqY2xE$a(5KY5o<4SIW z#Gtpr>_7QkCkDM2XaC9XHZkbEH2Y6}_lZI8<=KDoM@$U*z|61pL7DyMduEPR?->QU z->b9#>b=>2^5?V%ePrg>;xFg`o5f$$5%k`l{U?7( zN6-Uiel3XE4BhX8*?;m&bqBoHcm$U!WXB3_IzMB1KzN7HO_x0>Q`3<_k z?t-{Op!L8gk#FMrI+Vk!Rxj?;!KVCcgg-3V)G_@Bg#^Iezk==?;1bN`L3NgWgMX z{N#Vs9rRwF<0pSYCu+PabNu9gf~H@c<0pSoXV80Xj-UMBx`W>9bNuAz=t0$I%<+@| z2bw-}j-UKoJwflSIez*p@=ttk&+${AQGDWiXO5re4{}X>@6PeFKaz3cdvA`P`i|lg z-}`g?)Nd4@_&%89XFe#7!#RHP^Pt%i&GD081|r5U*Aw(Up5rIKK~K;lcphTvexJX@rm!dIewm3{pZqP5xPX@%k2!wwPwNSK{~VM?(d1rp z{N$g3Cib4=C;zIRp!c6Se)=yInE3uX$Q_as-~ZXQC%$Lq{HcFX za^ib-&Y${>QWM{EbNIr(U&H2-xQE=jWea@f!KhV?|bN=MNf+l9p z`IG+{C=YP{^mi1T_}-rLr@o`q#P`meKlxvvsdeZ4$^WY-=)E`RPkx?WRQLLG{^b9I zraqYSCqG|r(ED)CpZo%7>Z3V-^2hZCy^rVo$)A8Gm(2N-zo<9peL5(d(B!f?fAW{0 ziRE+tM%|&Y%3VdV}6qgW>^AuAcKJ{~R>2X3n4d>w1IUw{!mF-+(69 z&H0o6P;b!teoknbW9WWAne!+AkzUk#a60Et{;tlT_p>>F^7nM2%FXBe$$zak=>1|) z_@e1s&iRx72AbGv&Y%3RdV}7t2Zb-1+-A<7{BO|2wsZdE|JNJzem5x0`%vxP&-s&I z)?woN!<;|)h5CZtALsnZFM_7#H0Mu#wZ5SD=V5*n?0$cl^C!PXAGm!0$wz|S@2_+I z~J|S%fxZHitpZqpx>K}vB9-6x6oIm;f`hwoS2BiTs_3t@<@&}-aedheh zpVSxh{yXPS|3txw?|*at)NhoU`2IiV&+~~q6W=p){p{~$ocNxd>u3H!iHYyIxqj*& zl$!XSpX+BnqvXW*!dyS~8Koz_7w7uPpMqw#G}ll5vd*CQ@?1aptvZ9=D|7wi@9GPB zug(RHYk}_f+FU>Rd-_o86@9Ls{PX&P-WzlMnBm zKh_uY-kIws{|PiXcdnoOO1(kvy}5q!tMsDU=g;+%-=r5D7yM?uLGOdPe)2Q*2fYvH z`pM7IkE%AB>nFbmM2ugoKj?it*H3nDE(n%Hu#pZrz*LGM?C;tx%3Jt+Lq#5QyN&{cf(G{)|Es z-|q*dE$NBx4|DzGUx23eI4GUTOniSDd*sA?Uq5_fP&66Cw6E zbN}RD3F_x?|KwjaG3dQF_fP)S6NBFSbN}SuGco8fGr!j3V3=E&yWfX%|Kwi-5r>$| z-2Fb9`zQbIi9zqG3fnj?w|ZmCI-D<&;67C>BOM-o4J4TKbsizemnP1{^t{e-tXrA z$^T+v(EI(|Kl#5;40?Z<`zQa8i9zp=bN}T3IWg${Y3`r=za|E~KhOP>|M$e8_m{bU z@+(Y&gza@CH{9m_>E9?k@%?@7pZW*oCcb~n{quYx@5J}dxqtSrWt{l_H7IQ4C%%6V z3Jdv(?>}?@%m?Ud|L6Y6&oe3LJu}Zwe%?tz@7Z~N^7Bm! zde6=Clb?T5(0hKKpZo%og5C@B{N$IP6!c!4=O@47q@efGJU{uBCI!8h=lRL6JSphC zGS5$b6^NVR;iJy;lizq!(0gs3pZq41g5K-%{Ny*C6!hMh=O@3}q@efaJU{u(Ck4H? z=K1OWPAjq(%UJM;WJ59+JB^Ze}J3F-^*{M0`vKk>ak&rf|ug^BNjd4A@D z%BygmpZqZpJ0C^!Akum~&rf~}h&bFW$vi*#-6sXTPv`l`?=dOpeKyZee$Pok@AG+n z@_S7RdSA@*lizz%(EDy=(9YJLvC{89r($Qrk_1AfR@|Qr&frrm+o}c{9 zlY-vg=lRLsGAZc&W1gS`e{cE0|{Oyy1-oNMh$=@+4=>2D&pZwD& z1-<{y^OJw(q@ee|d4BTG0;MOOpZv2y>52Cz{~Rl%{xp@?Qp}Dc+y_S0@F%x99!Ie{E9GduQIC{MRQ1y?5vR$^WDs z942tTd-ML}zk%jff8L+`&nE@F59a;J{{l^YI7kel{!ujVPyUyag5JmT{^Wl(Dd>GN z?@#{MpmfIjlm87Uo$>zU{|!oKyg&K>fYKT7PyWB4bjJIW{~suw@&4rh4@zgeKlvFZ z2fc6R{mIWiIp}>m?@xY#$wBYCd4KW?P7ZqC&-;^KXmZf|$-F=Lg(nBSpU(S}Uu1I7 z``Nrd`IRRJy`RtflV5dm(EG)_Kl#-r2fbg;`;%XNa?tzLyg&IZCI>xX=GS_%o)?-& zSi0YD=Kaa90TG9U155Y&?Yuwv%_j%F-_84z-*R%$`~AE>`K=}gy+6$RlizxB(EH=O zKm8YqOniTu_oqIi;>7pod4HYAw=KaZUgJ#!r-k<#5lY`#B=Kaa9N<{deA<{Qi@J z-v5TVnYsJ@f8L+|pfM_DzMuLB6(_!D=lgm7A>YLJ+TOa_mEK*E5r`@K5fPyYDHLGQKs ze)1NQd_Va+CkMSx=KIOtH96>gI^R$J?#V&#v-y7V_e>6YpU?M`fA-{{_r-ia`R7ay zdSA}>lYj2yp!d~$Kl$fP4tihD_mh7Lp@27*pKzZW(*|2b7=zc#RB&Ra*{bEqK$W45|obM<9g2||D*42DJ`PWYl zdcU6UC;x`YLGL&7{p8;`Iq3a%zMuS?Kxu~WC;w(pn&JD&zXgNG`F`>rn;i81GT%@B}<@tZ|-<}-wUYY+V|DDM}@74K#^1lY98UCOAZ$W8>|0n-DP@3WY z$^RadX83>de*mQ!{-6B+L1~8nCqKiKp!d%FKlvG_1ig3X|H;oZCFs33|4)AADM9c3 z`G4}WObL1)%>R>LcuLUwaQ>hCqEmw2NAv&W7n>6FKA!(4zxb4(_sRS}`AyKqiqrXj z@=Ht!dY{ezlV5#G(EEJ;pZsPWLGO$CfAVWg33^}7|C3*HO3?di{-69>Q-a>t^Z(@6 zo)YxFng1uh&Xl0{?fgIat)~RN@8Z#yOEeLw$Ce!D3_?Nl!Re7~Cir~X0JiSO6*|I7!K37h$U z@;gAnA0E!z`G4~JPYHUzoBt<&z?7i(`}u$J2Tlokf0+L#f6$bm_s98v@&``|dViY# zCw~Z<-t+uF`IDyvy}!)=lRsrj(EIEBKlxLq1iin_|C2v$O3?fJ{6G2Arv$x!%>R== z15NL9{-6BiQ-a>V=KslGF(v5zd;XvNl~aP=f9C(mUo|D@{dfMK{MA!}-v8$R$zOw} z_down{_ZJ3@0kUD^7l*$de1KKlfQRL(0guypZtAOg5L8B{N(SS67*hJ;3xkCG`->i zKl$fR33@Lr@RNVRl%V(W0zdf|P6>LiEbxH8~h0@x8ae zPyQWfdi@1{@*fAKGl8G{CqU^;;3xk{P&yO%$$tuz&IEq)p9ZBffuH`MvDkEhpZbhy z6W?bG{Nz7_W>&tyPyX9eg5DPk{N%p_N@oH;`R{_#nZQr}d!TeC@RR>OD4hxXU;eP8E_{sksl=cLE@_z)SJ%OM6pQZ%8pDpl{|MQff_wxmQ@-wnd ze7{)Wr+=dO#P`bue)4}oGh?;DPk!d9LGRZK{N!hu8uWg%z)ybGsX_0z3;g6~n;P_f zx4=(+_NhVd_Y3^=2aPiy7Wk>}s5bHaae<%w98rD-M|6SlGzy8#q_rGE02z0;yFYuG!U@GcZ z1GC^ye*39G@7V=^@;giode1HRlizV_(0hKtpZrc!gWd}Z{^WO_8uVUV@TdPm@rm!H z1%L9ppqU{r_>(_)YS4RS!JqsgQ-j{C3;yH}of`CBTkt1;*wmo+`hq|C!>0zlHx~TK zp8?VP!CVlUCI!0RTMPc=kAR3n{3p=;-d^x0fBMv*_s)Vp`7@^my>}P<$)7bf=)Je# zPyXzwLGS$qfBHWJjp+;if8=-nR?>*6MTG0FJf?e=PWu-*{Tk z`{#l``AyLDz6Pa{X+iJbgVM&dp!c5zfAYId3wr-u@F&09w4nFD1%L9pPYZhgU+^ct z2bx}Hp`ZNW(}Lc!3;pDem=^S&Tj(c$kw4nFyLO=Oi(DZr>{p6oK zE$F?!&`&`PyUP3g5EC|`pJK3TG0E|LO=O0PYZg#Ug#(P6*N7Y zg?{oso)+|eyU;zwHeC%+0r z9B!t(@K1i@=|S(6g@5v!P7ivoF8q_kI$nw?NZlEc}z-eR|M) zbK#%-UcEu@t%ZN``=E^@+YA5XXX_4n?=1Y2zXfeQfV=Qd{uO9q-oiimXP}As3;*;7 z&2a?_|I}|(oA^Fl_~&`h+*7ph&wfRwiSOfuf98YwJ;}m9`PZQ7O&9*j&({(3J{uG^ z9YOE&L2d=L4TXR5$8`j~FBksFp8%O7gNH@6@K63(9YOEwg@5wTL6d71{>i_tBj|m* z@K62?XmZ`cKlu-J1ikMU{>gs?O>VOAPyTBiLGPyv|Kz`cCO2F7C;zLCp!f5IfAYUU zlUpqOlmA~w(EH`WKlvHZ=BQT-|Ku0y40^v__$R+wXVCl2!aw=_I)mPC7yijVuQTZV zZsDK&3()lJ7yik=sWa&PVd0<6KHbhg@5wj>I`~+S@q2ocsM^7{>d-a74-hK@K1gT zG`aV}Kl!z~g5G}?{>iU{Cih+VC%;`+(EH!QKlvTdcn^uAi;C;zR!p!fA6Kl$&V%{euT{N#Vv7xcbe5cGbs$WMN!2|@3ti~Qtwo)GkYHYiRffYZ>2`AF_xEb^1z1tJc2%W{#Q{J|4~ z-mez<$sal)=>2+;pZsAHg5GZy`NfP&`BQeAta-)_#$n{)!S4-yatF$sYlc zhnsm^PnLeTr`B0u@FCj`B}E%MXfQDWlz z`yxN}52{Uk|5)TFe-4^i&qaRnS5F9f|61fHf9-^z_wPl1^4CoWdjDDECx88fp!eTF zX$_+1!*3+_{TKPk-vAMZyNg-$Cx8Ehp!e*eKlvw42zt*g`jdasgrN8QqCfd3PY8N1 zEc(+wQDWkIanYapjOr8LON;(IuP8Y2y}anpepRN4@0CS=>N~1We6KG0lYa@s><`+a zi1eW^`cuDAed2p#(VzSepz`KO^46k1^$)5~d~XjjUt{8XXOMXs6W_ao^l40d?=AW> zA2bH#FZz>z3PeBL?ZKiy`4>+JdLJ(OlYi-ip!dcH7CAb4)VL^#P_R3fAU{IGjqM@PyYL$G${I${~;(1ivHw(G$H8y zZqc9ok0%7Z-!J-;{|iLVhr>wjJ1+W@{|Q7K?!MEaKlwj{(xB*1{;!}kDEgEC8z>Em z{^b7-N`s<5`8gnK^FG{1GV8JEPydCI6W>1v#f|2~_pe2No?{f6`0l;v&mKpniEloO z{?s|BIq}VR(VuUaIac*B@^-)ZE&5Z3QETFx|Dr#6awZ17W#-X(%PfYFXBYdagwYk9Gs z+}sm`UMq|J?dR|Bv1EiZLy!+JP>iXn7-IgZt00ZuZ_iia?4H(dTlQD zlUr_L&}(b4pWN~jgI?Q<{p2=)=y~IeWR|INU66v7g-f6N6s+i~Zy_oEY>v zSnMaa(ZrzF;bK3zjVA`Zju!jrr6@J=RlL|wtwybhuad=nY8}*?_$poOrxv63#8=s3 zKeZgSC%(!T`>ENeJ@I9+*iTNAiQxUSFX4M<#eQ-+PYillE%uW$cVf`Xda<9Jc@u+P zHjDk_%%2$avR&*aX9Gmft8THMaDDw^KRF8^;&8FaVm~?SCkDNoF7}hNabnQR*xLNDPesWHp81!6N6rE z7yHRMZDP>N-C{pEr%w!exnJxj=ZuL#9E_aZFAj_St+VXpTE94 z{`~gX@#pXRjz7O&cl`N!v*XW~s~vy7neF)V%XG(|A0|8g{MYXI^HsIu&p+9YKi{W2 z{(KSd_>+yXA?TSo!%wzsjUO-a9e=WA>;KSaXb5^=&ajg|+h)fd$%acZ4bMKwfaJ>^ zf8JqX*f8fZ-^3V3hM#{J8-o5^X80My%J9>Jli}wH7KRNR2YDyH5N7=8!OQUT1Vcm6 zA7&1%7(u8UBi}?1QHGxxYz;v#ZZrJ!kYxC|g1I5c!;)dAzb(Vg7xj)m;}scx#;Y>? z)M97|dS%S;lM7^TG00sSKVCFL>2AlL{MmLp{z*1ml52SMNe-lDzT;0pCWfCESr|4L zI`K~wWMlYQ$j}h@RMyZ!%w#53_tmU z8FzYs)Hyu-#Ic^?=L=@$RUDfceu}(j_{sH|;V0L3hM)YO8FyxY)F?do#POfur#v%D zrJOzEPc~(tpKPGMZ?MWv5B7#j93V6Fg?_$R@Ay;LSm>unzR*v$W~QHP?My%Ug;{qB zGd5fjc6j(nxS#1K=VYdzBIlWYa*8wrab9Nn$#$LT=VxYltT_?aWh@Y9E_A?S@h!_OQ^hMzg&4M91U3_J5}8FtnyGW@JpW%$V@ z&-{~9nfWK1I`dEYV78r}j18AK9Ugt+)Mx(b$;vRno0VZgcB9fnjX)MK-wU*N-O+X; zI6ZC^ocLlg!%vNs>_0mg8iHQPGyL=brB_yl4W6tF8#E6ZO_Yyh`{~2VaKVF>;Q~h^ z@5K3yyq0hJ9e?VuL*05(aN-(HhM#ZuJN{e)iYsM?pKDkdz8B>43=-S9e#2fGyi<8?C|q68^h1n`VK!&aWVXSrR?zY6d#Co{&`Y} z;pa+FP{?BYXGZ;bn{pYzTU9&G=KFl>x7tSBOE}{G!}pr-m)V z&X>$iKVSTJ{E6c3c$S|W$t*uP(pi3j)Nx$q*m(lv?uJL7IPzH_WkoRyB+L#9O$3FN z9juIY6rKnQtC!VIKVL9A{geZRnY_bKf%PmuIX1KW-fa=_iLkL(q$ShMyeFtUuY`p&5#2J3R;CK3|AC!rSSmMmWRI7yeE^ zHKG}Qz6f^usR1g7!<~L=Bs2Vc5$*I-Bc0*ri+HD>8rcj#UnD#I)W~P}`6Auvr$#Zu z&llNFKQ+o3e!j?e`l(UP@bg8n(@%|hhMzCWoqjS2v;ItCZU}nxp5dnwE5lDF{)V7O z!3;l@I2nF|!@Sw)=Zk8mpYnnXKjjxPSjsPDu#{iPU@5!QVs&~Sh9yvJB(&KK&pD&u7P}Adk z)}JrL9e#3rhT8p|^(V(??wuK6yB~ex_|N+D#eBz~8qLtQfTqaA7wHT?H8dH1zU+1a z=Rr^!eF-xAu;Wj8QHGx{j)Q5-7t0-gYP5s(P2jK8Bv5>^SWBQ(P8mk1R;7(@%|lsMYkG-Rb9x^-e$K8yPL-TNy3oI~gtIdl@a| zCo)>fPi3@}pUG$`KbO%`-jv~|Jjnm{jz7ih*?w|3vq938JKIl=$vitdz;X5L6Nf(= zD4k5;XcU>q5zO}Ug}UQUP~5!O4D}N;_OjMP2}?TJj+RdP*?zvTcl^mQ8EW@*wx1lv zygOHb?SAx$V?G-solJ(t!&Q-q8lDV4U+#DM`NAGj=7ZxGltw^hI4GV~87yC%cKWF? z9jfQA$V81mhMzCbV@V&N^ae^}AbpxpeY2tZ1VtxmL_*{HveQo)P#OXG9TdheJ-VQD z;`mczKEuzOEDRGY^+hMXV3u5^k;w4#<#i-C-go->0+hZCq3O$z!Scm!r=J{)k`@PI5TCie8KGe zQ)4NTo~NP{U$8U$)F@>5`I6oF=L=&<-gs&30FG}?Q2qj?ElUQ=7oVMea!h6ioBfZ4 z;pYnuhMzBfJN^8}!|?Nk0K?B0|DAsR6JhxILW1Gv3vTD1|6~|`zEA+wBMv+NsWANf z@4#sJ--XfgzXzk`e;-E6{{f7a|3erp|3@%d{*Pg_{IA3C^M3=Q<^L8&%l{pWmj8Pg zE&oqowERDX(enQcM$7+m7%l&sF#P;)!SGY;Jo``1%j`cnue1N;*v!9Eow?x>`-2Cc z*zdFdWRvIk$*IiolT)4JCr7ZrPB-R;OKc6#KC$U@{5;0OFyWYk*hDsCj-S(57=E^L zF#LSVEWE0fhv8?h0Eo@As#S#HXSW2y&!wylL9G@HJ3DL`cFtE|_&HyN;U{N4$4|~; zj-Q<696vce3+zl`Zn(tK@aPjuJ;zVxW{#hSSr{fH9}t_U%);=~kb?ojZxElT%){{0 zP=Mj5z#6*G196$Y88Gib4 zGW>kUEWFB(m*JtAy6bN-YU=KRSi&iRwW zSZL=8SUQpC{P~bsUhAtd)079>9e=*kcmDZ;-Qnk3Zik=m`5k^fQFqw+Slr>~Lvx3n zALJdt?fU2H4nLo1JN$gA@9^`HvBS@g<_iz69I#=o9CD&Yv%toquw!VEFmc-1+ATP`d?|cHN!9X;@5^W@AINAaAIfMcAIWGbAIoSduggGmohA04^QSyB*H0#PuAh9u zB0D))K>7OFCk}otNSSg_Vj_ny*UuN9A$7zHd1sV1jXJ3AWo85S3sgb<0!~O>_0r$@ z=L>J=pL|ID0w#a1pYp+6KbgY0e)4S=*{K1uD;{c>qvS-6WG+O#NWd-+m|fw{KVSHx z*)^Z*C(~lCpG?cSe)0v2?(~4!wH|8MLCJ|6o4J0z2zJKkH=z3|17=UW^UoLIX!gA4 z`pNW}>nGE9uAh9LMR#U^?E$sRxWMHi#~NrI$SO7QL^G&O4K3I5p=rYrnl>C6EMG)B z|Kwoi{`n#vDQzSprHyo?w2_UJHp-oUzR0IR+OX&TDeuhvlgXX?C*NeTogHAmf!ZgWC`KbiX{({%2ie8%ECx3V-`5_Nd?NpwE<&#Np96Sj6q zO%z?s{c|g0Ly#$29}hjQR>0z_+WF^;ax-vGWDYE_)tm*hx?1aOMGr z9cbL-MZNP+&{!X6Ob0Z^2P&IhFte=!jq8ERrWc%$@jOtQ-4Cg}Wvb`-DcH>OQ?Q-q zC!et7PA=AlOI!|*K5_N){N&ic@beT4!vwER>4`6xxmKO%X88G{+xe#esQm@1k6oc@ z&lS|pb_TUo!SYJ5Fz!c6dy|pU-gKn2H{bc^i`g_td*^w63SQ>S_0ISQkJO4C*)n%uhe;UB* zvg^)2Uz~UTDM0PIY(MW$ro+5HnU3@R_m>sygxla?PGpM zNc;FQ{`$;=8Jc!(JO6xf9W74z`F=79^ZjHJ=ljWLEVFY3*uF=fIOO?$zF6$|lS7#g zl7O69&3r!vxAXnvE0*2)0&Lf# zPaOOCAZhXlsQl(ybz(Ea&lj(e%8f|Kn6rH(gC)lahMy~07(Qq|RGRpLnRC^N-3&ip ze0ToI4NH%)&@>jyUA>6&^x!xH#82!l5I->suVUb5_{k3P4>Q{;262X;tkMiW8F(6k7_1p~{z!KG z`GDKyC!Y<&PI(1}pYkdUKOf9@_{rqY|C1q@|EFL$|4+Wnayu2k_CET=5YPYf$9Bh` z50*RrWVp=m^8qv0sspDPem)R(`6&PmZ}}8xc&9K}g4JDTfVg#y^27rmJ@PK_xDm{N zs?A`qe9-Usli@bVOwLsYK(w;UPj0B0{5eo{IZ$=?A#r=+F=(vORU>?{A3ql_{jn4TUxvPWS3$1$)Nx>$KK^9 zJGINSIs88b7x4cST*Cj8FF<~$2V27>4uvb!mBuV7=E&Y+|A6kibI6qC#wX*&kWXvAPx(LonXJayP&qw-|+tw{J{TH@C*M> zz7O&{Gr;z}_{8yt|K|&Dm!B+*43^+AiEziC95whK3b0*| zK5@(ufRt75p=A{_Ae8q(jmJXgo=<6{@b4!SY48 z%TJC40zV(*yZn3s@+T-vqoH96;)BK+;$0x=0mKK5H6*)0(gcVP8gIyU0jCSFeo#II zrL%k&>h|64fY#y*{1kj5@RRR?;?5J`FnRWg;|(3M5ro8dqUuBr2f?2Ys*&s~$8TQ-)V>$V zjwt0CQ?1}n=|;hyf~|r-;)Cj**)EW}2gHZfJ@ctt_kh-! zUl#l+cwO+P{AT5y3ZQUec=UEE&_pC_~#f4(@3P{%(Ns%|QS<%{DkKRH+!A?esvZQ==I#-A@vyZnT;tEFc`)y`zF zd~qI>rWwKQ6>wW7Rc+!4YsQ~1F1tY5?CuUfIp#2c-3Fd(nG1CX50ainwTUO38GpXG z4k@cZb3rezGyLROfTV9BXq;4H6^8(lzNu;xPk1x_d~q9Eb{%*8$-IQ&CuqLw#dgP^ z5~7ShU)*>3xr2q_gT#Kdi8q27f6icR2$BG`&$$|cI94E;zYw7~hMy;tq2@5V{*<6{+u+)I;h*O>3;(>hUHIqy zV)dObKcGsUL4h#Q$!R-nTOVIe;iT}bsFEWe#yvQ!{^PI58P7V%G zTjIf|6Z|4SU+}yBlxJk91l3n3{6&6V3>Nu$F3oH9+bd9)3CzF9I<)S>)%H z`655hFBbWEak zcafjxK5OpG0I3I=`5&sDS@h=>d(od4okf3MbQk@3ZnDNlMLts4+`g~S81@TwF13_p*9;(?iM)d_KipGTz`es-`m1f8&E*a?nH zX;+lC&Y^zMpBE>K{=7I{^yfKa?VT&Y_C5S`V!r6l7xJz@Pb?Pw`A^;T=L==mpCG&L zJ2O~%fY!SFXWV(hoxu`prn>7-c~1sQd0z%g`9KCs`A`N+`A7y!`B(qrAU*XZG6YoVKc}d&#=LvnJbo^9(;tOYnpC_6bfBw@3`4Jj^+Z}(( z88ZHS;S3q~KGDqxNt@+E5GB<6Z&F5 zFFt1cdGR^p&vTcVcb)*PaRSA|d&Zwv^qGDhG8X%J$Xx8_{bHS+FJSJq7lXL>z0=PV z=1{xsU4Nc%7W?_n-Sy`SXV;&Hp!or8kGtznCeXShkbU)HKMysF{XEnz_Vb*u?oJNQ zhD#g`&pw^#7lXtpqsGJ&lf{1i^LPEJ!Q2q^Vm|{otY3IT-H#()IoOfn)gLWh&x`#$ zbXn}@q3dEl&u!M-sR6e0(Wevl#lZdY6OYAy{tI@6*x#rz5fok*xS{QdM$L)v@CtVQ zDF7PZj&}TcLSFpm3wMW~CzQp1UQ`$Vc|TZhrw7Pfhi9Ko=!-+l3e=i-!dU$0zi3x* zdkbXlzi`)|FQQ$4%5gIM{KqW3N{*M|C%Yhs&9+KTl;J0l_2Q6tsdxDas(+wm+69h@ zS`$yqW`xKkL-RkVUOWh`!wxc7g3AYcc-p(bu~lp0iN%aR|K)?yog+97z~v^WPkt1t z?#?J$L!}q8MS9_(`aqlMI%idP&Ze@#hO~X!_mE`19X;r=Ks% zT_JtJ!;U|t&qCFog{t?2)Y%|E9oP*ipC44a{*(ZE-+X=c<=P{ zfIq}t3s@K~FtBJ(Ja8DQuio{i3=6|g1=fb31J@yS30U71sJ>t%eG1wW51fYTYjy?q zeH}phZX@Zt0o50dq|ZWo;(^PIKmWD6{(NBU`11fe1Go-N0O`FCiAS)VcNi=m#Jm1H z&@BG*U%%_m2g$BKFS0ZIJOCQgNO%2tk)Pq`0Z{)x+x6!~aZnqT;pc;H*Pj>V8Gass z)+G=6U4IIyFi>?2+fvf1hu)JeL8Sn9MnFzz>os+ zqxjE%(_Mc)u!i{Y!DLs6+nI$|9e|d<5183j9e|d<3j`a24nWJ_2h&|q+V~(l5AjR< zJRmIb^P;%K&-=y(I~RcMeDvvnyaYJ?g2p5cfZ8Lw9l`CXv$q+4zMb#-a|39d;91)@#h0}x1VpfJN`WJnDOU})rd4E_!OGPo-$azSnv9CMIz(R zx5A@qA5`8gcKx{mR9=AipmjFOU4O2S1g(Q|{J8?O7G*og3@Bff0c`h+-L6#a z6K;!@__;n&;^)RxiJ$u~8tgm)Dk~TseOi$#@e?%8vZ7Gp=ZpQ&eEQkp=ZduwKR0fa z__=Yb#LsnwhC5$?)H^)E7oRr2RLL8F}V=f+UUpX)Xn?bHC<_vq7#SV?f)g2v2Oq%!_|dmdbl zU*PzvJ@Le6MtIzS;{OGx%zF!s!?%#KYegowjemiIRcGRf-=KChQu_D^Rr?XDHW#Ya zQD@=_W~QGn?z{fv2gN(6O#BK}`;`IQ7G6;ZRokjF@dP*1&litfA>&t|bo3Ld_9sZK z>(3RXj6dJLhPr#L&cqYKOg};G8Bm%6hX<%U{0r6d7qpfJlvX8wzWwa_^Tl~r@OoEp zy1VT9a}6&8B+XrS{kcYz0g~R{yZ&4w%kXmrXzePf4g1;kCl8g|p4;Y1{@l1w^5@2- zl0Vl48t?P~#ZSZ2Pb=0!)5A@ji7Pfr{(Sq}_2-N4u0OXBNDn92kkZ3%)bz0Et>n** zA0>Zo{3`i#-ACh{8DP5}ep>NY^5={Hu0K~WNDv)fPb7{`Cdo$?M0mK-h&mY{J3 zc@G9lc^?K#`2Yq>`49$6`3MF}`4|REQCq2>{EkvT1zn|n%1<=e*}&Csi9zAfCk9`s zpD(msf3Bzm_lqttaOgt%Mbb<^U+}v@#+KM!f3B!SDnDd(C!SDd`uRc_D$niqb48%k z&$r@k;5Hd3Zb5p%@fPm*6O<>wan0}eb49Pz&y5qMer}v9^>e?W>CP1(|0z8Bv|=vQ zUm&|0A$GlxcKf+vq14Z}%5Fbj$h-Yq1PxcPS;}tkcGrr#Qa^V;l=`{xsnpMP7fpAb z0Ga9V?9+<3Qjq-Ws5^1RN2#B0wcWsTkYM+zyW!2V8o~`hFXl7ugy&mrHt)5S{Z+W=OT?kX&xF@pnS^62pNZEWV8h5 z?M}v@Z|&V6Ztc{ac)}W_9#ZbY^0pu=R1YhonSQ=-N2nL)g{tRew0xoO_H%`!^v}2cZa-fbyMgN- za6Fp3{oDY{f7Wh4H^B0rx7*JR$obFTjp}n<+iInMZf}(Sxv^FH=Q=_2ogCcI^0-$T z5@w8g6IV=>{`of84Lq*|j^F#vc;oklFj5{2MvL3C(m&TWK#s`Srw8_joW!0fvV zwXacc;);jTKi@{X{d{rU0a8DP<4;c=B1rZ{quD1b^K-qT%+HOgGC$V^TI}=y+xO_x z3SAjQTAB)t@3(prPXsgle39%1AFq)YgvOsBG(F8^{JEBe;lf&8{fRG_c~_l?W`gvg z^4-8=9H6>F5HxlI>hFs(TE2*P`+34x=I6h1Nd5)2tZ)>{~z3^hPLHjl)F*2zQ2|)^YeVM%+HJEGC$9Kw%C~g4yR|IPSndl z(t)G?#1qXjKmXOc{d`gF_VYTlp9WfI0P5$U_m??Dk@{8jsBv;=zs%3`hh=_VJTCL| z++@q09bmg2eL8Vo2HaKy<=YeUA#GxCzFMk3@kBDy&wtAuf4*o(^ zLG?DIZ*Vpn5-)EjyZzh&ZR2f}WrVcxWEm~rHoN^ids*h^zu9g---bK>Jj>4T^KHA^ z&$HYN5I#S{&$r!fKhFw7`Qi*e-}bxxJSz?5%QO6ZJKgQ)S!F068ZU2WyZzkn$Y{CW zmCLm4giM>1OOk7cynugmareKJ@bfLGJ_o=ukhc`;q~=ef&PJI{jcd-UmSzAQL?-Y$3id3HG? zcz?~RLZ+W@AG`g0wc71x04u}KSBF7+#2A0RIu52SU-diwJhhtf=d0b0KRqj%e!kr9 z_EVb`vWC}CmGS4x@=DJX|x=^*-p=x6dCVEa}`uQr^@#o9KZa*!cW?LIV)f+Ne zzFhA1)3Z?a=d06hKVPnP1MNG4to2*(_S1)#;io5Pj(xKmc; z86f7KcKfMA#QJc1U520bjf|G|t&EoTos5?Dy^NOj6B#Y-r!rdF&t$Z;pUY@zZ_4n~ z-V(G{!tH0+TG^le7cGAVUbXz`m#Mi!Q@r64=Yc1mJnvdU!fdADM9+toKVMyT`}xw? z5wcEkz9ZP}D_IyWXihYk2wSTN<{vbe2wJbWlC2@=B{$V- z>ux`tWUYQWWorI-dD{&_-**GMWu;QXCC-LtpFA^lf4u$g_Vd+yH;|h)Xx=oM=*bA$ ztAq&0l`IS!G>;lie96rC6Xc#iLGZl)22Dnzi7(k1e}dfflG_pN9!_~A_q<1Q&u6F` zoq@h8|{^6o#KKFa-c`YQL+@1yO`OrC~IoC+^KdH$9A`I6uLrwSv3r9CTyC0MVm z{Lh!d?mq)TVI>ZwrQIQ6C5;qT*6u%FX}d$hO3(xnR_g9QBUl;0@rG-C=}UE_aM5;0 zjVFC~NZcqx?KOtd<`DZ&g6)Tfi@W>JSI+J~U)m#uOQQ)mTzoj8@p8}<946pA;j{24fv z@n^gt^G=_+j6c2aGX4zMDEl*DtL#s|LhGF`xj}1gUwrc1EBo_hu;Wk9zp_7H`n&%O zIH>qD;HV-v>^gZHE^#J2_~dz3@#o86u=$oR!`*)d@GAWbOw{=qkg5Y#cTx~!*0WEZ zxjK-rWwe;+S*Y{#RlNJpm(lJ&-J#(NP6v0*CceDS08Wbm@U&$=%>`X@jPs#l)A(8Gf#u$@KGOzB_n5FKADTy(!~Qkh*wNM$41? z8Go)~Vc6iMX)*C7GuNt>3z@+E{+HDV^#+#Ea?Fy^@@2OB&y@#te!gvX{|SnNm7uhq z@BVWoXx(zSAXicb(#W z0F?{P?mr3beIcoAYj^*-?XS+y^^CedH?r#fT$ic2gHs3;&yPN>F3Ggj6Yv?gWRn9^KCzpzsj9{ZUm)QUWT8V3=9`E4Ou6G`~~XEyzF=XX}^%c(tas} zrTt0AqsQYtcsP4~&pfVQZ z4u@x-R>tZ=(ow9%#Fya=KUXH|{(L*z{pZW+?%*~&I85(5K*IE~!_N(%bOZ{|+YaFH z^z>;6S_#_Yc;0d6N?5uvw3zs^o&gfR#*RN%!oqjH`_Dylb$>2isQYu{QiwY|!R~nU zY2{j7aGm>dvHQ=Hr=e-5&|>1tW`>_DS2F#4x!V1w4Je)uJN~q{g`@=)TSiMz8ajC% zs=n7^;>&1;pDQ;q{d{}a@#o9!?%+BWv}XOKH{;Khj*$KgxL$Sy)ramsS8mk(`F1~2 zc&|oFN9#dx!T?EUAb)R0OGmr0q@(>*PDkF3KexTr{kig^?$4Kp-G6TU3JJSRaM(Tj zwDK=B4)PO1b0wqR&$p-Df4)5K{&Np210-MIi9=0q)Hr-u?|>P1-i|*P+3NjV z>8SVf<$3p?8(sCl>8KOzhDV=P`szXAuiWY9$;*sCUnV>JT)C6!=iBRuumznf0M7fa zkoW`bF?%`R@#o3wpt8W>=gNajKVLp}{|QGW~q{+Wn^?G+mhcLe={+TE2Ym{`2I0#-IB@d++~)$`!d)D=$LR#b-#m=wxXK zdRffyb7deSxW5EW7lDkHFTcD0T=@{v1_YObx|S1PmNWcZc@wJtxBE|Zke%}#ey$8f zN>ibXmM<^6|6CcU_wy~Y$IqA7-NEBf;5>BO9Xt*N%DXS`yMxD}z~#|@cknnAs4N2K zX=V@Vx3Snges1g4`?+JH-p>tF^}yxON>IElc=~DOTs=ts_qCk(ayrA$l?(NLzUBA$ z`I6h?=W-&_&Ph*5+VKR{9r29dG5~A(5%&1G=&s(+9S`+>Zg>iD!%46k9)4Q+Ru5bk zto*3=^R2iCxQ+ykU4z06946L|KUeDN|9mO!@pGf0{?Co3`d~l21nGBp_-UoBK19Ex z{?C{49zVC#>i=BdsQ+_AD?}ZqaKj}|h9{p^_Uc3Ynrk_6E4b%HDVcmzJ#rbh=u6Wc*_X3YX#`cj=Po^**J7#jS18SMcc!v^m^aQ1-gwF!2F>~(eb zfb0$o-X4&BLLfe9ej(fg5-uPBaD z28Ih9j2sg+K4Z?}k5L&PNK{ZfM#Gho+r=XxahsL1||)H0^-+ptLg^WCjBy zZeVF=K9$qXVvnC@v4%el6AgbFrb5E%1URf7e$vP_{P|+J$4`wy!=Eo#L*i3pt>I7R zjfOu3w;F=u^d&4#_Zt3u5f6>igN8p}Zua>3V!a2r4MiwUPXrJVr`tV#D*ZM5smy5f z(~#8&9H$&2pt9`ICkmHD?_DYBya)L~tJ;mU) zTqm@de}dP3-S&WtrOtQwd7>V=<^wb~TFYqpVzb}R%GtjkAXVe*f-gjoSyzkCvdEcAS^1eT#<^5nr%lqMsmiMC> zE$_!OT52#bY~TnKnRr5<;pe~S9zS0^_8@vcBd8n@^ELX(A87PbFx2QLU#8s-JCTM< zat_Zv$;BG|d{ONH9``+wZ1nTrdyk(lUVHpJ4s-u~2k=^%7mpo&UJz#Z30gz+;qm43dE_5`hAvwR`% z`SU`&($DjsjecJI4hjDZQ1~Z2`*h;J5vVOPfrHg*;t6JB@H!o^f0aEU{=M%2o|^-u z0Z@MsG+zx$1K@f5CdhmRcz^wG#+@r{8Fqr&KQGih5odO2d;UCRZ~XJTv+>W1?#5s@ zc7W}E^y!4Z@lVhmm=%hkxh&70D^!_&zBTv!`9k0G=ZRoraM=rX3$$Mhnj?hd1?h>P zbp>F%%sq*!7f1K&$sTLKVLX|Le}otd;UDJ*ci-*&ou^n{(Rx?37=~`ao_mo3vbV#7atq{ zy!agA=Mx~iK0p=&Xux&!30%O@3Z9Hu-tc+yorvFTnOc`gFqH1RUlkoJ}C^V6>hH z3gZo&;PqY;I2dgvg2MT2vggki(VmF)UiBtFU&MRCb=ppm2b!*Lsod30Vt?rySE*04c{_#5?Xh zVb8D=5~j8jL1kD6X#L%8hM(|qtl0DCA%3%;7lqA!UKBS2hrtT4TONHnA#e5*l$K8@ zoBjM(?gRKf!Z!#b!VMH6!`2p0NL3EQb2;Jd*z^?I*qfomB%m1IgME;lFmzpNH0) z{k*u@?B~Vp5I4L4y8*O@7V5vlWTb|a;o{~*1d zKhHCp|Gda<4h}aCF;F@6?9&N;bBN!B&42!zj^ww=g#ET+8N_e@Ky}b`Pe>iq?Ep3h z6n`&}{Wjb4=OKUdpBI(YeqK~p1G~Y4A9Uu|i%%!?)qeh)@A*@Mp&{r5Gjtv6V$Yw) z^VNPH3O4_FC>-Ku4Un4;Jo|Ja-W(La8#rniC!R<)|M_pV=g$|*J%3&xqTXDw2I6K= zx`(E>4IG|~6JLPN15$>jht;0owb3uud;UB$-~8vH#pXW`Er+n>WQ(5{ zrbEnK0W$Z%qfaO1Tl{=+8B|7F{QP&_^XGkbhMyW74MCv%=fT|&1e)UoyD{AH=dJq| zKTkZi_<7>F#m^VFJ;Clf0W;@4)P0{Ve*Syx`SZnnPskcjP}+X%2@Y#PP*{V^(YO40 z!dd?333vIQ=O)|j?BIc}Blnm8`Qkaa4u-5F@0b7i@3kkmZzgSM`BTu;65Jm9CkAbg z*;@X5G1&p!9y{x7`Sagr&!2CDp>w+LJt1?U_Z@zo1&R#dgb|=dyKo@QQ=x zVjq1v+iwYJNA)}YJgd(HZUetv?(p+$F!RrU%bk9{{qOk`RPVh7tu^e0wy$~_Ex~pb zvQ9iJ4Q+>Udx6K4-_CdZdDa+e4l~!Pv(Zp<_`TrfYy_=60j*h_2sNh_FDN{jy&!YsAUwIR@#gBxpH~0pV!V?{=9uz_2%~D_i}%sBQ(8_XWl6f`^~Z>RbJMtL^pktg+S4 zxB6b-b~32m2ZwRK)z7!aUOz7uTm8IP4$+qj(&zB-)7g5czGkbRZ_T}aUfFN;^WtHv zpBIlq)b)bYH9Y%t_PiCSKHji4lx^bK%T_=C*@NQG3%s5eydD8h+SNUTCG9e@LDH@| zQrdO)`gw@o`sYPq>z^0Jt-=0V3%38!r?c|bKi`7Vt+Mscf9_row=}X%1g(hx)luLy z)5ty%lz%{Fz*~1O@Eph6Xvd#tOD#dc7eNp}aYXu~C5P`LG*K+K(N z^7E}fTDYAz`S~{3>*vMGCOvt^eWw#b_PT*+wj^JO^5 z92@Z1CZ|NhCC&v8Kdt1o0nZby6twyIGTI9~)(sjDdI=g6^|kr=GT!Uw`aqkX8$)ft zcok?q4z#xQB{SQqmC&`Voa&HqWAONHxfkj@Z?)IYMYgs-*E`z&+~{fx z4v$W-eV};;TgX^_z1Po`mXNmjOJ>PcD>IoP;_Y7WH5m3Yq4NPV87*IGyZ&5h3scXq zYGolaIP70e_5zP{f#)0MLebyQ?a#Nfy&!Wxpz;28FUS}%XwDEc$Jp)l z6Fi;|;)CXf`n|w&V&FMR&>Z7*FUUL!NFLPR1I;nc_M+|_<9x55+j?z(Zk%ZQbK_J< zSgizwRRd^j-xd@%6ErnBCazp)`}6H`ub<#_1MhY@}R^#xxZ_xicg6-nJfj)^N9p=sqbH17Kyf3EZd zt;uoxsnN;&^Tl~D$XHXq><+VwS%}nkaMEOM7y6a-+TQ8wShryeDT@~GIoyROqdL1$T%R_zV}`z>sViW z_WG%G*6yd$MZ2F$S0Q$4fbD$rN#m{^cn^`rL%W|Zzk5OKKgbC_-@}Fz+NX5n2A}N# zHv7BRPZ`kK_h`qT8nX63U;Os^X|8Dh(@@nOGDiV2R{=CfVGkMG`|tHrBbMps3wH0H z8i`DhabITdpBjetKVR~DgU1NK{^RzB_zyIG3(Bvcb{Z(Wa_xV<5cdA5Uugf+uoPlP z2FMNu(D=MP#O;mtKVOJ@L;Clib6xh@|9m0s{Zr|n{ZFN%_TVt?0O@IX_DSQcJ;aY! zc_wOHwEy{1-5We+0(OHk)D1YoxI+~wjMcqS!&uwFc_SYp%&onD^1#9zq~G8B=L=`=pB5nd+`WGq zfM{=sJsvWkaouMSd$PTMzKr(%3F_NxFoMRyz2WKd1)lk-jt3Bbg8De1bA`eE-h%=Y zU(AM_-3How37@Bmhx#)Z>Yik%d(t89$pE_t>hJm9kTj9*{!=3rl80X`clfDs5IPQA z42?t3`f1P_O3?W1LPpCMAoZD0^~_wWG)_X**CW&mEQOB!ErqJjg{oid@KfU=^Us&v z-alV7Bh(A7gsNZ3X!#=F`zJVjf+1&_y(osJ0}vmSZ_B+==c%f_QRk`Jy?<&bLiWMH z!=>Asx?_d?-apMi@jTi4r#Xn84h`qokZ|q*h4X@kka%D1{qyB=Q2K)UE8G#h#t<}@ z0M5hKjz7WaaJTo*7wf%$8iLH*45hb2%v%96&jD)QeyCZXw0GJ2=ZnMMKTSY-jzisY z8tRtw5WOeBdZBU7?*obR+ulDl3ZZcViu0S$IDCwV6Zy5!I9bbR`2wWA6sjH+=MSOk z-y_ruZiK4e2vrYS*9kg*6%^+$nSZ|g?fvt`XM}qGtx)w_87*JjhlUL(j#(LgzIcol z=g-mN{Ixe~oPS4+^B0S~iE1mT5Lj!e0XkC@R0djt)^9ofeDNO|@60}scw&dr+&Zqi11T%yA$mDvLF0c=z1luMU#k231m`ty zS>Xt7Yhk1rYoDJl^nD;{&=_i-ImA2-ka?haLXiFCNbz0^jYm+te}u-Pvk#=a2Icjg z(0JJijYsgBTSxG|3XPvo_5KL;f_tIr_d?aTLd^%oJ0r`_m&raqUj!r6^B;t&KM0za z_Cd|-&OVTO2b2y#dEMOyHLrX7pvHT+4@zEt5${8rcu)6%yVn|qffWsOZ zchx>WLGA$O>29B&FY2N39_<6EBbuS{)DF>`0n)qRDWt4e?DO+wzYn60;3jSj!-;38 zb;OI=&@~L8`WH6eI@t$OCr*c^m)THv%!jz61LO`^dU+2`FROiiYJk>JgYq#by>PNX z%8$*6_>?~ijnAWumM;$b{M6`$#y_Z>5QM5fj!-Xf5~}_rqa|p3TVolNUzhNhR#K0jZ8!WooLm;3yD@g168K<0tOn0A_z3%Bpj zm+ZbjUogAF^AhZgMU?tQ+V|%ReqTtrB@Cs-eIex*$UIp6A`dkSWVf;J&lk$Rka9~M zsz)12>qGQ%$bs58P`{Y_{sgB1kY0P=pD(PTdiVSMv;oyopt2Ms=kELSg)>CI21q~5 zFTuV)U;6w0eBtd2@e4>js6GIN1!#>bs2vKcFVlS??bV6$KQ*Sx|I{1v4H z3}n5{zTi0x=zCT~i_XUq_ih$OO zU-tPa2%;DJ{uBVwt9`*^o%`gVW1S$e=?*_Z`TpN_-=8O$8GgRp>e6?ICAbyxi^!DOX|T$8q1E zFLwL>JOsM4XT9&wiy(Tx@6QV$`mir#tQYK;N1sk)YyJSQTLPuWsnEO)DmxURWx;u0 zL|tbx&%eRJ6twZ6ITi(212hK37BKd4;&?fdh^ zb6@JVEnoZoyao!__r5=GgXquD@cs@7=NIVV{NMMd#zJTqfWp}l8qVx~h;Y6O4THOk zmM?_;erha*st1L$BUHUOLcQQasQQPDmM^6JerkZ$5rg6d6vm!V_3{Yy{GfGjp#H&A zMoZAVoyJG|Tn z<4yx2_s?j6)-!<0bv%1$K-#~{w=tA9_XDSI z4tYp92TtGiem`IQ_x%ZG2mAee;q3SG3Mj7Jp?bWbv_C|z2D098zn`Eu0PD^6`}rao zsy800CmBkoL-cwe>y`HV`6A!%=Sh%V^BsP^EC%V-{PD8f58{?)zn?Fvq5A6~`ZK`# zq4{#L!_R;JeIe^ZHZuJL_4oemcKmrFn&sy|VZWa*x)E_I{}vi=Z=vf$wle*Eu^O^I zXcwXbxbibcY(EVM3AEEoZJ~CRqlJ@&~YB$r*fBXF)d%QAPe!kl7`19p#KlnZd zebCxH&_1uPko{Pmdm-x>%|UC~K;l24;-Iz7haG=f|Ame*{$;d$Ip6Q6=TRoG*`1*C zK{*8{zBFh2>DkBv-fQ-9u^(hC)86r?CnM9(SF8OXd&(G@EMKa#=!P*P4@d4Bgz2Y-|=#}-_IE6KCT#5hM)0{jF$1PjF$19jF$1fjF$0%jF$1C zjF$0{jF$1SjF$1b3_n48K|C87Eni;u`5E8JXc^zhXc^zjXc<3|(K3E2qh} zJb4*1#{{;=QE=kR?F>Ip&W7sykI<(NT3ZH+cU~sQzQ5~CKmYOjgX;iuL8zI6OqMSn z`$6ir=YHThVsQQT+VAHDeukeXg&BUneDC-3f;a=DZe#ZUc|o4x=SgLTpD+JA?z}+6 zT+@AhhMy;!87*IO`~SS(&S-hRo6+)qKcnUS$&8lwr!!jKpUr4_e?Ft-Npr~j@=Jby zNc<`L|9m0r4=F>${UK$DG?bS22bUoyzfbg|M}uRG~b&0|9qkC4=F=#L-Vgb zRIf2a?+awT*8V?1cc*~M5P$!lFYKXuouPW%p|m$dFNXqB*%j>n^Tltdy~+MRUxY*T zeuvr{4b>YD(W`;1H{1W`i*$czS>ylnMLtw-vH#Btmlb|qxUK;9rv_-<)Po0~PTW`c z`J&wa=S6u%NO)93&8~--?SX7|yZ_G@+)%Tp`~Q5=4KUx1=*EyUFzP#b!u3bz(Kk&wuIeKVQuEN7RL&b#b7&N|Xt*Klm=w&llSv z>Q8Nkq{~;U{o&H1X=$m z%Vhatw*OBLQ2#yH@hA8k_WAyhbJ#(AQ2%zZ|4$FlSR#lITKBfxAAG+eXg!MuXx-ap z|DPVvu`CZ&hM%ua`~Q5g-JiPcjor{RyWjukc~CeXhQ`rxfAG4l4p2CQ&R)yb`tj;K zB;A4h^y;$z&ll(YA>;j@9e$nw$v^i0`Qkd%oZC=y?nBI3fo#rmP&#r4hs~?k{y$$l zM>FTQ|IZiiq2_#sn)4lE&Ix35{)5bcgz+opfS)h^qnX1W@bd+G03_{k2SCz~aKO)V z*;+eZDS*zhfA;AV$j`eSe*O~=fRt@t1t*?jX88Fk*zxBnc7~s?qTPR<;%4~y>b}Fz zQ~V4+UqRc@r^Fe4zPj!3^OQ8h&r|Okf=)sAPQMZl_zBtv2rlm$1tI$YA3OYfDIV|> zJRT_@@biUq0HiGq+RG1WFKY+g zz|R-!|zKA4*2=PKLF*v zG!`89rGeJjgUhkxfS)hIp>{_@>G%L}8uS3U0kpOd6c#Wyfc8v)+N9c!KQ#`r{Ct@o z@bg7>03@HML*11f0CpEBoL&|O{CtrgfLK=uGOIb@=ZkV^`lyD|^$>q&fb0aV=>)0U z@9^_qKQs<>g(jY0W&qDufzNH|4#2rC?%!>PpD+3Ye#(K)X=-=e2{HqoPWl6Wg6_6? zH9r8X27XWAWT@ZUq3)RurDsFj)d6-FC_c1)fa>uVivxaoyaly!0)D<&9stf$pgZkA z?Xo~lhM%DO9$u{v`1xWrDeQM@W0@g|_2Q zkCQAvU!4y4`QkVr+Hp#;;Sv|aV@RGo?(p;9^MIeP9tZpc=T~oEaDM&wIsm-q9^6j)9Psny`+%PT zpf(X`|2C+-^gH0^%kKd{9Wym|yi{zs#HsKY(xx(Y{P~JG@aN0_0g(2gw=a0V{44fA z@I0ADrq+*F;(2aRR$2SUeE0>NV_97>@43L0nE`teFR5He2I@Ay;WE7Q*x z+Fn03K>IS}1Ao4d4us?xP=6NG2Q7E{X$ak?!ok4sf#aY8VxNk9AW`da!EQ4S{P{v1 z>Naht+w>uB(*U~-G!LcqnH zZf~oR4AQ1RmE44JMbr8rq+%urG`sf4bMJtfzrxp2he?XJ71LtLefHUAmYpiW=HV8@R#h4KSAdd zAfHVD-Va|M2x<@ReAyiM^F?_e>bzui;7=z|+Q|?683>~5p>%s7q>T?hpFraW=&saP z-GSgWETHoVIzi_(925rc!w2ObFuze8u@ApH@F#eGQ-9!3(7HYkkU!=J{(Lbx@TWh> zuIW&_W(WRs%+%QN0u=TPk3NCbEe-_Vl?tj`UM>&(`C@S(yj};(Zx8(WVl`C%dMLda z9LkWkC0N~Vh&f>P`M{qq_6Pn90oi>R>W1S``ZPpu23YT7h#M{kVx8~MfZq4< z@_OLU7w;W@g3W#&`18f>z@OnDv+qOQ^B7`g2eO&3ahnNhV}Zl#J(9WqLGFi|`yFcT zZ-}`okj-TdLbx>;F*gHpE9g8Pu(|9(2=|Hy{d~b41c?WJC@mcH(=StF#|dO}r4i=7 zln?s(LOKW>4+5MFKLvOhelmgDim))y5Bm8+8ETd~l-7or`2yKYV`NA4%mVMCA08sckL(Jq*fy_^V!`~apO#h&tFT5dUg4_vOBLP+u zAN2D@Fx0GYC>;$kO9Nz<12o=~gTQ?mP}&299cT|TsJ;e;B{&?@k;1V&=;w=Us9E_? zx)@@n2iVN#khD}E1S;Pya1_cx%D3vEpD=Ub?IkRGK{eDH!S@cs=Gr?z=XW{EO?>eh zGS~jHJ?Q6)`kvx#$)2b!B+ zyoSu}yqq5N^Tm9qTNb0aWqr`k7t5h;Sq*VZ2iPspFyD+6fA1ZBzSxY2KTtXYr-|)B zKRrS5f8F8dtKC6}I`DYV&lmfHAaj?8gTQs)3Xs|0F?>)TA?WAJ(@5rn>V(q}^Z7yb z3wZoq>&L76K|fzy4*F>gwj&5qr{0E`cLHRd!}Cwza{G7C&zG;EVK);RcF%)|+KbJeeLk`m3%O`F9NQYoK z;C?K~o)_l9kaY9e;pa>1;Gh1ArauE!O~LIgPf^f*@<*RMbxnVQ&I<4}H2wMNJLs&L zVDLS8pzzbkHU0U*Iryicpw3T2Q5|qNtq=r-)5A|1vN}Isx(9>na4toipCYk3KLr`( zehRY6{p1t0*~!JzaEVLd%_lBiIZ$8jg4aR$iCltmKSAgBfzCPrl`9>fJbh7a;*0N) zyPZM%!NFpT@)KYDhVBh#Z3ueB%%P?!6@~)lCRuP!$7&8hM{sl z^)}k<)Bw#tIXwHM5i19Y$EWfWH4^21zVr|N`NBIGUIxAV?(p-4KQu0XJAmau?f~se zfBD;SCrAzym!NU)SN_32Jwf+Ig4RI#2cxX_beb#oGjO5Y&%mW}Km7u2cY1=(+HrXF z$#bn7WW3ec<)8iJli(!0qFg;lV$3KIz|eL|Ci~(Kb43at0(%btL)&P zVQ=MrIvFbbbTU-{x0POk{QBU*Cr?`i@H*4LT7{pUjS4?KTNQr3%m<}k%^jSevw=Wr zdKG@YEDrt|cvj(OprYc>Kvl@uL7p&qT}6l)xr#qM3l)EQmMZ>ySq?EH6Ql-ozEG{= z&zIG~p!18s=Q#!JRs89hsPxk_Rq5x;dWhPSFtxc#KVLNm|I}n?2=ZiPump!od+^VI zwMst&HY)uL*s28a15Ev1rJt|5(bV?`{|xx6^fQoA`DY-jGFUw)ALtC62cJB7m4CjP zjHW)^@n?Xq@=wn|<)5CR%0FLDhqy@-rYBbU=d0PlKSAeBz}+-I_-DXe<(~lym45~- zh1lTXE=c~nN>X!%q40x;jGw`GG&%mz`^_ejBf0ci}T8*Y2C>z7GjO8H&w!~aV1KTJ$^UtSLW8IY*{GawbB=OkFqgHN8h>OWszN3&zC`p=iQ zgMS8YRR0;c6{7wnSUui&FsPcThUf`1$fV znjO9xKRp9AetL##{CxQu;%`l``WK%(V>N!hd>;%xw@d@(@3|U3Jr`>H^jxa(^W|rV zdQY(W2cJCGYW#fp9b!IQ{acNnFMkLB4EU(=GvF)4J(*zjpmWhQe!l#VX1=ZF&zH<0 zKLZ>!e+IZ}g5BQjsD4h>+j|bR2&~b+9ke@HhL*VOuJ3wctfbL5Kjm3cc#mNA1 zD`*@Bz{^OA^Kmy^zVi0KWP2)Wp@a8&pd|yzt%rrD1+9G zYW;XMJLKn!$sv%jsOb>@a!7&3qM+kF{Ek0gE{BGHqarx`Pk`>Ibcftg`Eqf{&lk%> zesY4^=E4xQpuX2jaYwK?Xni>+Xl_CpDhE0X&faM!NDug431!Eh|CWdRgs&Z09`e&3 zdaw1%)geC*fyPhghd}0xHi!Itu|5Pc9=|>0=ecan9hy?0I_=RX@SL`~BY0l<<^GVL zC)t^Q{yQG>^X1_X$b6r^<4@2%AmI5v&{pZv9P&b?p z`T0U0G{&g;k9*2O(sX*<_m%BrLzIYx2?qk1r9r6=A&j7RcbI8vZ@1gd7hlE2W$XMC?qcUL&5WQ9iVjM02*7?`~f;I^xu3x$XWqj=ASRj z9l`q-PdsM%2^Nhp9`t|Kp0@^nTvKKV>XUJp;I)nX$Jd%Aol_s8GW`&$j;v5Ru zYvS$r^8{#*WIyyQB~z$v$ zC=F@uSP4qU4NoEI*xm8xzkFyqc2owZV{qEc4~5*1n;i;Se}y#W47!&Z)DHsP_s#*^ zM^hd8^F@9rYMEaQO3xZUUZg|qFNf0g5OuATH|4u{l(x2m@Kksle z{QSc#yy_109O*yIY^&}-&yl{P)ev+CG#7e4^yi<`p+EcV8FtQBX81W@o#E%7WXGRP z*>*qv9S;3D8${m@{rUTH=+BAS)<1q<5B=GfZN0-(AfY9e@6O9r{z7o#E$CP#@Nr>E}<-`VnU)%b(h=KP#=7 z!D@dpGpwq#hN}M_`qKca-q0PY-kr(v=kL&;mG%(zGg%lmSbkTYSn16A^Y`?ypFjVH z{?gl7^Ur=3h7IpERVMym=3iyu&HD3a zci7J#++jc2*%`q4#Qm9m{tyoP`5mOskIC};^U$B-%nU!jzYhH=z|QbfoSWh2_xGVc z1^5|$iVHLR{Qf!gr+_%aPjP96pWpezehSDl{1jJa`1wOT?B{pku%92)7=HeDVzm74 z#%TH9i_!AGAEV{}AV$moVT_jlqZlp!$1z&|*JJqkzlqWEe;cFa|1L($|9y;>|0gk8 z{-4HZ`F|Fp<^Oq%mjBHde*U*&`1w8A@#nWJiyuFf!+w6dANuoSmf4SQ(qRx({PXeVu%8ciJN{G%VEy?JbT@Kx800SG-=RMh zJea|94J-^B7%EgIDnvllq=)?k-%mZ?@uz|hl9~xB6BQCze?D9u_VYnD)ST^(KR>XC z{S*jb{`p{g*v}8U9e?s?vHtw9-SOx9{IH+TSs8x5pYQaOKaBb3huvX6-|u$(`L>Yt z=lk-opKpu9zKe|CY+ z?e2H{`Gz?RJYM9Psq^Daeb`Sh-5&PyWpfy~FY*#}cJu+z+A^IVZw`n3e04kwJXSo9 znc?Src7~txxfy;=av3d2vOOywVj znTkIQGZlX5Wg6_zlWDlb*YN0*UZ(z!UPgwWy`VE27*!|svNHVa=7jPc)hG7yGW_fY zodcPv_2WOc!^Hp2W)uHbyRQ7=@3Qh+xbw;%@lGp$r8}u{;g(Q z`GA?>=R?rFfXog*U$Q&=-%Kyy_6G7@F`sN6KW0z4#|z zbmfC=hMy1f8Gb%0X88HIoB@13+sk5ypRe*Ae!k9j`1yJ=!_QaK8GgR(2HnNZ@biK9 z0ow=84Ym*557<7iZ?FZO6ZRq^S~9Uw+{a zT8jfZM;J5~%dpiHNqr@}muk0CqzI0~z`I4Dq)k}AVpP)NKU-~=zd}Z(O^R=@B z_>R3d-VQ(C=!4d8F#LRN&hYbKBae!g7H z@bl$jkT~b-1_p)@4yb!085l%f=sWy;Y3%UxwY9^~SLO~sy%ZTHW-BsG)MRCt$id1m z@wGj}&sQKfLEUrb|9^3i9U!}21~dG85zYYi>#JagpD#gYNy;<)e5K6r^OZWo&zIT^ zKRcKN%w8%x{Cpt~vfp9n3wy?$FYFz6YBDGne_gOM=n7tHs`1yh#Y**!re5T45 z`A(IZj1HAAvzaPiW;<1KC~BhEB>}PvriYVZq9!N9#FwD^NY`TV)l~T;U_2!b1*tsYBD-laxkKr$&3`o54Jn(L{3-K%8R!D|4&EBi-Hb2 zH3c2OVYv{R7r}W{)M2NlC`9}rBjG%%{{R1U;_@QH#RuFBKOgcl{CvR;iVIMfbG~kb zmM5^XxK_{qW45cG(dLkm>y{jElri!RT>z%T__9^__Z{{O?w7aznk z{Ct?q@bghRBoAPeMGTO#2vmmtKgyr=}vqL{Qnmp~x`tg+9a2 zSK1CgU+X*kd|~YH^QF1NPk7nn%<%J-JHyYHQ1{&cl}*+TKVO2(0hLS9kg$3MDwm=i ze!i4u`1uN2K7q;;aM>j9@biT<*qu9H+-KPN;=Ti`^W|+wzCm~AOPC&5*#tT} z4OBj*JN$g5?C|p?sC)vgxrCNapz@>xR5l^fbmfcnjFm6eJ3`8))r^%dS3APXCgdqCSix4uR!G!GsDl9?9j4_-Qg!V-y`J-boV2tiT}=w7ynkX5XciQ zEB`w)g3}b-JxKX7k((g|B>tM);b)d2!$b{5hKUKR(7K-$Quhn~`#)WQi@^+3HiOkA zGEC$IwGkv8_A*L3>}60;lzS~cUkX&0W-~GfaWHaoIj}nHRb)lT!NoZp_9}8h#9xZf zm(m2Q=j760XE1}iVBH-=Q(M(xCl{;3PEJmTogBOl zJKq~K?0jd=u=A}o!_GJM3_D*dGwgh&&am^PHp9*r`V2cC*gNcfsPC{BRA++R_=2Bd zXCf0r$OB`Cy$_X9#TPO%ggh{J*!xf&Rs12SePQje_n|h#&7d-hgONc9WY!CQhn*Rq zHW9zWPf)$0@Q`65<3omtSN|cG!-(()rH#MU2tKGj!B(CPP&tgVtOmIoUJjGk9tE{4 zp=la!KCF%dw>NR5xst#CrzbEXwO11zEHx7$^);frN3?rEZbWTw4tyO4$}iw_!RYXp zkPlDE%PUgCKXq!WPti|IZA{)66R$@H70(JMn+|@Bja&Gq5sjU^u`&@gcLk z)WY%K#p~2g`pn zoCsR8Zm$Vd?`Sen19V4~A;VADJyiC#P`O64i5iX!pmi4$Kz4F4>P+NhVA!Dfk#VAD zBkNBvJCJcACo2PpJwdb3ZX#&T0W7Y_FcGw_g_D6{f@Yz`L{34d8b_;%pt%QmNrs>D zvJBvLr4TjN6FF63YHTNRYBKzk*Jb!AZ^-ad-jv~|yd}d=d0U2`@{SBY|5iJ%{Lc(2 zd;d3s;+1vf19gUNp2GR>^AIdxId?@Y! zx~CoN4zE-s{lW}C6$%mhLHasC?iP0ViL6Jf(Dmm^(3%98yQLX^DrBPSs{-khcKG=a zR7XSotexrl^Atlv&?|R_omod2EYbDkzGj^A!rfsfC@eH{U4LpEWw7MnY6yD3%%KJ9 zSO0H@hCi(R2<=BdVh4q{!_NoK3={uzGyMF|&+xPJCgT)P+Yi*nLutb?GhO^!%?{3! z4}}?iJ`!j6`B<9a=M#B`pO57oem(-Z85BOkkhb(IW_hhw>*Zq&w8FoGv2e}Qbb|w(vx7K6TWa2r_F0TSL$bVTPX`+zml5v>A40fcm)l3_HPU zB=sl00QKiUYrW+ee)>OWoB}E{QQ{UBKkN)YA96GNe8kW2^RY0)&nKXG5_kCd2$U9~ z?Ke=^qQ?uUPYg;gAA=aCJWyu%`4H3|1odI18Gin^X4v@v6u=0r~YYG)zF{ zJt)6{>bBRgzBI`F@(e%!3p4!uug$RY0mwb#4nJY-vj0?afAv7Q|1hX*V_OMI(?o?o zXeZ*zk>U*pmGix)}S#9klF7+X2a?yVNe{R zwZEY06`oGHL1(@*`~;;jc-n^5$++x zQWm@bmpu$WUx3C!xEX$OG%|qm6^E?@sILB~$jA@^3Zn;y876|tMo`wi|1S_7tce@IswoX9j591tY^muv^s~c7pmHns*&0fy*`q26J#)5(o8F z9e#2$a=w1S&hRsvfg$7tsD1{O+w9=_djkif!%r@eh9J#auGtKXoZYao2T;5I5i`Tj z$Dr|AP?`jd*+SD5JWWC46;%E`Xl9rQNdxj)96S&|g36Q!JWNwSX#zCH0BV0dWOw)p zDpRlUFik;;8>BWlwzLIme|Io2Y(-92ub4sOA`U-Sg4#U?rNMVS#)Ix5bYz&gnwjB4 z_C&6U-k`lftk5>rLcWQh{h}TV89;5T37Q+Z!1r~^vx3?=h<5mYXHYuf!CDqP0H;%E z9m(zR^EJD}&)3lmKVQW&{Dh51PU2;lk^$Op7^y!IG=>VUoBToJzz#nj`aAsG$jvb2 zfiuI;hwcnNA4EI+d>HTWa}7w$n&Ia|d(fCBXgq-7BQFy}2-uuN{fRG(8Gd?z>J)Q^ zpAU>3em*pJ0L=|dd7#bk^C75S4tDtYFx=tiERZ?S^5226AqZ@)G{a9&dq{yn;mZGJ zP+sK3+75UMs^6qRdo3M)z7lr$`I;Xz?!oZ$l`+Fl(0B!C{PTgn!_SAt4nK?TfZ7=# zafhETKx3iK3_oAEGyH5|VAu*yqx=jz#X;k{pmAenhM%CggNcL2s6h4bb7hC0&*U9` zKH+xw`2aMgvzT$>e{*ns_4B_y!%szqhM=d+@~a*(v#xr~%(_Z}(f-3UxMyCVPG(Sj8w0J%8Ex^pfrhGwu0&e zR`}S&BVmT0p!Nx)RqLf1y(mdQg-feD<2dy{Crr>@bghM z!_UX{3_n44Y`rXZ`1uMn=Kva8UyfvdJ}j)E_UAMFe31{D4`BHDda=XLSIZrK{-4e8 z^Z$H?pD$;F_Gvl%e7%|B=d0}uKVPn9`1xWz!_U{79e%#r?(p;VWrm-xt~306d79zp zi}MUWA7me}eURK>`yl;*?SuFRThLwNFR~eazT6K=uSjG2#~F4`1;st+Zk)po;PJhe zyCHKK;BiCHSXewn56T?IdgvTRw8PI=@ebg-b-;5N-V8rq`9ty-sP2HzVXSA^37*4% zsRxZ4S~L8737W?M&0#>(`0vZdkn?|Fj) z2zU+yI+pm-pW){V&^$)A!_Qa#4nIL-oS?LUcbsrPr6!8F#+ijW&m|o?$0= z4g*xbfW{Wl#|c4p5j#$p&s_N;-x)Gan9W@IGTRwGP6!JF@EiuTZOQ?g!+^CXKxu%4 zaYC4x@Hve23_D+}ci8#jIK$2t#~pTZ$TAdx#ve|A#;npEem<>t`1zz7ny*^*C%&A_ z@be^NLlEdr!3UEaemj+g{zT9{ zpq$JNK`+}Gem-h;`1!aUVh+45fz+OYwb!45+83btOJYdVv*k}Xd zKLN!HFDMT%e0;$^U+RA_!_WWW3_l+RJN$eU?(p;RW5$W#vcl0|A}Bp~ax?^i($7hz zhMkg2td-eeuBnk_Wb)l9n?<%wWmOHq%WBvX?zWI ze&la8qHXb9+TrIjafhFeL2(GG>p}4as@s?yex}xP%~k@n4H^&HJdkd%1*2T(3DccFh8<4FruJ zBb$eA{(oq@1U4=SE4QCWGyHri&+zjZsGh^s4h6*vBg02fIH9*uLGy5+zL5rKK86`G zm!!$a)eUkFs9bo!&#;q+i6P{vvcu0OpguLIj|82I0qbRU0N3FhwVbm#85@GM8M$6- z9_*UM%*ffD^Z);JkbY2IbpqOcV_*ff^^w}h+zvmVD1*`r2t(72H3PV=d}!_P^O3#7 z&&SG0=|-L5Cpg_`gYIH&2znU}O*iq-bOX8{+?e4fI8B*D(v9A~|I~D9wW6 zBa6}DXEv+DPwrao*$#}{-AuKdvv;vDOab``6o;Ikxp_veZU%5W3gKROpAb4vha3+W z;mFD`5jh-t7#TvIN;~{~f>uX^+6|y~5GV|p6&WVV;S4v#I0MLy;QYpj6mG^0KS6B` zP+R7aIVis|Ao80$G~ARKetPmW1ikczhMPY$+!~F*^Ft>=<$^vW+#dY>KONLR0s9Ly zH^J)gQ?rH>+@=m^VVDAP12~*m874x)3E@6aTN~C72bB#VKf=ayxELLNzT#&1sVN9@ zJ7+hzt<8SG_5pi?EvU@`vJ=rBXSfJ62i`VLX}RRtvf)$boXej)>wbRXVC3xf1kV|B zp2{=>?Q=Nr2^5xIpt*Ktu2tD!Iqp;R6!QYx$#sgMmUH%hXUN zG)KkG0P06h1o!C}IbU}%GK7HQ1XRv|=FGe@Gb}V2xlU!7f%d>1_ypc|u`L|kdX_>JC4j8aw<1t%U%$JCqr~W2+pD#^5_yJvbVIUeq)Ed{FK1^I^Ti z&qtCCLCuT}LC2UFrhxnmTHgQ)8;we>AE0#!FVw+(X7Cz{mr(bB+Z-@Ag6b!EP&)}S z_xMU4)ZSk>TSjCWa8uyeueOI6-Sk*cpC4iFWw;gqh(dD=WAU zdjT9y?hHS{;pEK#?n^xMcKE3z-4Mjg)DTny3MWwi6*M;~?(h>7PV5W}A|Stk@(;*A z&lwq}fZFvOERZpXm*BoLsK3qtZmWaF9k=}dFaEbWaOI!;fR+E7Ss-JWhe2sUaOK}> zA@H~YD7`*XX88G79W(~#@bkf8rirlrusA2v6wvqr$S}6;?1P%kx z80iCN#)+W13#RW8DD84GfyZ^hW4?!(CbDocO#zMDg6e~(p!Fcqpnkr?PX$MYiHw{M zKbd$PeqR6ke>&Vgeukf%jtmn)X&Sr+0<;c-mGkvl28NKo)j=!&Y=j3Pf-8mku&2&tN;I}qnq`ggz!1f zL4Nog;6M$ZJsgC?r=N)M@nv8L5nx~tAtp?Y-oOl#um7-w32Yt(t?l?&*x~0ReutkA zLH%LS*uFBu&qttf+{FwN|BEyH{0|z-m1p?5kAYzdXlxKP_maW*V>@Vk1w1~??C{eR zl#W4dPf3TL3=ACI$Yacq_KfRF_i2wKN%w3|k)?JN$eE8vg{XtzrVr*>Gstfa+s;NPF&)vBS><28Jmh zb)YeOSljjmT0g%FH2>Co%;te|gDohpgT_o=c!SC&P+#`Pc2N2PjU6h1;=$PA=VPdy zF!N4<^n=E(L1PHQpmy$$?I61!3Oj)33O<0s3$!MSh2bYZs9y>yuRvpaj6b%6+B%?d z0Z{z{F8dfE}Ha6_?gZGiBC}Zroig(m+?ko9D_iv90Nz9 z90x;@(w?@g4gmO+zc8w{9Db0I+n!`8G{44 z12ldMa%%@eLlCIW=a6NX$jQ_Y#O28Fk?~;XECFzyMywrz&yk>|UC=xQs2l^88LvR& zMxg!!+S~zXtQOR!WB}R4$ocvKJHt-U+5}LW2b8A3^D6R?{uM~>0Xt|e2PwTm$F4wi z18lzbku+%jiUGWa22^H)`beNYHmGg)AKEqs)j^;&LZEv21!#N?GdzBJ-)h8I7icULyv7K$_7da=dxoE&`W@8Xd4NYBJnr}zexlC- zg8D0ur6FqRKM#$g zN1*W`a61Z=XF+8O!$&SAhLA_v4nIM2nV>cnCj)eBNSfg%D2zbstUzIN<^TWbuf##? zy&QgWut3Ta0Z@AbsuwgjeER?Y=^!&e=Dh@^572mwFeGe`faE}8pmYkVr&$<&g4%c> z^`JG+p!5$et9U`<`wSmJ?MO!kh7b!-nWgOT6J#dHznlL5pAKe&%mU57g3>yJ!%q%g zNZhRY|9|>pP~V%|;pZc$nvJ0H8?4sh=dC~gr$6v!`1uetcCCKE_JMYTEl54c|FE+6 zMKa^W|N0C+|3mvz;voIaM{OR+H`s#aBwv8q#?ZW?!T4i4*gc>$qwMgr3*=8w7=zX| zvVhVMg9ylvAiskB2kQHP*6?vc!Vpw`fWlb=)IMcp_y}5ifjkZX8s7$CP~QQ(rVJMU z(hNHv%QNhJBFymfDQK-Os80YI3ucFm2ZP#npm`BcIP!q{1)w=_ad3a@br-mOgcuWs zjpaU;ci8zz+5tYVTExmU1+-4`Z#5r$o%xEq0IokXY~b=-@IUYkobFQFzomlx2AUsWLDa42 zeuJhXxZj}bKSAlNhd{c5wE;H}VGpRz1@&b?Y3dcUZ-*;AquUD__j@Yr09pfrlAfP1 z%dhf5wi`Ofj4e%r>K#xW4jOa7upiWa1FbED<|oj&4yYXkax*9#(DNHE_dVfv`1x4e zVdo=ZM4nRtxesaH5Vp4jG)DkwyCLTEK<)$eCs%>iLNbHK)ev$Im>qtC<|P-fGED)o zk>?n(wVyy?4Yq?FWDcTj1gf`S?f3a0v(V(h<2;~tDky(~=2F4+5x8#I0&$;%C1|`9 z%w}Z(k5zmJttkeL$?}8RMVznUV?9Xo`C#>o3|ql#GeB$DKy43kklPS*_aL`D0F}3( zaSZ5MA<$R|Xigb6j)c0_iwQKA^!FhJL)m@b{y;!{1Nd4u2omJN*4%?C|%Qxx?Ql)((FeIa1}A zxYFemEt2F8aTJ2-CXOOGWs4*^MvinjCazRDMGcT#5mc^FPDukKS12dURV1gVQ7m_v z1Ei){ZZZeRoMJf!jv_e$u0lBmivqc09O-fl8fkJZ9I0{)8Yyy*^%?#?QfB!3P@UoL z18s)C50oALK2&%3`$*g2?_+(3zwo)nX4;3%Gk=G_ADkWjesXvC`xzQGAB`RUJ~wyx z`xG2D335tYg<$_b0{KlNNp2BGD#ZT+d2%Wepm0c&Q{pOsssV)uNKG-AugsAm$H-A2 zr^J;84!cJj#bA072P7<&!16_MCR`x#61guNAax~jTR1@GmB=Y_q{uOF6w4WM70EGZ z6v{p4D3D{&NS9m6ktWArkt)Z)K|**sGhTdP&#)J?*7?6b!_Ei(40~VtJM7e`6%giN z6cl=4%&=3VR$Q1vmO+T)AYQ5FftVOfc8U}JM84pWH1EP*AI*x zcEZ*ry)t*$`NA5!j@}5gu274SE1N^}l+;URc`dI-hC&X*PASlOt(VLkS}#HCwHOl2 zUT8b)KFvYjNocO z`m7yxYS!`!a|ntVaWHafX)-bt!q$s3FmfWyvv%0|(j2^2ZYS6s>I^$Im=erDVqmv( zItad$W`M}aGweLUumd8;EU%@>$^e#oArD#~=dcr8pDHu#1g#Nyq3p2pC1`DkFvCvJ znmmq5hQhN?_ z8B+wQ`vxkLj2(6&`xn%v0foUUaZo&PX(5LfsN4hZ|6^zP`+(iyFM~spTr;>|i711i zZTy`tm>qWh=V#dYpPOOle|CnQoFCQ&z2pYbD}r9KGwkHBWDw%8Wf0=<6b=Th19-^o zu=fGK!(IjsMY#w340|84L)N%=gVwt6gXRl4jX>%^=5ja+3US(k*1bFIkKbr$TM98uj%k+_{j(wXYh9T`O+QSj$8$*FF;}pPZ;!J zW89s6fcFy7uXb1w;r7xM~S7|bG{(8mCu?n;=?-esAcuybbEH*DzhQe3v z3_Ce)GJJfY4&Jly@#XUQQXGt(QYZKr%t3Ag^(jH))u-4%=5d|kVB}n-!N{-$S?(2@ z94~0E6lm-kwD0fl$;p~O>o0@)jG%U-Ehr4kA^ZDaVxT_CD`Uu9`CEI3pC7Fqe(K$0 z_{hW1Fli+t&#n`UOmO$TXO`CzVCf_<|-h&2QWFbTfC$jJMPmx<})TlM); zhD;0}*E9A@^{8H$#miLn>8xPStPjivS|6DOv^^7S=o%dU0#elU0#gbt3YA@iX9Y=T&rMy1BKIrc88tdK8&`*PcFu~NgSMBt2kI0 zHogMK4c9IW&J&Xr|2j;A$H@$=41&mNLFz!`DWLHv(E4G}+7r;YF{mt%1ciOO!_GI*um(A)61f8?+V(RAzwMs;@!qc+mU;Xq^T#gO(;EqY&4>y%ntj_Qgo)=aJM$niPG%s8R=LKm5Mr5kd8y zA}B9_%z*AOexvX3^R2eS&(&-VLAi{)uU|9EugYWO`L%|T>Ej#q`BLW?89u&ame=ZG z?340RJUa`NSKcw}X}xFW)6)Cvc=?>_`B`3!e5*7S&(2!QSoKLuuxnPPVCSqJMo_ua zJqw&ym>GV;><76GG-d~CgM;cvP+kMA>EQgEaM@Fr#~74PG#RHr@)XlkC7p59?IH! zq&^7xnmf?EJ^Flx2M1`(4bdlo_pQ*}i0&>>U-T)su0`~602J1s z@n=vU?X9@O&)32ZKXd-pPkzJB@RRdz{p4&$o>OZXc}^{0e92hxYGct1iq9hF1DF}llSi%6b7Y9_|aQ<|d z1lntHg5k$D(0Dg^E*d%(0Gi7HwedjhU(nhz@OT$!yb-jo95naK%>drZ&iS|GvgTj@ zkDxh1jyp0PQpJZhExEQqG8`PHt&9N{#T!Pkh z;P%4_@Osb>lQ|mV@H?S?3rTR)N|IAh~SAu2~@ZoFTZ)@X?F0Z`L|ShN5i2 zo>^-d8H&6Fdu9=G8!S!3)^Nb{C3O8R4``kXx(@&}egd9v0gWT@gZ8aC`~;2pqNYvI zc@;t{|5gjHgw29F%*x5Ljz{SH4_-4OdyK;yfh zGz&|MpfVdYCjPfNc;%n`pq2lbA!lHK<|`hEgZeHGJO9Ts?Bw{%SeOComxw#;d{NA3 z`6Aw7=L>I!o!~J|ZRoxnZP0!mNL;@F^+o>57kYsDY2pq$xoX*jU$Qp{f!iU)jFz0D zpuRwZ5U6kVq8w_THN#HO`XW%D2h^?t&2PizOTp$jx)eG=-N4B(J4lm}Bl`uYe+BAq zh&$}$v~(y0@sk)BLRS2r584y76QmE+KY9t;_vq+Q_z04wcWTx02)|$k+0Uf~@{dL> zoA4|4h9GXJySN#-wO*GqT52+Kae>`f4Rs@Ie-Wsj1kFK8BmDf5-(ja#t&s2wMr3n& zu$rq~%O;%7$i)S=ry9wgdZ<0p3_C&df}nj5ptuFir^3PlJ*|S;fuMCu=yB=|nhzi) z&BFHK@H6au#qY55zc#~9(EP#+X2yyCnL+m{LdR_78Ng?HfZD%Wj0~2b{rbq`xVl8y zi`bL%Cx72cP#^VwGY9CbgPjjp8iMYE#>?Uzc0S-{{P~cd@h68YLm{YN!x6~vk%NaJ z`&%!Xl>!9pJ4h9AR&&t(1Kys^Wl4jp9^7X z8?7h4cnnqhoZ;t##|}RqK6m&z8>IF&!_SBJ8GcTKsqM9%_~J5D?RAEq4=y|We0bgA z=LC@2(+oc!o@e;k4O2VQdg6=2P_@Syem*$t@blquho7w=wYwR9KHLvp-w$!`QtOE? zHbd2JXZZPGv%}Aa+Z}#Zfz+;M`1x=>!_QKfxf`t~zE})ZyPVMs&+cVPuLpX6p-3(hMy1n8Ggot+H>)sF&&0N(0DR9{7za= ze9;VAFVGP5qMhOAgJy@H58EAnhJo}|GyHs5&+s!4rmvBq(2J8H1XO%^NdJ;9#_Y?B9uI z_?ZFPKM1O?;}Ls8n7A5(4l^|bJ+^1q>HQP3)(SLs&XAdDVfO$3^w*%V9nd_5w8PKG z_6|EA1T*}67|sCM7XaF$3)&;V%&p}DvJ_sH6B3sv>@*ZdH^Zs!Fwn`<$n!yO*5#?1KN)YT1WiK z+hOOcc!r&zeH6%Lw(h_G(?RV)Q2zkbFL)sh+Oq>%+r{t`R+b^#_1_t^j*EZg-)aHy zIxbkc0M%n0j;s@36obySZ3qILCGw!25xkFGLjYP=@-zHg_MdSIxNQNl2e$qYH17n8 zAJBd)(A=vg!$)UOT?87>=Z4JPzN%-O=)=g_{kquUrx#;G5HIJcS!-*#X7e)gzRqUk z?apR&ugYis#t_b~Fl)@0=A2Avg@$;e>I$;j0WaytiTZ3Ae15mMcG|L_0lu=PNH zs{>a4$@gFR-x;zl3}r1BXnq`A*E0P4-;R`jni+nA^G`cyPMiTe#snHy)nsHS;$ZE# z1=D|la=8kXlw&ymNz43_p4@ypYSl` zMTDUjBTqMIt!fsiz5&H2Xb%EttrbZBf5wSgj2zu7S;6O|TY~0RK=Xbt{xeQQ32*3n zOwby7(3$leY{ zhOOW-@1o5_P&|P5HY3k7f&2h92ebzh)X%;0@&9xd@LDA2mH(mV2f*uCX0wYgm^miC z0G02v8Nl~$zd*KQH``CJ9sAjSKG^N{^WlECpHDz`Y-aoUa68-2|IDEC=0NAmLCPG^ zz1`sbqWug%Vf#fvbqGf$!^am>7(!l8cKFE!s;33zG3sg1UXKUPCh&cm>;5rLdC=|l z^I^Z+&*OjpPk+$N_VZyo8|Yj;5s*Ecjt-SC|4UYa)=<3YX86g;0a`=-0JN_9wE_!6 z$ct`=pC>_gD=0w4K>5Af0d(fUB~FDGV0Dfx3?bm~d}uTAMLE>3)oec>l)L?WSnc+6 zCCJ`cx*X6JMmW{RFqive|w&j;>oKOcItf!DZjFg65%)@g6~_kVf-3$*Q`YCG|THPmhP zY(F1ZyZwA<@Ak7CWS=qH&xhu0KV4ZEz~RTy=wSJx-q7*@PeV{E%zSmWpAWTB&7bq{ z|8xtO`Ifd5Ur0m!BhU8pfwbGthw^Se<6-)R*?vA0N7di+@BefSnEpuHi7(jMetLlR z-*dD5e8BGZ^C7p}Pk)&H|ExbBGNbAT)in~xZoaQ?368T?+lenev;I5*3fu3jKOcN{ z{rT{_>rWeyUthESeE6Ofl-3}A1+C9vZn(tx0Fo9O8KLpM(01aB`>c?-e9Zdu!F|`C z4s!gW!Mvr@v>0#=%zGi7yVb zLh{XV)}Id!yZ(H5-1Vmz$jsfWKOgRA1+AHewB3%{PJFQ*>W9s&KOd}j{rPaS>rZZw z+U2Z2AFgKod6O9$UxE%lUo2+*`C_r_Pk1@-(01aB*--uSS${s5?fUcKeAl19LF62`JmhN=fi&2pC3SKn^}K8Y-j!XV6p4Zpa1?( z--B#tJu7Gp`~(hGyNNH#q5i67{rRBW_2_C& z?|1n5a=OFMSF;^{I&dDaRWNL_1+SUPhMFg8H}OR@H0` z-b1!vw+kKuyB}1q%>DC!`io-F`aag55BXVtzBG6J32GO=sAl*H+JE|@8agMK51O~) ze0>U3*5!lddt85NR%ZNonGF&LjiE4vc!2gpo7zo$!3+&ccGjN{m|cH9WOx0U3ku8M zEI%LqXZiUc+u>&!sLlfIS*>RH`RxCHaj;%TyNNG8vp~|(cb12k~rBH%RB?*Oig7&s5uzHnBXsPMGW)`9hat-{MjTMkEtiJ-j+pmnOCGXOwg z{^C5tPSCg*D6GKd9%lLZ@HorQm&p!4L1S?LceDKbzn=ws<{;>dyBDB6;?WM^{SBbA zEkS2jg68eA*cn1Vdmq4Vxa{)Nq4I@|!p%lo(0Y|zMx?M?%>qe>>sfw2Sncxj;d+;! zav=XKX8HMWIm^!{;SN7voOS`9mG|_#%TI7PfchA-Ss?w3`7A#l%y#+taK6jWL-Gwl z%}fnJrr-Wg2c4Vw!r$R12WV|8XfNt^m!BZJLHo}iYYjdQ7^qpAYn1em*pI`FR**o;u6VhuRQ(uY&mUEI%JAv;2Hu z?eg;k)ZX(TadDQP52aatJ}`IrdFb!|>8&7tfb#cjho1_Z2W%Ny8*RaM)q?T_%TEnZ z-NerF^8vHV&xh47=bLO89K0E(h@b~}pWSIYx1o0;K**Fgo0_L(^Y zg9zAdN9`uQC}sxVC-$P8`R9XT=bsPDoqxuG+>p)u^I<;o&j*{GfA;->#C;w}Jem3D z!*u4K57s;XO#J(QI(oYiZ0=o9eZUN<&*PbYK8SYy`7qx3rw2%XF!Rrc;Sl|Sxb*)7 z)ep=+SAgQrpZVtlZ|9#6{hfbWfb=^v|9t2U(eH>$zoGra7uL*>v}Vuz^MSSV&xiKT zKQ%!5jhTNwG-v+#f;kqEHUsS^zR+g=32KME&}aVnK->A}Lw)C;G9dNJ%s(HhGyihsRR;i7&XJ z?%@ZeJ?Ebf`JI2V{Qf^3+zw%8{`rud`6p;^s_~!y(-~MAf`mb4{bu_4@IMo{+ykwT zPvCC|;sxokcli0>Gt3LJl^0@b6Sd0}^kpU3|H7hk~N5OfUWmeou@AFgNmS;q)1lbS^)aF2|Gr=L@PLF2EO>F2|8CUE?L%Ci^hp@_8g(0<~JY$izB%4hodAlvEZ!+fWotsuLS znSMS@hsLiq!%tBBP6e3>TGJlQ^z&gn)6bXFoqmG$H-gryywr!DqX1gR2s$4F6rZ5> z0qAVf$xe{=fijl%K`=Bv!kK8CTuEODlv52cxYK2Uf1>GS9Rbg7O4IE}XBum>EJq<9*Hj_B&j+s^e?ENg_)`Mp*2j!LA3kUN z`QWqT&xhX~e+q-*3$z#GHsjBS_ZfeJ<^do5|1S>q11S71GeW}eI^)j=mmPmTyzcmu z4P?$~#-9(*Gyc2+ZRfu1N3^dQVjCcB)Wil`usMbd6JKm*gruSEj6WZ2b_Ad0|K;cZ z=?_*j{(QKe@#llxjz6p4{-4g#zyKcG1NGTK<0YW_paIkl2Ay3T%J30%Ml*O0Y%veK zt~&OMaSBL3sBTvPx#zGW`1~z_U(mP+WtjM)8yX({j6WZAJN|sw@A&fy$o^)=pAXv^ z!FR)e))6uP`ad1CAMC+l$Df|Rk@QzX_1820d{FK9^I^T?&m$oH#f(26mNWi*aNO~y zEvRhU?+9*hg8iMyF!4n;R9`;h&j;C#KOg2h{@e!Am(2L{VLIc_2m2j=?*9#`pN@gV zqZxlbjA#5=2l5Ac8%MPP(#8p9g!F@Q8797PW`vXj?u^SP#-9)E z8Gk+qcKmtn7sUK^AaP^HpAXF$e?IVb{JHS=|LG|pdyv~8qR=?&WSICunGw=IR%iVA zK-uxQ`~bpAUo`e?Am<{8`8J`(`J_OByusi-NMz-@c zbY9>+!_Nn=9ezH1@9;AYr2a9(&xfFS5N5}p>A(L^*8-il)$j20K{6XW-B|pB=Iw(F z6JH!>fRvA?8Gb%E?(p;BX@{RaAanLJ{Cs$r;pcF*)KM9|sy;QrcbhMy0XJN$gO+To`ONd0_< zpAQ!^{CsfU0i1r&+as|11)jt+{Co^OYx`j~!_No#&~;IuGnbPce!c?TF#x)2U@^nb zSIZfGzMKylLu2^)Aozgo1Mddg2mS|aAGkNzg68}|b0MI4k(Y;=CxXwX0-YaJ&G7R@ zGs{E{#@bDwJ_xA)2O2*H_fc3Fwu07BfZE(An4s)<2TDp!J?e;RYHb0LK$3PO2e&f8=lk zw~Gk5!QJ5}Co6a#SeFJY{^A{eY9QGSIyW8E#{`{$^tu{UzktTe9e#R&^>KB9-9d!? znv9V3ryQy$XK6Aregwr6gJI(=f?gey@J-M3Ldxxx?cgjt~nnxhtE*R%)}4^ zP9xwmppe1`dOj7h|3T)Xrvp&C3gnl!`3^f5urdgO($1@9hM(Gu4nNmlL6p?GE% zhhf(&9tOrj@cuK^)3Y=UyJmsb!Ggv-9%M7@drG=2F);sCbJkb{x4 z>jWEAF5UrjZv#C4fX=i5r56rHt}c+>eg7(OQ!V5NUiX7IU zZ~&e80E!nK!2`G6GV^GG_Q+|fo|(nP(GcX#$eX>2k+BeD56CZ|bBrM7%WG*e@_KV9 zo}Be^vcpf1dXRiJBV!>b48VQ{jaxzIQIYKg$1iBS04aVsSQ!PuaqJITlh0UqfB_oE z{-AkU#=>Ssh7i!0CfL28b)wA-KVMCD`1z_GbPoz7{6S%C!@wXS&%hw^(%)exNG-_S zptuE%;q!2S;+8Y}4R~!Hr#JTaZHtd}Bx|!i8H|T5}#WS-&{5O+9^G1w?J*uZ>tz~2^>@n<| z1zI}`N_#J1X-xIhEDpoYSs=fHHXkJ>L;iohMgUB0a zNW6g35IC)=o&~2fKSrMHE=I;e(0VLQ$XYBdFGe2kH_TjGpnL_2YhTr~v(_4RgYz7R zVfQQ!M&4Ckif3kdfz%_}19Ha$XNR4zJ+z>9F)W>c!VMH&U^U(jJ3)C8#D0+tTJtHt z3N$_mn!^X}?|2E_2LY0QsSIA1z4HZVAB8-_P7O8&6Oi}|(7iG84m&|(uCJ>detP{q zJ^58V!_QaM4nJSFL;2MVKVP*w`~=T2g5=v7e!c?T&BM&8rOC<#4nvUom-P9?l$H=pKEhG1?wSVg;gXU%rFfs^%);59Gl=nc^mA}?x zjSn7j19IRH@*bTgG)Q?rYYYSh|eukf*eRnUMnJ0qBmtQhW z1dZvg1f8!2I#)0mycXvY$AJf*K>J2OcN&5Aul@(EVK6>m`=Xg?B52R>i)gUAAW$10 zGzR(rdgdi~%|)dC#FwD?ap>BFN8S!UANxD}6r0E}1+MFK_}w3{J;)E_57@o}ouMM$U<*3C5_x?AYz`Q9*5aQD3{zm| zYJl5&j*Q^5nn8D`fci(UJ#}C?Qzr2ICwLA{95i3+w-R=D$RF7HA$Et0|C<#qzF>El z$njHl;)~CWKVK}Cn)u>73O+P-Lq+TYFf z^Fh1U&xhSe_7`*gd{NHz^HH(a&&TCnKj%+im;$yN)aRVe_46JmErZUbeb4yw#e2t} zFJ3c(-38jK`Qo)Bh43-{mq2i+;j`19p?h(Ast#n-FTj*z%g6q@+To$Kd+d#<1Vow z6BwqvJP&dUBn&lK8797jy7L+|ZoqCo4C-Su{si5}`r^3b&lkHHf4)7G;iC>K zBCWmJ4R!lNfr+o=xqki^=lc0yn(OCFaU{1(bN&3!&-L@aFxO9pM5x;t1tvaVZnb^E z4|TgR*Utz1UOyiSBf0%I=g$}aIe$L-?fLWZf6t#xps?QU2zCPl=pL&5Aa^181M23> zP=EaY%=z>GcTP|q`k>j#H1XwU&Y$3N?>pzum)|{qzTC|C^Tl??pD#B%{(J$t7k52q z%!Bi_mk~qAi_MNdU#)li`SP>pPtf^*p#IUz*PK6JyyyJ+^0nvB7wxqrSg_x}09+WY4#f9{_zg1LW!`WB!$CNJ>4YYZY! zS3CZE;P3tOVX*hlM`jH{&WsH~p!M}mUm2%>?p)_++&?)oGc8`2GlS0O6yi9@T-eOY5c0y@c_#-W zV=zapyzmQg=AB@7_cQ){>FxdVNAZur>#dd=G zBN`9+&@o(=Ulmez7qES`BL2b=c|0~pD&BKe{zU2d<2C(C+O^= zX2+io^1XjPEcX6+$Q&|OyaIg2F^ATpFN{-OtakkQ^1sv0SM82Jy%-%TU*+pmK49k5 zddbY7#lfig+6$IGgrR=n=l=PU+xzDWeuQ66g8TwXALY<+290I0FhJ4?Gc=spxqrT5 z_Wt>T-TUXOa_*lms<}aR40sPA%&kwW9e+M3_x|~?+WV)I1tgt-!tV()ht~Elj8k5I zcl`OH9F%_?EMGil+No(-2fl~yFatx#%jZrzUwn7`$zcl-1Fe?^uZ8|B3W&0Wy zo#3^X-?@Ii{OtAf#dm~R9U!wnVUP_CgM6g4_L>WlSKf2|eD&Jv=Zp7VKVQ{z|9sKR z4IV><Xr1F?kl)Wc z?R+5)+CQn84fpq9sK1|c{e1b@>*tH-2)D+9+zRtIFGJx$(E2uz8PA<|zMAg%(~Flu z$Xk{{2(&I7?*CM&U=BlWEzr7faJVpXYQ5Mj3b}LQKG)Bex4nM8xR0>I1!M;(JmR6@ zkqi!x4dAllGBiA{bNzgE+3V+v>s~)!#dH6Bk<1NVOM(&}$&NoC#C!jInC$(N$qEu4 zpmkcH@Gt^}N4z8GY#GZJ+mXU!JC^X63=NO-Tt8o)_WJqaJi@J@eTy%*JMDap90r^W zLO$qW;42c$;mM-~TECtRP7`S1(JTsCyMCPO=gY%hKVKY2*a2(nyj<`2^YvoKpRblX z{(SwI^XIGQAPgz9UxqXOe04Z@=Sy~niJBkzCcfG)yz~Ec&Y%BpbN+mJo%82Q?uJX8 z4lh2vyv_OZ|9Q@z|1Wd?WN?JlPka*}JZ`alah?;LuU=f{{Q2O#=g)_iJwav022D_# zY&Yl67yCJXKHBa1^YMPqpHY1bQ(l1Xj^57rGyC6vaSn!tN&nY#{`|k0^XLEVoIk;P zch+-)+G0U3H*@~f1ho&hbN+l0%m}{Q8a!{{&-fEGhTg@(5c1OB@h8}ypmt~???g_} z+&+ly$UE`nYR;ek7jypnznt^ui^ZIv`QA$$3NJprSkC$L|7^~m|L1f5d~o>)qx;cNo=;!?TsN3`B<9^Sd>L9=PJA&PI^FPEd;Bai@ zo~Q-72d9|xXBUVr=lrR~*bwxpn)Bz&Y|fvV3=Kgq^ErP$*nY(J!QmF$m+hc>lJVyY zZ^oZ5-5r0v@CKDPka2KOx&-<2l{3_z-yMIxZ1()ARhjhzY?mVU#8>H@KmW&b{`{ZJ z`SWEwIP5Ng*5JKN=KT3Tob%`ZXwIJutk5`fdD$eg1zoD)1R1G+ookv7O}Oshca*d8-;X!*SSKmDz^p zJMDaB@3ix!vg6Me>W)7jX*>RW_S+FW2k@BPVPf(D_KE+0bNu}OpX29$X3n2_3=Ki= zeslcn1JVCEe(HhOA9MbE%I+|6sRH}Nx6e6#zIn~@^Yv}Pov-c-?tJ-~J~VwhvQ2!D-eUXW zG6y8Dg5)lH{Cs%b1Cdt`bNqa9oa5)C!yZ2$ANTl~4~lnwh}&T48??{k0lULQ1_joM z5B@Vi;ys)ZanIRrhM%&acn^2n`RF&e&pnaBfo0-@?+ic1zc5aD4C)7dhlw*!{GZM7 z^CUCF1WiH9i7)pv{Ct_u@spFaA?W2{hMzBsIevo9kA8U`v<{2oCl^yg(5vI1F$<2L zuj)B|aFF`U&lk}gKTk+D z1id)Vxbwwsft@cvX=c9wA}zgU`1vB41Co~BgYLED0MA7|WaiM?|LXtrxBDG_zB%mh z^Yw9upRZ0k{Cs)d;pdC*4nN&7kv^Iexyi=J@%>p5y0BXO5q*-8p`~ z_U8Ec%Ae!s%V3V5FU&c9{@3UD`QMlWk}nh(Cq7uu0BYw>;7GKe_(B~TN0&i&@q*Hi z{Hll78Gc@T$T;QQdWWBHFFX8vbKl|T>&Fg1U!8Z{`SP{H&lm3I&++rMFvrhV+5$VhKw+sbu=Axf z$Iln?96ya;|DXPDzr)YBhaG;tIqvZD^=Xh@4nJQ&_tm`o?(p+{vi-#OyBU7|f6xB& z-DmcnZ@;tueD$0C=bQiRKi@EO{Cv&M@$(fo$IqAV*?<0j&i?cNYxbWH9@|ZPupPXP zeu8G9{lu5|*?+!x%>HxML&hm@x8wHLJCMJ2JN$h2oc-t9*X%zZAGVvQ%h(X~_BQ*^ zhtJ)AK6>r`^NF(E#5c#;f4)A={`1vr{+(H@4MDHw^Y46lnf>RB>+C=Oy&}R-%C-~V ze`fgke>wZlcdOZdzFp7$^VMedpKrFa|9rEX{paib>_1-}X8-weIs4E5^Vxs?U(EjV z!D5?<4{k&KlxaWl<#hI+FJ`mjKKswNi`joZZnl}&%g_+?b~5|V zhx6TkK3eSl^T}iDiErxJf4**J|M`lWe`gn4L(nUJ{+%zo*?+$1Xa9Nf)&J?B{Zrt) z_TS;B#b?GTFaJCIe8KGa^M5(}&;QlzKQ%$;;j)9uQpTSz*&Tnr;D(kn%OPhp!|Irq z%N-zf>3WZ!T&xY?yK=z$yjVf&1g$4N*xqdWKbigK|8(}BFOu0oSR#0RUJZC^x#)dal&iATHtd>HSJC~Lggf4=Z%|M|$<{pVwU_n#p> z3{ze#2i;-J`13lnZwl^zg68}{cYn+WtDDlbrAOc30j8_TGI&ce@+(P`Tsok&zGmYf4(^H{qto%!%xt9 zdT{@9Iw-As{{*dl_W<2NgscB~oBQXB`&jyK(;a?3xb6M(;eFKp=c12{Q$TCyL47xH z|FapK?>BHbg8QEvaP~hRbN_tx9NPcnXN2@0VEs>i(7hPY{^xs!oi8qX{{)R6zr61K z6Eq$VcR#lN=V$JpFTP`Oe?4eDllRYu-%UE*|I7!UUjgcWZqS7FKa0WnY6H&x=Wp(xFaM+U zKl2@aKKSkZ^WlHY{-+Zt{HHtoe0kkr=PU4DB!|jZ|Mjr@ddX2Co|8_7wkNsJcHW*Ob6XV=JWF*yAMYHQ~V?2l$W0! zf4%^%vt@O#e6gPabZ0W6|GD2`=ZnvvybQT(72XevhPrz*_s^H>y??&gjBq!&|9KsB zUJ!#22dw|O-(e?cJvP?*8|9tTtOaIf~;pcY+_-s?l7yU@#(T^oOq@m$4nfvF< ze(#?zCL`Pm?tk_>?0k(J2ITZVxy2#<&u;FYFWW)oCBhC+zYMfL;U#FjIcVL0HT%yO z_8{#3(}R(t8+2ys%VNKsFE(3Fe3{R;^JTN;#8<}bKmTj9|NO7d{_~|aJE$KB>C5P| z|NO7a{`0>&`_C83>_1;{H(cUyc=73lI{VN6((FI~%d`J{F#WLY3ulXo589h;A51=M z`=Gno_JuS%IK909$xFNcd?@b@X=g?1Pkg})t)JN0e?DS%|M{5R{pY4GP+tdB*D(A{ zhW1ZE>pVewIKh2S(3(wNhL2gS3?ZPqmOy?4&rc-sPUHaf$sU_eeDR;{=ZnMU6JPyi z`}zMf+t2^s*?zwG4Bj^c>3@D_`}zMh+t2^+*?zu!&Gz%fd$yndAG7`Z|D5gTgKALy zX*TgecC+n+^24?d@|$g6JZ6LV0VMy}?dQYiZU{e|W&@WgFV3_5e019F=i~EkKN~@Q z0IkEcXZU#u)-MJ1F+pdYn=||b-E9L}w+Z%-KD0gs`9~jG|2KR71p7tVY~qXKY(HNx zn@xOmnC<8P-E2Sq?`Qk@VmFds_Ot!`znSgl|Ltr)Uv6gm`2w_WZ8h7^|LfU)K8QbT z`{J<4#0UP(why8Y+dc?xwtcZ0>KBmwYPX*c*CY95GSn~A*?vBn?Dq5Vbhn>=Aisdt zSehgJ0@|An+7k^M5zOrZgsm0K6iR-|FPp_QWe*QOS`}yCR?dJ<~B)?m; z{rs=b_Vd3n+fRkkgSIaY8%%t_-)yT;c+mENaI@_TeW+iJ*?vCIcl-I!7|E}~P``?^ z{d^?s_Vcm0+s{XxkTc$2C^P&#g`D<4X97zz{DkdM2FD%v4136a8t}bw&7MC&XFfk| z_xz~=+D8K#58!5l^xgQ`em>xK`}vUH?I)9LL(pT!hM)s4|4#>>fg=oFlQL1WmUFge zrN$4?-doUq8qhfI3T6f~a2{ng0L?pY(EQ3Y@g*}GWR8QK?dOZ%;5}QAcIa8D;qSv^_cbNi|4FAA3b*c`S`i( z&qbXKQ$T9r_8!)Q*sIGt@#Sq+h`sk&f4;bkWbbv>pZ`y@{``NQ_2+}vhiqRk>rH&{ zxXJd#X|S507a;M|u0J21cLlYdCqTy4Sbx6Q&-(MxZr7iW_q+aV0fjrWyw(Yrf17m? z_HKsSyPfsti`8H=A^u&@`t$!{)}Q~Ev;KT={gCa8!#WcmoNuxP?SljP;RQ&1vFp!= z%aQDz%=+`ibk?7bCcFN8Jl*wYS|`I4&{`JI**S2#opm5~r!r4`+06{rNB)$xdh1pD)~5e?D?{{rTA4^`}-R!xYfGIH(^1+6N0}b25BP zWQWf89oB@n<0$jQm)5L5PlCq!>{)-lFb3NJafdnU&;Q!2KmY54_7sBRTXW)r$xXH| zw83hEUVy~4U4K5*M{(3YRtUn(~yZ(GE@A{LYlVOS#Xs!h`p3||t_`0_Q&Pw?Gw?^%9=_M*YV`#HCw{s-+rD+a}} z+QbLhO|~y?L-#^~#BaO&e0bjlk*-g({Cshq<>#Z*E419X?zf9IVXEDYvyuyA2kgSau3W#Y@jERei+ zoaN_>-C+A6;j*9Q=l{(tKmTuM`S~ColqOUsJ_v8JeX$v=Cg=r79J)6Q9xjVne!f`F z^7GMRm!FT9yZl_*!7#;|fk6ar?_m{)y>nS6zMRbhN&oX%e!iFtHWOm+be5n0yIFqz z2kj~I2BiU&i4UBcY+rPP)dal&iFZTxn!)X@X8HM|p5^DGYL}mn>s@}fBgaRxGQ>_$ z`Bn_|YdOo$7ujI*Aa>@n{QRHH^7DT>XulRH4Jb`~pxMdmwIcXZiWxn&szzdzPOMltF1japD8%CfgU*U^PK6K;qUeKOfp7xka1hCuqOg zBW;(TkM&)Cx|yZn48j^qYrmY*-!S$;lZcKP|3-Q}lR2g8&&(7e-s zXV4fTBJCZPhuELUI`QRiW=I(rB2Q&X~S>iY}^1~X8ZYXzyI{LEBf&ViO-^H`>0s&GhpHNDR84?&WEwpD)fc{d{!V>F49~PCxb9 z86bXR7K8ZhD(l1-hoSqgjx+syxtj^$ul-Cv|8Hjc`F}gp&j;}bZC^TzPJ9sFX!~+A z)6W+mG3fp|c(^WR`uSox)6YkXoqj%E?(|a_X4hd6h+St{C%%{swQD}p&zF;t?3&K> z^M5zf&;Ovka^44RUowkKeBj(@`?8zq=L?V+bpIUOu4<;AFY1|oKB{*5`MBQc=Z`k1 zznX<1c7fuo7;0BJ)6bXLNOt8j{rsQI^z(l@)6WOyptus6_&~qW_GL2D&le!EWT&4G z)144yL@?9O7vW4l9|b%8d>rod^Dfk`7tTVUvSETIBiqE6-b_C`!Sw>u&lk>Mdm&|m zJJZkq)=WSD+k^JNfyxHKi4UY3ZC_Y})qv*yem<~v`uWh_>8B>>EC5g&T$}0V3w@@a zkF=eBKGt{od9V$VK0#-GfYPZ41A`g3oxm)wwSs}c3?>F@Km3M_HEU_GAjJRt|1SE}yOTTY$n=L>1DTOjV0XZrbHnCa(#anOFagSIc61tvaVZ?t_O z3|15L0wgZ%^z)%Ol6#q%e!gI5`uT|2>E~m1r=L^X7^d8ZxMS4{28J!*wM?uG9~&4L zLcnKJ{dW8rdz*0zD6iGtW}Nb$-C^SYW(Abf0PWRd{P`brc3b-a+ZTt~CqAeL?W=*V;{>gX2FXFsb%TdH zXf5++hMzCKGyHt?+2QBo?+!nc+8CyQ&J!%X%{T?rH-q)}K<;_%%nr(%6M7igC%$>j z@be64T`M=k&o}QGe!hOp@blGkhM)g$GyMF2pW)}L#cUHFTxaNW#t4*e3B!i!I@ zgc*Lmy3g?Q|9OU=|1UHAd{BJA_SIq5iJ&{WALK*s?0$8g;ing9&Yhj%=PQt!^A0~B zUUvBTax=rv7uy+rKHBW?^YM0vpB7Me`hncZ?(p*+x5Ljjp!>W)XGVg~vI5=7D(~>~ ztuyOHe-?%b?`7F1zF*Do^WA!epKlj4{Cu;V0e-*uo5d^>A53TX`DQj~Jv775|NRU< z|4)YOQ+j=vdEx_m=w2n~2HV&D3_o9igJ zPI=Gm@bfJ{=*~*WUG<=|y+Gmk#+iBIgKUPMp!+Ag6&WVJV`iQhz``&=z>0n1hh&DI z@6#E6zKdq~`8FPL|KyushM%v(8GiovX7~xZm-03AuJQlQ3_t(7GyHs@4;n{coCvzF z>w)$G+XvDOwhuseA;~w`zH$bgTh|cu3Z%~2;panlho9){i=VVIO!`)yww zW`w3k_K7d88GdSl&YuI_Q)UcaHx0>8<_te!XCXe22Gxa(6G3;Lg2sHn^%E$)BHd#H zPm7>5D$nrq5$KFKd550|S{bH15_bTffd;CNLG!nudsFV+Vx00o+~FsKfS%j~afZE& z0t#{u)fx73gYp;XPA`xib%(v6JDwjZGwigu%{T?LHxp*oIf(wf57iy^g3S2~;)Cuo zRCd@2R-^8)7Zi8MXRaagCi_Luel*aTUk|hyem()+rw$qyYGwzmJ)Gdh$uaTOXNI4r zKx;+5GyHt<8Yx`fGyMGjnBnLD=L|m;bU}HPZK8r|gDvQML~yu(>RHgkL01U3U2S_`1W-%2tLcuR;5hp=Yas-0>=zZQ?6tNZta6jXcBN2eTRXG6*Qh zJ&l!zC_C_PP@;1Xx@cJKfhMh0( zGwcMbzi2)Ybgmz0KSw5Ly%T6mupDyVa?Aby(?RZfE9~&|jX3xW^_`$GLD1Q{Cwlj@bisRL(rSW3_D*hXV?iU2S9ru zLHDQUfXtV7*o)JAZY1-eWyM2hho6t!9ezGeW}Ns|pW){lV}_qR&J96tvKe;1&S%&O zx|0EP?l;K%ko*6q|3A!*y{>YWzxX1VeY5+};8itNg#2`RD)b%s({+8-iX!`!^46AG8Jah5oN+{`nu& zUs=uk^TmE=@cQM2pfhlHJO6yK9BQAltmVt)&Ocu`$6CHz&-^n3GzJIC*UApyKF5zU9!%fiqy3E7}#~W>5Ooy5|oB8L1>CQhN&PH;7GxN_E z?aV(PH9P-&-0u9dy@g>4D9rv>GynWw57l>A+7eXWy{KpY`CvbyPxrr=`RD&~=AWRm zML2{SE^#D0`UE;(rrmGp&VT%KzQWKmYHCgb!%!6*M-s02D@`JLWkVJ~ps2goJ{|WI$z(vcu0; zps-skHStwA^UqhwQWLdV7(Qe_w3+zYpZRAtLqpK(VCJ8%+?f$$*WS!O|JyVF{O`>C z^MyT9U&a~I$N6u~{8M2qsIGwYWfWE(uzfJS(e{No^sE?b=ARGDoqs;Gc7}{AgXVsu zp?*Z_*BG}jOablV28Agof4w>!Yx!co#Kc$oB`jYhOF-PY(01Z$e&(OuAa@Eg|9r)c z}E{e1A<>F2}WP6&72hPET`GyQyY+v(@y`%XVOTcG1`pfuJj1}bwtXkN6L z`0_FnWNqPfrk^iPgY`l3%Xy}s{|__${0}<&BOR0`L?=FoZ?r}3$H2}6f!7<*KFg!c zPCp-Scl!CJnPJKc&{^urq7$_Q8-iXfXZrbaJ`8A%Y|NMvc zZ(bZ00iO-h0UE~zt>5ft`uV?^>F57;rk@YoLH+^l&1nSf!vc-11ib)>H#_}&*pAfZ zE@t}qqMYgHqhhC@kIS8Y?r4VO9k8G3g(2%T>xC^}rZfH2gq~#wI)@b04`Q%|`YRsN z4+8ODG=uiRG}^w1hx#Fz>F0xZr=Jg#k?izl`uW12>E|PFr=O4goqo=4W|#sxC*%KW zrl0@UL&64hJ_jgV6xbL-KxdYBGBkkaJ3(R(xgd9@gVN%Q)lNUT7#)7TP!^i_%AM)w ztH*+n^a#o~_Dqm`@bEKc`uSg->F0lKrk@I`p#5tC z6CZFl+A1g>uzkSaX!}APdd7z~6Zrg(huTQ#ksIn?ex{#~xSf7J=6Cv;+RQM;11Wz) z-D>&5USQ%YdjZQ=kNF{P1?8RpjF7y;%=GiscSeL;e>494|DN&Zf6zX%_l$_V^O^DI z|L2T9|G#GZ2|6S0MKj;T2d^7!6&MfLK6u|?`{FtDe1_MIKOa1I{Q2;;BO>pdhW201 zGyZ&Z+VSV(^Nv5QnvwDjGan-F9A<>%o#Tw)eLV2~(SF9C|2H%K{J)*?=Y#v8a+-JI zgX;~pptC=~?Jkh`X2{te@Vv7avXAA_V#lA4mplHHZDyDPy7Twm4aO-CK_M_!*viUowKr{};{N zkal$~$HbS#jF35pa>k#ab2{Mu$Y=cdKbi68f6)0H+d=I|u89xUH`u;NhK3PHJlXN* z!*oYPeF<7~9nSdkQLy9B$Kj4YUpFyK0iQ_?bBi-4+%2kL0%oJMN9gZit78GeG=%@5K+VGrrEg4)F3G;|zvZX)Cia=3kH z{nef(NE#{vr6Fd9pB$hx#Od%8bk795z6G7p%gON3frTL?0h%8{_pvZE9=3T94(iXd zLfi^UCySwBvmA1!20U!$L;9}&L1)(bgW`l`BKVAr$zcBkf!5GHnC$TL;dBR3I=KLz zi)?222|9n{QM1F($L$V36Pp;OIDpbDv%^oY-S(hem+b`atG*4zi@`1kAfY3J`Q*I zX%9*p&ynnPW`g)1ls3Gf{`Uu+F9|jik|y05e!}k4exM908yP2p`oh@ml!eC?=!|H6 z2Jn5vpgY?Yn;542ca}$K^Ztk4=?%*NPe6BwFhkwq$T<;om$xTqEX#e$aTUnFpmXd&`??=A zGlIh+4Wu5_KjsJBM+zAa$>47Y0`2jBbDePtI1WMQ2Pm?C1os7@_cuI*-F3d00hHD@ zXoC8J;?RB|X#GQ4LlEdbM^DgLqCL3(_d(NE4_2$ZI@Kxqvm#>{YmA=3SCzzxPJpuYHn*G@klzIXarc7t&W z=#2Llr6lYgWAZKnSO%rA3V>5*h>N0|9=_McK&~w>F2BC zkhb-I&{+Rr(D=8|M23&&ZC*HwPE@$rVEam42t4+oaJRws#eQfz3UogLXwCq%rbU-~ z;)}&hkaG&N8NlQJ4;DN9e7M}{=cD~jKOY}<`l$)Zv)v9qUpy88pT)}XghBtoe5SuA zKw%Z?4yG9c-Tx-tV4U(M+2QBQbcdfWKxZYv{4<~FFR1MeYKOmK=2`V>I@8ZpuyGnt z+Z@z>p3L;~O+VAmSD-GyMe3 zEog$q++(>Xg4)F|kj~C~)b8~2aktaYUkwaXKzHGQ_TqrT9PDOa_rLa_FbAFG)b8*T zC0?QP3U3|@O?(i~^z%(J)6ZAwOh5mJGyVJ@&Ga*2D`;F&Xkx<32HRKROg~>lGyQxJ z?)39vwA0U*&P+dFxHJ8Htg_&;}-7l6LeM#XrDM}T@z@{HRvoV&>AFAIt1Gds%Jp;lfT2ym!PvaKxMr* z!%vQtNOpTe?e66UpUVf@v-Col>E}avr=O3MoqmGsUJ9}swElA?!$+{)?hZde=QqLa zhS~$N`*F7e{G7@chXugrJ^TmFU4YUd$bZ7n^Akb!7wEh{VW*#u#3A-P5NG%)b(3)l zc+44EzC7S&`uUKb>E|nMr=PE+LH%5&pa0oGc?K!JfbOedXZrac)Smy(_*0=3RJQX^ zRH$vReeoOGuCr$N`Qkq#B%D`rgU5eBXCa&b?ZpAz(|~loA83yE@qfpkDGdx$K>h)Z zxq|0=L1FY5dQRMP#-9%#JN|t1-0|nz8;n!lN;~{~Ee{%_cK8Xpe+j%!v)ke4n|9Ee ze8`!A?`}K(d+6g^|DR_3`TsoQ&xYIswy&A_CN`ut*uFl^ z`193y#-A^DGyZ(BA9AkW!~Kr%K80-q!xYfjM4&rGL1_qbh6?Y**ZK_bzQgNe-ibLP z4VQQlo_zw1m%Ulf`1AF0#-A@&GyeQPpYiAa#f(2+s&h_M_;}vtWi!u22j2$Um-9hq zYB2tMFdsC}4Q`i1+FPJCoZXB+U-UEneAMmu^Krl9PnHITDWLoQKzEM5gx(toatr9Z zl>g0)KmWHg{(J-4-`&pm^M5tt&;Rv|KNDO*M{Q1b)@#kZA$De2M z*=^1E^S?dgPhZf!8GFW`35rmAjTwLbH)s6$#+dQvD|5!5|Fs!^{?}*xnIL<>_7yYd z#01d>+gI9*KVRrG{sf)j2ilYO613k?p7G}+X~&&$OqNA z3XGt0ARBD$Z!&`AKOc*(7NUq&d@W%L2DcZ89s7>_VEcr>YEqN z91}rjP=nShg8KR3_5*0o(RYTQ4?jEneDvMn=i|rVeg&lca39)!c+BwAv#lZM%Yon;G{$*v_~Y)L#LO^MmHwz;#eJXbwO{?m<6D zy|Ub+$&7m+PG^M96DY}n<_I1vcHH}Lx#QkPs~z_~UhlZ~$!5pBPq#bneb?={^KHN5 z&Nq`CcfOwPxbxL)$DJ?cJA&@7oT$ghuv3?nVW$o!!%l5phMii13_CSM8Fo6}WSjy@ z!w-@f_dZN#-1{h-aqr`N#=Q@s57@qj&ilP`W}WzdHN(&U>luD>I6>R1!60|4%RLBZ z+{-XQP3}Q7<6aI2s9Zeb-Ur2wdmjcP#*NXCaV%A2bPcGl1(Idk!Ru?Wu~7xObtPA!Wn+P zQD^%3TAJzSE3`TFSI{~2SD^X|X+HfW+C2K|8Y4nLoyJN$fF@9^^l zXkQYu{3_7;%!kaJS}ofcr-1GOc$n|-^HH(GPfw8hK=)@s&qxHdB|v9Jb4W6L>|$dG z0nITj0JQ-RNQ2rqtG0vu>&)=eft6v)Ly-BPGKZ0ABB&4fAem?415p1FbRG?~Z)EtC zamoX0ho2Aa9U$YRAUR%$d$pc2P65s9JO=Ii0?o;McKoR!!|?ObVQ$d+stFv7<`Z93 zGeXu^)ieI==xYdi!OXl9bgst>cIKVnc0VKU#24+1KQ)*df?+Wn1kl}fpgm8Z`?5e~BNu24RNvv} zQ_$Vppfi)y89?m@h7HlXvV zL36HMbqrG$urZi{(jj;c!9#|L|Fao?{?CVum%+zKi5M>f`?-;K;)`I$pB^ATheOV$ z1=V4Xk{y3OPIvriR>v>}Wd8$gho9kf3{ybi`%vHE=cC7*h;X%s`U6z&^@GBWX(zbt z*Uy9qS8r&z`ZNA~kjy{v_cq2UFF^Yulo@`4?ji!Q!RZCm{|DW3X3p?)&Hw-6%l`it z2c;Wu7=!K+c1E&S8)~mUnhuRC;@1M*+v2q*Z6mZx<#`i90 zp5=v%?}73KXzxAw<9m?vBGAV7&ebwZdC<%XKEE1tcPEDkB)zsgVVv>+RG-#6{A^@! z_}Rqb0Pg33_V`{RHw08l&jC8$jZnkNUHJ6*}}QHzNohAuZ4jRh=#WMq^187hF zswcG!(0Brm`CZU-$gcvGb)dcksBiIr`JnBC-wn1en)xS!_~0=z(E5`9j6Wa#cl-%jKlK7M zw*cDD3z`E2ouT`h@#o|Bjz1s0cKm5v%P<9e=BmBJPmnuaG0SVcN@v*l>NNAtmya2L za&xQ*dTq?G^Yv-wo!ks7f?k_5?0kKkc_;UW z+MZ$O>)p&dxg}Nvy>@2U`FcC^PHvGEL9g8zcD~-syz{~51GW#}AFyS(qnP*FnQvk? zW9_DdMh0I7hO~JM4C(V8oM!y{`n2QEH?y60zFEz@^YvxW8Rt+vR}}qTALhl>vzzhn z>)nn&-&8yAeACUm^YvlSS?LFCA3i={%W!3(-)m~)e*mQ$2amSd1mmV4#Ryz}K^P@9~2??ZRyy$`%W*m>_mcjvv2oI!f@~+#mmQ&PFmSfORmV0H*yz}L3#-ERjnfE?4XWsk3ntAW5*`RYboOiy?X5RT~Gvm*f z(;2~MWW8$Uo%m`pX^JO>V&j+Uu*giPiVEdApXCjCX>i2-_ z9ZL}1T=AiS;LH#07|LAHB!<3iC4m)3&JM4UI?XdF==)QJq zNF2X;%>=q*_vg#kOgmpaXWIGtG1JaBroi`9&zE{|-D&T`?GQewt*FJw#r5E})82=>Az~mk3XEJ_FTz1*-Z1`r6z=%* zVXz~lUS((a`NALU=LsB)wi91~$}?|9aM|}lo?+*Uex{urGaz*c_^wUR9gWT~^`P@r z7{F(ZJbuhM@$I$$)8DW={Cv&r@bi_t!%ooLBxnxuowdWxx6tr;YYgh|9NEa)7tgfwT{P3q2gOc%A4Wsm{to27a;Loy<01U_;`5~rF!FP~ z)n@$pR-F-i#tP`n^*73lKVQp(`esZ!U(IL;dS%SC^Cf6)qA=sn$IToQj}hZGe#W2g zy6)($)0+dJ%h=j^cat-Hg{C*BTwANxD( z1^4^)9e#q+)N5;ppRb^0$D7-XJ72#Bow3fi^W$~KogXeU?tF8aaVKb<P^go+%4go@mo z*^E11pJw=p+z)#&+i~y1>5h9JO?E`di*LFacfLLhDz6#$K5S>)`=Fa~FX(I!P`~Wm zZpNK&Z!`RSQ_r~b!Dh$354St+WzbNRd$=1yKd5Ki`+7IS&)3@-e!lt4I`PeRhMzsW zps|bxpWc`={CoqF+syFu^?C--n8w6}kLPV31UJ~eW@eoT5_`27adtas?G#ow*D;D;ji%?gP-6AgG^I%m7}C32KwCt7e!2a@Qkm z$Gs2L9rr#^cH9e^8+mQ+@Dtq~uksmwz5<UkYHaFAob}e9+GD^9gv& zUl`PTiG)p&ipZE_^I%Samqt`hnBgY7 z;Q|vM=rjCe0@poC#b*m z{vmYyA2x2T%s=t9H3N8^!D?oP3E6^{6JLij{CxeI5xn2_bu`1z*Y6pBzWmGxDl2}z z`ppPAdk7S-|3T}jnSO%SA^gA3`1Ai`#-9%s^G$qU4(_L1;ArHZ_yRPCa2;AkgVy_k z?j>QCU-ht@;pd(6j8op2JN$g@?C|pyDE))_$)NsXJLpVhhE<^c+S~h#Ki@nCm5Y26 zeL!obFN4M~9e+N0?D+G^W8R6c_k+%GWBmDQZbQ(kZswg?p!K8E8Fs$vXWsepG~>?~ z=NW(6-~2!QUAV)~x6uwi-^4rod=0u^0d&tow!_aC#ST9omOK1>|Cndu`(V)8NyeY= z7Bl{QyPWaotJRD@L1UF~HZ%Twy`AyrE6^FIpz+G-j6eU+2K7ZC{Y*{J{QN}zi7)#Z zf4-Q^_;cBL#wl;@9e%!XcKG?)-Qnk}=?*(zBKZw`o=>pD&v(-qf4-f~`17$d&&0K$ zF}!ZZpAV-y{(Lms@#mAp+!NoFGyZ&C&G?ghUPI99-%LAS{b$GC>8_)O?G;aAOo$=@EY{s9j{271#cW3v5WZ_W7gMKbrq7v_wJxhK%rrat4(|Hh0zUo2;Z%rh}_PJA%A!4@=b z32xtl>Suk&pAU_}!D!={wN(sLKgYPr}oks?mw*{@Scqr`n^O3mY z&lk!Z6G3|b{&O?_{Lc?c$AS~V^VORfr@REsS@S#m{QsZf=YM9#pRXRXO?)NJ0A8!# z1-jEr&~oCd#|+?egFx%}xj<>~HN#JCriP%`p!NNq8GdrJGz7i=&hYcqZ-$>QLF@RT zW04P%*}(lfaM{zyKM{1^7w9hchs^S;L_py!3|UtI4sTGu4z#`kbpQKx2k?4~G*Ea; zV+n6&4oG-|)**tz8?+828PeYZjTd$>Gko9>v;^Pp4m!6Ev_AlR-`H-3pP=#sd>+|h zhM%0Eb?TtIR6z63iv=fw)-HqA9fQUlA3SCO&#i;Q7L?cLGeFv6i^22ltDc;L%(H{S zAMAe6o!FrM7wGQz6zPJpo8z6lx(ER9W$Q-GHCuqHl*hB@#23zPF!ywRpsRyT>em*=8 zUi$_~gS(l2g62OT?RNV4c)!!nJCzJmK>MOzYx5 zA0MzYghYeZu0ZFUUL=b`+~+7T@zr{!pQk{3J2pf1O~LCo(0aQ0Oh5mF_EV_~Lh?zo z$V3Ir2HO|&!TtyW&AC39@AUKGVk9?qL-tBN>UR41xZmmL=1KDt^(N&x`(V9Y$haZ>Y0B2FJ}7rzntl(g6sj?7tX>H6+|0sUm)$JdQj~2 z^IF0}JBzr;gquxwE z|NAq6?^y)JEog4yg*RAD&=2l);^f8A%C@+#Q@JjVJm znQtO!{W)kJZzVIs2F;5m6G7|UL1%b_^D^ju((8KjY7b$075FPn>xtzFy7v^VNFBpQ{!%1ieaT-nj}?cBV7$ ze7PO67BS%Z|LIuk?RlH zE@%7+8k2?11wMGpJ@J7vbgfN2JQ`7)UC=c{nWpD&{s!TnfR-T9!IbK-+? z(0VDxpAJ_Ur-0UtfaZ(AWp1^@&zI(mKS5`fJ~Vgy`N-Pw=S@)G4s=%udL7LSt)oF> zbf7vTp7H04c*mbFghBg~AajVI`MVd=;Qb*#S1>bd;J9cq@dZEQPY#xbAkf-Ha5*i` z2&qG$busvS1ZBpbFF<3Yj|C>a0M)U~&^>pL*(ZYL+rjnue@Ob`2l+wV;V0Y=(xCB7 z$lAw;%#J@Fu{-`;0rG=9mU=Il6H@QVL+&?u0h;Fo?Q7Epof*#A4Z6ePKWGi)Z-$>Q z7PEoJ5J2;SaKGGUfcWJ;w0?OEtq-3=_9TJ*0$R@n+WQCc3#c9kt#JhHw{vEj2wF=9 z8k+>Ifdtjzcdjr_dBKk47jDqGnG8R{b^LjUpP+q7r69k6#%0mt<1jnKFZ@uye21)e z0*!Zq&Y*vV~KQlmOoHn$6 z)@MYNakD{pWHki60PQ<^EHF`L6XTTs%;MPV=fiv#AHeP*X$G%L-k^C<0DSKN`22+H zj6YvM)-yxuo%4)8{~u=j2`ZZ&JO`~Q<(>H8euM3c!_YMr#~FV0JOH@!Q}(CpfwE-PB++s#sfiT`ahWM`19d>q&6$s zdWH+-3{wg~?ErpIy61t|muWxoMKjdCcE+DCs~Hh{uj?6qg4+21L32U-57@qR=AQUq zdxI^k?0Ep4AA*Mkbe#fho@Prq!;}!1eTTUq_7&Ps1dZi^^Cjp^@n9tT!Xf>G|DgGp z)dy@}GILFQu(-h%R8E1$=RkK6KJ-WOAEDi9@{(qoKem&#R6tuGjvYIk#XV+ zW2pbk8GpXiMzRmo4^U?O`5$y|;q(KxFAsA}e9+%u`x08F!0s}H`%f5j4l?7J1FcE+Foe=~sF`wyBy@y9;#L3M*I zXiYWpJ%(`mK@Kd>*Vaoq*$o^mOTG4K(J^j!zC+`MZ&>e)>)@(muW(WbDAp^=^ zFPOpW0w!oaw3!IHw+g&pu$=*XrYF433+l&$+92f&KOfkG$~ESR56nUBPNQ9e#q=SA*3pv<0`7!0Sum8GgP92Ac;db3knj=>B2Q z9^n_vOcNg{H`s#ixCW;oe}%RvE(- z@Lv4s4nH|K89pv#W(WcMxtVbyCrA&-eq#pEo}r+Z=AiSBk?aSpr&4D4`Cpyk=L2z2 zna42k0e^!nX#XJCevr7b!_SB64nM(Wf$Rs3e~UBxd<5!4iaY$=RR-Fp%!i{4=DPS| zG4n)F+ZHso^aM1&+ROzSUz?!mC^!++_5iQb293*u=3_zokwAAsfyUrLV+PKUaf1iT zL2l!m_+UP0Ef6$6gVwbn-De7SA845AzyFc3`5T=hyF)FS?q+DX#I5k+(`#vlpRbYH2Jf~r`~;10 zgT@8^Z)W)U7BrR(I?Efh&mVNZY4QQv*Upf=5DlPxQr-==4YCJpANV)ez6R|<2C0GG zfeOmApnf)NZdA66Vahvkho5gi>k_fJ_w8fmiSOzee!eYd`1z)q;pf|C=85l$8GgRa zX88FgpW)|QW#)yH1=XZoqI5w!MGWTL{# z23zlEobQClr`inQ^~IpGOJM!+^Q8<^K;!JNITnyRUfGLG%mIyo z+KX7eu@;&5-&(}-t$m249!EpaT1GytwT$vcZ>>cv|AYGF%}hUE)ieFn5(Axq`0Nv? z4-e|6|F35H`KFxd=c{U_pZ`H;n-qi2hvA2;eLgHSF`>7?_RVsB$lB+L4YsfHnILCM z6f^yNkni;KVX@QCmuTbTOR%}MUTEU~dLhd<)*+U>91THl(;0U5FiHva$TAeYVTY_O zds8oD`5$!Vi8s?v(7rw`=7vjL4<3Aa1?t<|GyVJz+S8}b2XR}o;KYQ=2HRKmp!sm7 zpAYPvem-<|!dP?FSPGj{39*E(KNI3$WiSDSA9!sjvmj`EVS?9L!HKVwnSO%Di`8+T zYx2TcWa10Zc_w0@bM&5l0-b5{A9TkzKhsZzTu@oZKT!d6KX&E;TZP01ThP2cxc$%1 z^z#9?)6a+ePCvozX3(7bXJ~o&o$=?R&yGJIe|P*DTFNj5?zVb<=w3{IOVF4)d=Kqw z#-Fh9^%v^g&@mpqi3*+#wlD5O%>B38GgdX!D0IVElL@tJhXQB z`M^5FvS&RbB%MLWI6-cC#momw9Dc4Q(*D3# z$-EOEtY`cQn%C|Eox2G-vt&Et&;Ovd@M^}N3AUgzkO#cxHNokY|pnZYWj6WZgJN|rF?fCO0T08ht3B#0EpgCc4ho7)9Ur>0wdCWEOtQe@= zfAk47Uj7DjZwzQ{FR0!F^~smBLF{4Xf}FSD51G4u;18L*eu>upy-@<$PYf~}G`{#Eg}=j2up7h~fBu(d1mEusUjNI?F%eYPyL`C8_n?Z zO+3R-olOlvZw`aj_%Q5zl@2+>17zlWLS};I(83vhJ`8sF`6wK;mYZ?nTYH9|pu0SM zKxTF`?0ntNu=5q@oN#~0THda6p#5H=DE0AwXXcB4t9e#}##5fKBel^Tg}`lePtd#s z4ch1*iy5XoVh69on!s^ZXd-C8A$U#}X}@9N1;!~4*co;%0PWQWxy_jo(oSX+p7>Im z;pa)vSyQ08UKy$VuFmiib_eML;RCiWni(cO;BK%5jirIx@A3>kA4og=d?=69eh0O! z`5Asb;&%A?nBU>&sbU83c|0%J!DrZm*Hwe!r1%2kl)u$JEB`w)PdvaJe+|A4`mor= z7s;d&P(9Rf` z4|3~%ho29`8ZI$7Jo^N?%TuidC%nDW4x>E}at&|LUI+Xw26wlDLYz~@}OigyC-`vKhv`1(aY<4(}M z+AlUc{Cw#TI(LEdHE6#9XuUf~4jdl&jypLS8YY4AA$+Ym=)-cCOmDxcUq_}*X(n$HKHKX4zkKG^9e=>GVp`QY}<1@M|^&^k!a z+&<`DN>CsDHsj9+*ByU8yzThYX?sJ^BXP!^kC_^RL@)oJ4!V08bk724eZ(Vi$DJU% zyaZXgy+QZ2PiFY(#R{4yXZZOl+UX}~9m~^rC-7aUFF@=5&O82mc-ir%GD|}c)8+ru zUxVD)@9-0J-}FmRU%E2;$E(MTKVO2@qu*!z`TDiv&sXmq!Q+FVJxwn`_miFm-2=z; z6MS~ZY$k9!{Pkj|pRblX{d|2GvbPa*p5crApt+cXwhy8kZ9(T4g3j~+&)b3aAFPL@ zA<)@$;JbalGwhtoz##HsHt38sr=OrRj9#vG0^bjP0(6#LG3c&n(AX{K>jR7oA)vL& z;JkjHVP`AoE^z2w%^>|8f1NMC0IjKjmbEJw7`B=+Fo?j^S0bwi-Ps+?@bhIj!%y)3 zx@d-JUI^5wWXpbIftpljs1+Alpop}aYSDwTSx;xI{C+r^j7oam! zt~>qIWOVomI!EQ@Wi)@@XV@7D@~5-I&zJ5FKVSPh`~>Z50PP6@?G1sg4So$hXALyp z1ai-v|Nq57c7WDCfc6%E_PB%m`U<-C0@N>iWzO&uc4krslYkkxjSjjWozY?Ei}|cO zU(9y}-TSfgYOaz^;2EMmoHe#I; zXg$s=b%vj>Kz%KJhMyfw9A+;;XElJ%o@aEZe4)=>`9j~jQj^i4@})L+p?Vd=o);)^ad5T;gze_z83`@Bj6Xb39%+^G|%Sp7E!KIH)i42#xwE3amZdS(0DayZ`XtK4Yr_rX~AnIKzG=(Fnr+nDhwWr2JcyHXZ-n~+41MY zcE_KOmOK7@yxQ@nMG?ak70`I=eh2t|yB8ogSA*N7kiIg~nDq;gn~Onr>q6ZOYQIDG zB|O*<>SIC9>v*vHfbE0L4Yr`OJ-~gee9*iT!v_vS5%4|)@Oih$`w*b}_e6>qrn~@^ z8LJt79{l%T92B15d;Itre!g^uj(3CZAMggdF9la=bb`^DK9|lN1$Wapt=Zjz7FX8 zo0p({YU0qc7<4`gBj@WZE`|_r{{vhWzqA33b%WdtAH$M%{P|Gc5!$a50gq#MfX2r7 zA?rIIgYK<+%svseUIjFEE6u^=}y<_kTPF&C4--;K&u32%6&p z-(81vcF3dqpth#N&zYdG2Kh;x;pfW#|Hc0w7DH)Q!rMm9>K7k`GyHr4Dl41SK=u0v z&9x#EUtVVU3Et;)o#p3?(=4F9LXh+dT1$VP<>&vyEIcj`}O|~x% zgVhCr#*1HowK|Tm> zvIU*J&LEH=_duWF?}NoGe;EW4fXU=CUj3u5QVJ+Nl@`(QoGU$7a? zDv)z1yqjzv_&3=y2qeorn9TB*VM4OpgXtjkDRK{HgZN2u59UMlFsn=i&4q%_U4P)- zWcy&T%io8~UH(2=?eh2WdY8XXCcFH7I^E^(v)L|xpU-#sTYBmL^q0&IKSBFJKxdnR z!tFsZ%io9PEPo$Wv;2Kr&+_+y^&wlZdG?2FAGkN!{?BIl`9B|e4m;@l2+$e{q;uF` zCA0hloxA=&n&s#Jc$S|pqgj5wh-dlvKbYm`|8SO{56nSftT^$3e3R`1<3qL&l$&fH z=pV9upbomXfCaMF2s9oX&H~8`jG_}?7_&guii5_!%~^gvFn0O*(A))nhPxm<+!eul zPaX(2***{_?Ejie|EIrZclh}hI%W*oUkW!BPmY;8!S$@7^XZiX6H|YE{=AW;B zGyi<`pZVwi&!BVDn14QCKV4J!cILkicQgNeu%G!a=&Zok%yJVS zJZ`joaQ~n!=q$qjtC@d-&JKLLn)&A|&>4cD^Zu4I|9rEU`R6Opd4kaM&tK1G{`qP? z^Uwd2nZf4)KDd6+_SIqOi4XQS+CI2^(DuRMM%xGH588sx27EP{`RB{&P`@&YO?+9* z{1bGB--BZ3pAXBOe?FS*{PXd2=btkv@vB9;oRdYGoT5dl9D_xQ+~Z*8zmLM1|2~Xn z{`(-F`R{|(2W?+7L(U0&u>PR!gYAvB|GgpSm%sI9{`m@Yj=3}Q&;RbsKi@b*&hZ1C z&j&ic@3l4a&sU&x{Gex(KUfa(tHi_y{f)K{79X?)okRX${z35BeXop}f4($_`n6GP z;!9!XpD)Cje?Aa){`pYc`R5~J=bw+woqxs^K*rV|>pT2?WbE+wp}E7~2i6XM-`FFq zrww)hmvR3==kxJ1|73Z>kpHB<(e_Dmqb;&;OuvM6NUae0`be=PRT&br14E;Q%?4 z??LuK+Xu;wwy#b@$DBd!wew6rVe9K)XNkkk;j-yn_Gzn;zXb2V?nCGLa= zpI$42&N4=v-X#09H(@)UZA`d1z{d_px>E}zdGsf@dGfV-^ zdAyf*`1u-i?xVWHPtclRWH-KR7M>`;!tgE~q4!#u33Lwb&j-p*;Il(sax?t|^<5u9&&-=ajC&kGXFCf(+!G`|@dGo{&-d(1 zKi~ak{Q34jBfMYx<}>5Z*WV#$t!00Obgl+y zE!>Oij6WY;cKrGHy5rB1e1<8Ycmky(P#l5QY@@`}cc-6^UOWAL_}=O7gU?QXog5P7 z6de-f7#tGi-Z1k`d>luH(SNQy`?^5;yWH{_ro2N+U+8(@<7=m%AKp9teE-?$=iBd2KR@1P`T5~K%g^_Z zS$@8K&hqoYbCIg!^bXv88{N;k~xy)7&MaQoH&x?7&MaP6gd**7&H>) z7!D-Ly=CTs5)I^X=znep!fcSdkK2(&i*!Sn{(H_nbfU%Na0e523!^R+VL&j*u1 zVaqx3wKnJsPw_Qe;%a#C=@n@HSQv3G-z#Sh@VR`i zpleECXY##7+pGIHk73GdQ2!6Mj{#QhLdU^hqs1vrl|b-e3zF0|%WW|KPL3 zPtbj~FF|)TKWF&)=&{4k$Il&pp2=gFf?mdh_F5r_3AlW2W}Eohnc?S?>;~H>`3<&@ zFEjjobe-Yv!`lphAKYj7`yd;1t_#~l&{)8OaX4LX~IW#R+p2HRKNkn^b@bUXZf*zfT3CFo3mdWN5msvUlU_KPl{15IoK=vVl`VFsHe!h6m z^7GMam!FT{yZrP7`5kn2xdCv0l^=+1) zukN$_{C}C{=l|<0KOf{DvVG;OHt|7vlkKa^EI(g>#4fx1e0bgEC+MCiQ2*u8VV9qe zkGuTT%VU^=oW@=~R)w6CzMtji>%*!OU$cYO7P9<&0lG(OHw*kOsaKm>5cd|igVGSo z&;P4ge*Rz2^7Hj-mY=WIv;6$OnC0jH}L51x*O_Ix69AR{VqS*@))MPWp?-p-p2{rXM?nM7t|(ywI6cd z)&FLepa0uge!gvH`T4q?<>&uumY@IYS$@8$X8HNLp5^ENVwRu(%UOOta6V-Fnpt_` z1LG#!2cSNTd6VtyVwRt;Kyt+{KOdI6!22kVqFsJIj(7R_G#9@c@)#O|-kCG}e7B$F z=POVcLHjmugIRvQ4rlrK-<##JN`QMr4=YMyWpAWPT*}iU81m6qw zK>d&{s1NkondRpzkesv2&xh_VKVPEV33Vu!Vah9vw4n$|8=jIA-zc;Ed;?lPU<^8M zgyrXJX_lX_7Pe2a7^)SK7L zKVQFR{`vng^Uwd!nSVa`4Qgk|O?>dW(e}ajgSMdl(Cf#{KVO059y|Yh_}uyD%hSw1 zUz}(D`RKIs&&TJTf7azPOnJxe@Do1wi6xD!=V=If|DS#53sC%s*dkXa4zMv-8h~+ns;DoXz|b zbeGek+0H*7&v*VAO@v$ec^iV>i!af`5$zb(`iuHNKbsQyV3T+@q@OY{?qGb=AW-Xa?Q>^AGSOHe3{Ms^F==M z&qvwLKOg5i|1{5KnDRl~VJC9&J4cG z>3=-)&o|M`KVO6HcM68w@$_IbD10FIIDz{~%NuQ9gU&S%Xa4yh*!kzfaOa;d(e7{( zCL(;^gTm)O`_6ubhM@P-3_rncg7&B088iQUi*&!!8*S#Ful1RK{#R!H`Cpy+=Y!dx zFp`+~pu5rb!SsW+pnlhDW#*r+Kyu2?KOd?)gYRj2!O#5j5x4Wt$NbJe-{lZaKmDwb z^s^r1CQukb?{#{|%>45$JM+)~znQ@2qQCjg^z$|7eDu#u;PcQQG=stjau)i7`h(!J z&|iNBoeRzM^TB7QpAWw~{X{?K@f4bS-s@xTy#SSa0xS(dA7F8Indv9!9LRTUX_4&Ghp%=-fxx+34Az@DZH|p7%&UXbUn zcc!1Ot(ks;);EF9EdOuL^z(uILEBf(f)gKzH`=}eonsCXGj{s<(A??gOJ$~?pfegl zXCppVclzlJ@;hja9hUt2_OZakxALI1kxW0|92S@eTFV1E3lTJS2)eHcbpH7ZZl<5_ z_@RAdVWyvaObtQr#F>7+<8}g_Gw>6n{y#J1%)__LOg~>Eopbo+H|Q(@MtGkLbQZ~L zX8wtwz89!J2AWHJ4c$Nd;Ire;hoF1mZZrM_tzCL_+wte)`;I?_a~P()m3R30Mj3k= zddClO2k8E(|2WPq1no0=jl4JDHK^UsJMqEo2HOYE57>hC2!PH6d4;q;0CWZ+XintO zYR8|C*E|0Fm`%VP!Vq_W&wzyX-QF&SoM#By6AW7O|7JGh&)1-R!jl<){-4hH6SN25 zH8amd&^aCt&L6OSaJ<3x^<>7MpmRPROm+nC`30@#2c1FqsM_)8<9f%R=aJm;4oA8C zUfk*DdwC|vS&qewKSAsN-@^J`(D{=$*^ED5BcJEA8x$tce%ST{wxDy+UnevEe1&ud zI&2+3tRJ=}n_gBB)>X z${4cN_kpqFPtaZ@*!ulP%8oxDt2_Rjl#QcJ@t9-cThJbLdB&e_4s%Qd&1r)AZZAN6 zHEF~->TiV^fA)$uT;gqb_6fA+Ae8^D~0??}Fw-9(04k0kW?D z0cf3XJ?P8^#-Fc{?)n0qD+oFd9k#ANE1O};J8Q@|4t#GndYcop?il1I=zT@+9y9!W z3z`Fg-tG0~Hp9=?pm`72+WullpA4lxcA4QPXblr=jsMHt3_oA&XZZPOx5LlJ`yGCI zWHU?wmHmX=@*Z@?2*@p-7EB9vBOW$dZw4qz8UDe;>Z0CKUK3CrhG7V_z7w|5OU84dB&gbLGA(FFVxQP z^IbE;&$sOiKVf$Xf!6=NMqbz8fg+Am{Hx`ff<+2b5l3Fmp_N&aF@X1hJy8d($zb>i+Jgo>>C;b8F z?zqQDcgBIz9cX{rZ3f7FanSiSP}%~WHU8kb!_SAe9e%z<+dHx$3$)hU0laVQe>LMy z@OhcVpmQaa+pBjC)xH6y=^~GeXXq zEO*@dsM>Mw6m)k(_nLsl6Ts=un{np@XU4q`-5K{j@@Cu%8ef2)hqNAX-yA4ig6_2f zrBOonTGeGSOnC`fpT!N?7xCaR^F+{{Rp9%r+8KVr&MF4aZG+MwXuP1B0dxid_$+Yn zd~!9zPmtR|XKXx>X4nZj@B3l3!_Q;KA@@%_WM&Hz4d@

aXk2E4CZqJkm!Fw_X0kQ}z5LGf6Lc;+ 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≪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 QO=srBZEM{+UrsXw00uvViU0rr literal 0 HcmV?d00001 From 7bc21c6691266426824d7a6033c6b8768a93ce70 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 13 May 2022 14:19:04 +0200 Subject: [PATCH 056/254] Allow CSV/TSV reader to read multi-line fields. --- .gitignore | 2 + core/encoding/csv/reader.odin | 86 +++++++++++++++++++++++++--------- demo.bin | Bin 710357 -> 0 bytes 3 files changed, 67 insertions(+), 21 deletions(-) delete mode 100755 demo.bin diff --git a/.gitignore b/.gitignore index e8b3d3050..4c69a3f43 100644 --- a/.gitignore +++ b/.gitignore @@ -269,6 +269,8 @@ bin/ # - Linux/MacOS odin odin.dSYM +*.bin +demo.bin # shared collection shared/ diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index aecb73d7b..f8f1d4051 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -34,6 +34,10 @@ Reader :: struct { // If lazy_quotes is true, a quote may appear in an unquoted field and a non-doubled quote may appear in a quoted field lazy_quotes: bool, + // multiline_fields, when set to true, will treat a field starting with a " as a multiline string + // therefore, instead of reading until the next \n, it'll read until the next " + multiline_fields: bool, + // reuse_record controls whether calls to 'read' may return a slice using the backing buffer // for performance // By default, each call to 'read' returns a newly allocated slice @@ -194,32 +198,72 @@ is_valid_delim :: proc(r: rune) -> bool { @private _read_record :: proc(r: ^Reader, dst: ^[dynamic]string, allocator := context.allocator) -> ([]string, Error) { read_line :: proc(r: ^Reader) -> ([]byte, io.Error) { - line, err := bufio.reader_read_slice(&r.r, '\n') - if err == .Buffer_Full { - clear(&r.raw_buffer) - append(&r.raw_buffer, ..line) - for err == .Buffer_Full { - line, err = bufio.reader_read_slice(&r.r, '\n') + if !r.multiline_fields { + line, err := bufio.reader_read_slice(&r.r, '\n') + if err == .Buffer_Full { + clear(&r.raw_buffer) append(&r.raw_buffer, ..line) + for err == .Buffer_Full { + line, err = bufio.reader_read_slice(&r.r, '\n') + append(&r.raw_buffer, ..line) + } + line = r.raw_buffer[:] } - line = r.raw_buffer[:] - } - if len(line) > 0 && err == .EOF { - err = nil - if line[len(line)-1] == '\r' { - line = line[:len(line)-1] + if len(line) > 0 && err == .EOF { + err = nil + if line[len(line)-1] == '\r' { + line = line[:len(line)-1] + } } - } - r.line_count += 1 + r.line_count += 1 - // normalize \r\n to \n - n := len(line) - for n >= 2 && string(line[n-2:]) == "\r\n" { - line[n-2] = '\n' - line = line[:n-1] - } + // normalize \r\n to \n + n := len(line) + for n >= 2 && string(line[n-2:]) == "\r\n" { + line[n-2] = '\n' + line = line[:n-1] + } + return line, err - return line, err + } else { + // Reading a "line" that can possibly contain multiline fields. + // Unfortunately, this means we need to read a character at a time. + + err: io.Error + cur: rune + is_quoted: bool + + field_length := 0 + + clear(&r.raw_buffer) + + read_loop: for err == .None { + cur, _, err = bufio.reader_read_rune(&r.r) + + if err != .None { break read_loop } + + switch cur { + case '"': + is_quoted = field_length == 0 + field_length += 1 + + case '\n', '\r': + if !is_quoted { break read_loop } + + case r.comma: + field_length = 0 + + case: + field_length += 1 + } + + rune_buf, rune_len := utf8.encode_rune(cur) + append(&r.raw_buffer, ..rune_buf[:rune_len]) + } + + return r.raw_buffer[:], err + } + unreachable() } length_newline :: proc(b: []byte) -> int { diff --git a/demo.bin b/demo.bin deleted file mode 100755 index 5f8c90ed9a208aabdfee04741d4f2304c9c4a40b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 710357 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

aXk2E4CZqJkm!Fw_X0kQ}z5LGf6Lc;+ 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≪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 QO=srBZEM{+UrsXw00uvViU0rr From 7e0c359f993bb595ac794518bf882414133cd1c2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 13 May 2022 15:03:40 +0200 Subject: [PATCH 057/254] Fix thread pool join. --- core/thread/thread_pool.odin | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index af80da9aa..2ff3a6a52 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -39,6 +39,7 @@ Pool :: struct { threads: []^Thread, + tasks: [dynamic]Task, tasks_done: [dynamic]Task, } @@ -102,8 +103,15 @@ pool_join :: proc(pool: ^Pool) { yield() - for t in pool.threads { - join(t) + started_count: int + for started_count < len(pool.threads) { + started_count = 0 + for t in pool.threads { + if .Started in t.flags { + join(t) + started_count += 1 + } + } } } From 58fc305b11f1f4fb4b5f7de037947f905e05f849 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 13 May 2022 15:11:33 +0200 Subject: [PATCH 058/254] Do a bit less work for pool_join. --- core/thread/thread_pool.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 2ff3a6a52..840cecfec 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -103,14 +103,18 @@ pool_join :: proc(pool: ^Pool) { yield() + // Because we already stopped the pool, there's no need to take a lock here. + started_count: int for started_count < len(pool.threads) { started_count = 0 for t in pool.threads { if .Started in t.flags { - join(t) started_count += 1 } + if .Joined not_in t.flags { + join(t) + } } } } From 42371f7aea986f7719972e7d3d7c4f93f3f4c490 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 13 May 2022 15:25:20 +0200 Subject: [PATCH 059/254] Fix fix. --- core/thread/thread_pool.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 840cecfec..4fd5c90d1 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -111,9 +111,9 @@ pool_join :: proc(pool: ^Pool) { for t in pool.threads { if .Started in t.flags { started_count += 1 - } - if .Joined not_in t.flags { - join(t) + if .Joined not_in t.flags { + join(t) + } } } } From 6c0e2e2a53412b4d7ba8b2b3b9bd485af240589b Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 14 May 2022 13:18:38 +0200 Subject: [PATCH 060/254] pool_join should look at .Done. --- core/thread/thread_pool.odin | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 4fd5c90d1..d27ae4255 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -101,16 +101,13 @@ pool_join :: proc(pool: ^Pool) { intrinsics.atomic_store(&pool.is_running, false) sync.post(&pool.sem_available, len(pool.threads)) - yield() - // Because we already stopped the pool, there's no need to take a lock here. - - started_count: int - for started_count < len(pool.threads) { - started_count = 0 + done_count: int + for done_count < len(pool.threads) { + done_count = 0 for t in pool.threads { - if .Started in t.flags { - started_count += 1 + if .Done in t.flags { + done_count += 1 if .Joined not_in t.flags { join(t) } From 3b842ffe29b272ffeccb7fea30d86bca97cb2fce Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 12:27:21 +0100 Subject: [PATCH 061/254] Remove semicolons --- core/builtin/builtin.odin | 132 +++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/core/builtin/builtin.odin b/core/builtin/builtin.odin index 259fdef37..d1cdfa6e7 100644 --- a/core/builtin/builtin.odin +++ b/core/builtin/builtin.odin @@ -1,90 +1,90 @@ // This is purely for documentation package builtin -nil :: nil; -false :: 0!=0; -true :: 0==0; +nil :: nil +false :: 0!=0 +true :: 0==0 -ODIN_OS :: ODIN_OS; -ODIN_ARCH :: ODIN_ARCH; -ODIN_ENDIAN :: ODIN_ENDIAN; -ODIN_VENDOR :: ODIN_VENDOR; -ODIN_VERSION :: ODIN_VERSION; -ODIN_ROOT :: ODIN_ROOT; -ODIN_DEBUG :: ODIN_DEBUG; +ODIN_OS :: ODIN_OS +ODIN_ARCH :: ODIN_ARCH +ODIN_ENDIAN :: ODIN_ENDIAN +ODIN_VENDOR :: ODIN_VENDOR +ODIN_VERSION :: ODIN_VERSION +ODIN_ROOT :: ODIN_ROOT +ODIN_DEBUG :: ODIN_DEBUG -byte :: u8; // alias +byte :: u8 // alias -bool :: bool; -b8 :: b8; -b16 :: b16; -b32 :: b32; -b64 :: b64; +bool :: bool +b8 :: b8 +b16 :: b16 +b32 :: b32 +b64 :: b64 -i8 :: i8; -u8 :: u8; -i16 :: i16; -u16 :: u16; -i32 :: i32; -u32 :: u32; -i64 :: i64; -u64 :: u64; +i8 :: i8 +u8 :: u8 +i16 :: i16 +u16 :: u16 +i32 :: i32 +u32 :: u32 +i64 :: i64 +u64 :: u64 -i128 :: i128; -u128 :: u128; +i128 :: i128 +u128 :: u128 -rune :: rune; +rune :: rune -f16 :: f16; -f32 :: f32; -f64 :: f64; +f16 :: f16 +f32 :: f32 +f64 :: f64 -complex32 :: complex32; -complex64 :: complex64; -complex128 :: complex128; +complex32 :: complex32 +complex64 :: complex64 +complex128 :: complex128 -quaternion64 :: quaternion64; -quaternion128 :: quaternion128; -quaternion256 :: quaternion256; +quaternion64 :: quaternion64 +quaternion128 :: quaternion128 +quaternion256 :: quaternion256 -int :: int; -uint :: uint; -uintptr :: uintptr; +int :: int +uint :: uint +uintptr :: uintptr -rawptr :: rawptr; -string :: string; -cstring :: cstring; -any :: any; +rawptr :: rawptr +string :: string +cstring :: cstring +any :: any -typeid :: typeid; +typeid :: typeid // Endian Specific Types -i16le :: i16le; -u16le :: u16le; -i32le :: i32le; -u32le :: u32le; -i64le :: i64le; -u64le :: u64le; -i128le :: i128le; -u128le :: u128le; +i16le :: i16le +u16le :: u16le +i32le :: i32le +u32le :: u32le +i64le :: i64le +u64le :: u64le +i128le :: i128le +u128le :: u128le -i16be :: i16be; -u16be :: u16be; -i32be :: i32be; -u32be :: u32be; -i64be :: i64be; -u64be :: u64be; -i128be :: i128be; -u128be :: u128be; +i16be :: i16be +u16be :: u16be +i32be :: i32be +u32be :: u32be +i64be :: i64be +u64be :: u64be +i128be :: i128be +u128be :: u128be -f16le :: f16le; -f32le :: f32le; -f64le :: f64le; +f16le :: f16le +f32le :: f32le +f64le :: f64le -f16be :: f16be; -f32be :: f32be; -f64be :: f64be; +f16be :: f16be +f32be :: f32be +f64be :: f64be From 85edcf9cc204751ac8e7a8ac541df712caeb4014 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 14 May 2022 13:34:52 +0200 Subject: [PATCH 062/254] Revert. --- core/thread/thread_pool.odin | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index d27ae4255..820de8ad4 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -101,13 +101,14 @@ pool_join :: proc(pool: ^Pool) { intrinsics.atomic_store(&pool.is_running, false) sync.post(&pool.sem_available, len(pool.threads)) - // Because we already stopped the pool, there's no need to take a lock here. - done_count: int - for done_count < len(pool.threads) { - done_count = 0 + yield() + +started_count: int + for started_count < len(pool.threads) { + started_count = 0 for t in pool.threads { - if .Done in t.flags { - done_count += 1 + if .Started in t.flags { + started_count += 1 if .Joined not_in t.flags { join(t) } From 0cca42a1f4666767d6a138c66ba6b9db4ffa448a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 13:46:10 +0100 Subject: [PATCH 063/254] Add `image.which` file format testing procedures --- core/image/which.odin | 150 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 core/image/which.odin diff --git a/core/image/which.odin b/core/image/which.odin new file mode 100644 index 000000000..771506332 --- /dev/null +++ b/core/image/which.odin @@ -0,0 +1,150 @@ +package image + +import "core:os" + +Which_File_Type :: enum { + Unknown, + + BMP, + EXR, + GIF, + HDR, // Radiance RGBE HDR + JPEG, + PBM, PGM, PPM, // NetPBM family + PIC, // Softimage PIC + PNG, + PSD, // Photoshop PSD + QOI, + SGI_RGB, // Silicon Graphics Image RGB file format + Sun_Rast, // Sun Raster Graphic + TGA, // Targa Truevision + TIFF, + WebP, + XBM, // X BitMap +} + +which :: proc{ + which_bytes, + which_file, +} + +which_bytes :: proc(data: []byte) -> Which_File_Type { + test_tga :: proc(s: string) -> bool { + get8 :: #force_inline proc(s: ^string) -> u8 { + v := s[0] + s^ = s[1:] + return v + } + get16le :: #force_inline proc(s: ^string) -> u16 { + v := u16(s[0]) | u16(s[1])<<16 + s^ = s[2:] + return v + } + s := s + s = s[1:] // skip offset + + color_type := get8(&s) + if color_type > 1 { + return false + } + image_type := get8(&s) // image type + if color_type == 1 { // Colormap (Paletted) Image + if image_type != 1 && image_type != 9 { // color type requires 1 or 9 + return false + } + s = s[4:] // skip index of first colormap + bpcme := get8(&s) // check bits per colormap entry + if bpcme != 8 && bpcme != 15 && bpcme != 16 && bpcme != 24 && bpcme != 32 { + return false + } + s = s[4:] // skip image origin (x, y) + } else { // Normal image without colormap + if image_type != 2 && image_type != 3 && image_type != 10 && image_type != 11 { + return false + } + s = s[9:] // skip colormap specification + } + if get16le(&s) < 1 || get16le(&s) < 1 { // test width and height + return false + } + bpp := get8(&s) // bits per pixel + if color_type == 1 && bpp != 8 && bpp != 16 { + return false + } + if bpp != 8 && bpp != 15 && bpp != 16 && bpp != 24 && bpp != 32 { + return false + } + return true + } + + header: [128]byte + copy(header[:], data) + s := string(header[:]) + + switch { + case s[:2] == "BM": + return .BMP + case s[:4] == "\x76\x2f\x31\x01": + return .EXR + case s[:6] == "GIF87a", s[:6] == "GIF89a": + return .GIF + case s[6:10] == "JFIF", s[6:10] == "Exif": + return .JPEG + case s[:4] == "\xff\xd8\xff\xdb": + return .JPEG + case s[0] == 'P': + switch s[2] { + case '\t', '\n', '\r': + switch s[1] { + case '1', '4': + return .PBM + case '2', '5': + return .PGM + case '3', '6': + return .PPM + } + } + case s[:8] == "\x89PNG\r\n\x1a\n": + return .PNG + case s[:4] == "qoif": + return .QOI + case s[:2] == "\x01\xda": + return .SGI_RGB + case s[:4] == "\x59\xA6\x6A\x95": + return .Sun_Rast + case s[:2] == "MM", s[:2] == "II": + return .TIFF + case s[:4] == "RIFF" && s[8:12] == "WEBP": + return .WebP + case s[:8] == "#define ": + return .XBM + + case s[:11] == "#?RADIANCE\n", s[:7] == "#?RGBE\n": + return .HDR + case s[:4] == "\x38\x42\x50\x53": + return .PSD + case s[:4] != "\x53\x80\xF6\x34" && s[88:92] == "PICT": + return .PIC + case: + // More complex formats + if test_tga(s) { + return .TGA + } + + + } + return .Unknown +} + + +which_file :: proc(path: string) -> Which_File_Type { + f, err := os.open(path) + if err != 0 { + return .Unknown + } + header: [128]byte + os.read(f, header[:]) + file_type := which_bytes(header[:]) + os.close(f) + return file_type +} \ No newline at end of file From f17a9dd5e7921ddf288de266b463ac01a680ca48 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 13:59:49 +0100 Subject: [PATCH 064/254] Add even more file formats --- core/image/which.odin | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/core/image/which.odin b/core/image/which.odin index 771506332..c34f7fe35 100644 --- a/core/image/which.odin +++ b/core/image/which.odin @@ -7,18 +7,22 @@ Which_File_Type :: enum { BMP, EXR, + FLIF, GIF, HDR, // Radiance RGBE HDR + ICNS, // Apple Icon Image JPEG, + JPEG_2000, + JPEG_XL, PBM, PGM, PPM, // NetPBM family PIC, // Softimage PIC - PNG, + PNG, // Portable Network Graphics PSD, // Photoshop PSD - QOI, + QOI, // Quite Okay Image SGI_RGB, // Silicon Graphics Image RGB file format Sun_Rast, // Sun Raster Graphic TGA, // Targa Truevision - TIFF, + TIFF, // Tagged Image File Format WebP, XBM, // X BitMap } @@ -90,8 +94,19 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .GIF case s[6:10] == "JFIF", s[6:10] == "Exif": return .JPEG - case s[:4] == "\xff\xd8\xff\xdb": - return .JPEG + case s[:3] == "\xff\xd8\xff": + switch s[4] { + case 0xdb, 0xee, 0xe1, 0xe0: + return .JPEG + } + switch { + case s[:12] == "\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01": + return .JPEG + } + case s[:4] == "\xff\x4f\xff\x51", s[:12] == "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a": + return .JPEG_2000 + case s[:12] == "\x00\x00\x00\x0c\x4a\x58\x4c\x20\x0d\x0a\x87\x0a": + return .JPEG_XL case s[0] == 'P': switch s[2] { case '\t', '\n', '\r': @@ -112,7 +127,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .SGI_RGB case s[:4] == "\x59\xA6\x6A\x95": return .Sun_Rast - case s[:2] == "MM", s[:2] == "II": + case s[:4] == "MM\x2a\x00", s[:4] == "II\x00\x2A": return .TIFF case s[:4] == "RIFF" && s[8:12] == "WEBP": return .WebP @@ -125,6 +140,10 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .PSD case s[:4] != "\x53\x80\xF6\x34" && s[88:92] == "PICT": return .PIC + case s[:4] == "\x69\x63\x6e\x73": + return .ICNS + case s[:4] == "\x46\x4c\x49\x46": + return .FLIF case: // More complex formats if test_tga(s) { From 7057f5fc115db9413b33a60833e9b02fb4234842 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 14 May 2022 15:17:37 +0200 Subject: [PATCH 065/254] Add PAM and PFM to format detection. --- core/image/which.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/image/which.odin b/core/image/which.odin index c34f7fe35..30cb78405 100644 --- a/core/image/which.odin +++ b/core/image/which.odin @@ -14,7 +14,7 @@ Which_File_Type :: enum { JPEG, JPEG_2000, JPEG_XL, - PBM, PGM, PPM, // NetPBM family + PBM, PGM, PPM, PAM, PFM, // NetPBM family PIC, // Softimage PIC PNG, // Portable Network Graphics PSD, // Photoshop PSD @@ -117,6 +117,10 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .PGM case '3', '6': return .PPM + case '7': + return .PAM + case 'F', 'f': + return .PFM } } case s[:8] == "\x89PNG\r\n\x1a\n": From 9921ac01cca70d30bbb551070d72bc8e7f651def Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 14:26:11 +0100 Subject: [PATCH 066/254] Add more NetPBM variants --- core/image/which.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/image/which.odin b/core/image/which.odin index c34f7fe35..c3caef621 100644 --- a/core/image/which.odin +++ b/core/image/which.odin @@ -14,7 +14,7 @@ Which_File_Type :: enum { JPEG, JPEG_2000, JPEG_XL, - PBM, PGM, PPM, // NetPBM family + PBM, PGM, PPM, PAM, PFM, // NetPBM family PIC, // Softimage PIC PNG, // Portable Network Graphics PSD, // Photoshop PSD @@ -117,6 +117,10 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .PGM case '3', '6': return .PPM + case '7': + return .PAM + case 'f', 'F': + return .PFM } } case s[:8] == "\x89PNG\r\n\x1a\n": From 01e8e682c0a6021c117553c53f181487be1c841c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 14:38:26 +0100 Subject: [PATCH 067/254] Generalized `core:image` loader ```odin import "core:image" import "core:image/png" ... img, err := image.load_from_file("path.png") ``` --- core/image/common.odin | 1 + core/image/general_loader.odin | 39 ++++++++++++++++++++++++++++++++++ core/image/netpbm/netpbm.odin | 12 +++++++++++ core/image/png/png.odin | 6 ++++++ core/image/qoi/qoi.odin | 5 +++++ 5 files changed, 63 insertions(+) create mode 100644 core/image/general_loader.odin diff --git a/core/image/common.odin b/core/image/common.odin index 4085a0a05..75a649e52 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -172,6 +172,7 @@ General_Image_Error :: enum { Unable_To_Write_File, // Invalid + Unsupported_Format, Invalid_Signature, Invalid_Input_Image, Image_Dimensions_Too_Large, diff --git a/core/image/general_loader.odin b/core/image/general_loader.odin new file mode 100644 index 000000000..73f50f055 --- /dev/null +++ b/core/image/general_loader.odin @@ -0,0 +1,39 @@ +package image + +import "core:mem" +import "core:os" + +Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) + +@(private) +_internal_loaders: [Which_File_Type]Loader_Proc + +register_loader :: proc(kind: Which_File_Type, loader: Loader_Proc) { + assert(_internal_loaders[kind] == nil) + _internal_loaders[kind] = loader +} + +load :: proc{ + load_from_slice, + load_from_file, +} + +load_from_slice :: proc(data: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + loader := _internal_loaders[which(data)] + if loader == nil { + return nil, .Unsupported_Format + } + return loader(data, options, allocator) +} + + +load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + data, ok := os.read_entire_file(filename, allocator) + defer delete(data, allocator) + if ok { + return load_from_slice(data, options, allocator) + } else { + img = new(Image, allocator) + return img, .Unable_To_Read_File + } +} diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index 9574faa26..bcc78c402 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -748,4 +748,16 @@ autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, for // We couldn't find a suitable format return {}, false +} + +@(init, private) +_register :: proc() { + loader :: proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) { + return load_from_buffer(data, allocator) + } + register_loader(.PBM, loader) + register_loader(.PGM, loader) + register_loader(.PPM, loader) + register_loader(.PAM, loader) + register_loader(.PFM, loader) } \ No newline at end of file diff --git a/core/image/png/png.odin b/core/image/png/png.odin index d526dfb27..3dcd0df38 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -1640,3 +1640,9 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH } load :: proc{load_from_file, load_from_slice, load_from_context} + + +@(init, private) +_register :: proc() { + image.register_loader(.PNG, load_from_slice) +} \ No newline at end of file diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index 83b212be8..346356094 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -403,4 +403,9 @@ qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) { i4 := u16(pixel.a) * 11 return u8((i1 + i2 + i3 + i4) & 63) +} + +@(init, private) +_register :: proc() { + image.register_loader(.QOI, load_from_slice) } \ No newline at end of file From 5af7004f442c9790b176b46053185c42f9d65704 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 14:43:50 +0100 Subject: [PATCH 068/254] Add image packages to examples/all --- core/image/netpbm/netpbm.odin | 10 +++++----- examples/all/all_main.odin | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index bcc78c402..fb88cdc8c 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -755,9 +755,9 @@ _register :: proc() { loader :: proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) { return load_from_buffer(data, allocator) } - register_loader(.PBM, loader) - register_loader(.PGM, loader) - register_loader(.PPM, loader) - register_loader(.PAM, loader) - register_loader(.PFM, loader) + image.register_loader(.PBM, loader) + image.register_loader(.PGM, loader) + image.register_loader(.PPM, loader) + image.register_loader(.PAM, loader) + image.register_loader(.PFM, loader) } \ No newline at end of file diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 36acf7714..5a0bc4d3e 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -62,8 +62,10 @@ import fmt "core:fmt" import hash "core:hash" import image "core:image" +import netpbm "core:image/netpbm" import png "core:image/png" import qoi "core:image/qoi" +import tga "core:image/tga" import io "core:io" import log "core:log" From 5bc866e420d8eb9e909db71e230d1283c6116d7e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 14:44:24 +0100 Subject: [PATCH 069/254] Allow for `import _ "foo"` to allow for `@(init)` procedures; Remove `using import` code --- src/checker.cpp | 48 ++++++++++-------------------------------------- src/parser.cpp | 14 ++++---------- src/parser.hpp | 1 - 3 files changed, 14 insertions(+), 49 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index d186163e4..da9a97622 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4356,6 +4356,9 @@ void check_add_import_decl(CheckerContext *ctx, Ast *decl) { } String import_name = path_to_entity_name(id->import_name.string, id->fullpath, false); + if (is_blank_ident(import_name)) { + force_use = true; + } // NOTE(bill, 2019-05-19): If the directory path is not a valid entity name, force the user to assign a custom one // if (import_name.len == 0 || import_name == "_") { @@ -4363,17 +4366,13 @@ void check_add_import_decl(CheckerContext *ctx, Ast *decl) { // } if (import_name.len == 0 || is_blank_ident(import_name)) { - if (id->is_using) { - // TODO(bill): Should this be a warning? - } else { - if (id->import_name.string == "") { - String invalid_name = id->fullpath; - invalid_name = get_invalid_import_name(invalid_name); + if (id->import_name.string == "") { + String invalid_name = id->fullpath; + invalid_name = get_invalid_import_name(invalid_name); - error(id->token, "Import name %.*s, is not a valid identifier. Perhaps you want to reference the package by a different name like this: import \"%.*s\" ", LIT(invalid_name), LIT(invalid_name)); - } else { - error(token, "Import name, %.*s, cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); - } + error(id->token, "Import name %.*s, is not a valid identifier. Perhaps you want to reference the package by a different name like this: import \"%.*s\" ", LIT(invalid_name), LIT(invalid_name)); + } else { + error(token, "Import name, %.*s, cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); } } else { GB_ASSERT(id->import_name.pos.line != 0); @@ -4383,38 +4382,11 @@ void check_add_import_decl(CheckerContext *ctx, Ast *decl) { scope); add_entity(ctx, parent_scope, nullptr, e); - if (force_use || id->is_using) { + if (force_use) { add_entity_use(ctx, nullptr, e); } } - if (id->is_using) { - if (parent_scope->flags & ScopeFlag_Global) { - error(id->import_name, "built-in package imports cannot use using"); - return; - } - - // NOTE(bill): Add imported entities to this file's scope - for_array(elem_index, scope->elements.entries) { - String name = scope->elements.entries[elem_index].key.string; - Entity *e = scope->elements.entries[elem_index].value; - if (e->scope == parent_scope) continue; - - if (is_entity_exported(e, true)) { - Entity *found = scope_lookup_current(parent_scope, name); - if (found != nullptr) { - // NOTE(bill): - // Date: 2019-03-17 - // The order has to be the other way around as `using` adds the entity into the that - // file scope otherwise the error would be the wrong way around - redeclaration_error(name, found, e); - } else { - add_entity_with_name(ctx, parent_scope, e->identifier, e, name); - } - } - } - } - scope->flags |= ScopeFlag_HasBeenImported; } diff --git a/src/parser.cpp b/src/parser.cpp index df7f908a6..1f4093e5f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1160,11 +1160,10 @@ Ast *ast_package_decl(AstFile *f, Token token, Token name, CommentGroup *docs, C return result; } -Ast *ast_import_decl(AstFile *f, Token token, bool is_using, Token relpath, Token import_name, +Ast *ast_import_decl(AstFile *f, Token token, Token relpath, Token import_name, CommentGroup *docs, CommentGroup *comment) { Ast *result = alloc_ast_node(f, Ast_ImportDecl); result->ImportDecl.token = token; - result->ImportDecl.is_using = is_using; result->ImportDecl.relpath = relpath; result->ImportDecl.import_name = import_name; result->ImportDecl.docs = docs; @@ -4382,7 +4381,6 @@ Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) { CommentGroup *docs = f->lead_comment; Token token = expect_token(f, Token_import); Token import_name = {}; - bool is_using = kind != ImportDecl_Standard; switch (f->curr_token.kind) { case Token_Ident: @@ -4393,22 +4391,18 @@ Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) { break; } - if (!is_using && is_blank_ident(import_name)) { - syntax_error(import_name, "Illegal import name: '_'"); - } - Token file_path = expect_token_after(f, Token_String, "import"); Ast *s = nullptr; if (f->curr_proc != nullptr) { - syntax_error(import_name, "You cannot use 'import' within a procedure. This must be done at the file scope"); + syntax_error(import_name, "Cannot use 'import' within a procedure. This must be done at the file scope"); s = ast_bad_decl(f, import_name, file_path); } else { - s = ast_import_decl(f, token, is_using, file_path, import_name, docs, f->line_comment); + s = ast_import_decl(f, token, file_path, import_name, docs, f->line_comment); array_add(&f->imports, s); } - if (is_using) { + if (kind != ImportDecl_Standard) { syntax_error(import_name, "'using import' is not allowed, please use the import name explicitly"); } diff --git a/src/parser.hpp b/src/parser.hpp index c7b4fd0d8..698ed7623 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -585,7 +585,6 @@ AST_KIND(_DeclBegin, "", bool) \ Token import_name; \ CommentGroup *docs; \ CommentGroup *comment; \ - bool is_using; \ }) \ AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \ Token token; \ From e46d87b2210a1e2f0aa95827808f3b69b11f17dc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 14:47:58 +0100 Subject: [PATCH 070/254] Fix type and keep -vet happy --- core/image/netpbm/netpbm.odin | 2 +- core/image/tga/tga.odin | 1 - examples/all/all_main.odin | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index fb88cdc8c..83c8a2f59 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -752,7 +752,7 @@ autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, for @(init, private) _register :: proc() { - loader :: proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) { + loader :: proc(data: []byte, options: image.Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) { return load_from_buffer(data, allocator) } image.register_loader(.PBM, loader) diff --git a/core/image/tga/tga.odin b/core/image/tga/tga.odin index 0539706b3..67a088eb5 100644 --- a/core/image/tga/tga.odin +++ b/core/image/tga/tga.odin @@ -12,7 +12,6 @@ package tga import "core:mem" import "core:image" -import "core:compress" import "core:bytes" import "core:os" diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 5a0bc4d3e..36326b48e 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -166,8 +166,10 @@ _ :: xml _ :: fmt _ :: hash _ :: image +_ :: netpbm _ :: png _ :: qoi +_ :: tga _ :: io _ :: log _ :: math From 9c1f270bd56e367a34691517daa11194d2ef14e9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 14:55:15 +0100 Subject: [PATCH 071/254] Rename `load_from_slice` to `load_from_bytes` across `core` --- core/compress/gzip/example.odin | 2 +- core/compress/gzip/gzip.odin | 8 ++++---- core/image/general_loader.odin | 6 +++--- core/image/png/png.odin | 14 ++++++-------- core/image/qoi/qoi.odin | 10 +++++----- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/core/compress/gzip/example.odin b/core/compress/gzip/example.odin index 0e2c2b9f6..c010d5979 100644 --- a/core/compress/gzip/example.odin +++ b/core/compress/gzip/example.odin @@ -45,7 +45,7 @@ main :: proc() { if len(args) < 2 { stderr("No input file specified.\n") - err := load(slice=TEST, buf=&buf, known_gzip_size=len(TEST)) + err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST)) if err == nil { stdout("Displaying test vector: ") stdout(bytes.buffer_to_string(&buf)) diff --git a/core/compress/gzip/gzip.odin b/core/compress/gzip/gzip.odin index 4482d4a7e..4de4d1b63 100644 --- a/core/compress/gzip/gzip.odin +++ b/core/compress/gzip/gzip.odin @@ -102,7 +102,7 @@ E_Deflate :: compress.Deflate_Error GZIP_MAX_PAYLOAD_SIZE :: i64(max(u32le)) -load :: proc{load_from_slice, load_from_file, load_from_context} +load :: proc{load_from_bytes, load_from_file, load_from_context} load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { context.allocator = allocator @@ -112,16 +112,16 @@ load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_siz err = E_General.File_Not_Found if ok { - err = load_from_slice(data, buf, len(data), expected_output_size) + err = load_from_bytes(data, buf, len(data), expected_output_size) } return } -load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { +load_from_bytes :: proc(data: []byte, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { buf := buf z := &compress.Context_Memory_Input{ - input_data = slice, + input_data = data, output = buf, } return load_from_context(z, buf, known_gzip_size, expected_output_size, allocator) diff --git a/core/image/general_loader.odin b/core/image/general_loader.odin index 73f50f055..4cc8569ab 100644 --- a/core/image/general_loader.odin +++ b/core/image/general_loader.odin @@ -14,11 +14,11 @@ register_loader :: proc(kind: Which_File_Type, loader: Loader_Proc) { } load :: proc{ - load_from_slice, + load_from_bytes, load_from_file, } -load_from_slice :: proc(data: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { loader := _internal_loaders[which(data)] if loader == nil { return nil, .Unsupported_Format @@ -31,7 +31,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont data, ok := os.read_entire_file(filename, allocator) defer delete(data, allocator) if ok { - return load_from_slice(data, options, allocator) + return load_from_bytes(data, options, allocator) } else { img = new(Image, allocator) return img, .Unable_To_Read_File diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 3dcd0df38..f91ad2b66 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -18,7 +18,6 @@ import "core:compress/zlib" import "core:image" import "core:os" -import "core:strings" import "core:hash" import "core:bytes" import "core:io" @@ -318,13 +317,12 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) { } chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string { - t := transmute(^u8)type - return strings.string_from_ptr(t, 4) + return string(([^]u8)(type)[:4]) } -load_from_slice :: proc(slice: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { ctx := &compress.Context_Memory_Input{ - input_data = slice, + input_data = data, } /* @@ -344,7 +342,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont defer delete(data) if ok { - return load_from_slice(data, options) + return load_from_bytes(data, options) } else { img = new(Image) return img, .Unable_To_Read_File @@ -1639,10 +1637,10 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH return nil } -load :: proc{load_from_file, load_from_slice, load_from_context} +load :: proc{load_from_file, load_from_bytes, load_from_context} @(init, private) _register :: proc() { - image.register_loader(.PNG, load_from_slice) + image.register_loader(.PNG, load_from_bytes) } \ No newline at end of file diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index 346356094..884f4963b 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -180,9 +180,9 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato save :: proc{save_to_memory, save_to_file} -load_from_slice :: proc(slice: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { ctx := &compress.Context_Memory_Input{ - input_data = slice, + input_data = data, } img, err = load_from_context(ctx, options, allocator) @@ -196,7 +196,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont defer delete(data) if ok { - return load_from_slice(data, options) + return load_from_bytes(data, options) } else { img = new(Image) return img, .Unable_To_Read_File @@ -359,7 +359,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return } -load :: proc{load_from_file, load_from_slice, load_from_context} +load :: proc{load_from_file, load_from_bytes, load_from_context} /* Cleanup of image-specific data. @@ -407,5 +407,5 @@ qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) { @(init, private) _register :: proc() { - image.register_loader(.QOI, load_from_slice) + image.register_loader(.QOI, load_from_bytes) } \ No newline at end of file From 4e080057fb0f24e66bb6ad4eccb931f1f325da9b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 15:01:17 +0100 Subject: [PATCH 072/254] Rename `load_from_buffer` to `load_from_bytes` --- core/image/netpbm/netpbm.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index 83c8a2f59..bfaf49fc6 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -28,7 +28,7 @@ BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM load :: proc { load_from_file, - load_from_buffer, + load_from_bytes, } load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) { @@ -40,10 +40,10 @@ load_from_file :: proc(filename: string, allocator := context.allocator) -> (img return } - return load_from_buffer(data) + return load_from_bytes(data) } -load_from_buffer :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) { +load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) { context.allocator = allocator img = new(Image) @@ -753,7 +753,7 @@ autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, for @(init, private) _register :: proc() { loader :: proc(data: []byte, options: image.Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) { - return load_from_buffer(data, allocator) + return load_from_bytes(data, allocator) } image.register_loader(.PBM, loader) image.register_loader(.PGM, loader) From 3aa0a733f3b59388ae320f07cfabb1f4e7f0cec2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 15:06:55 +0100 Subject: [PATCH 073/254] Add `destroy` with loader --- core/image/general_loader.odin | 12 +++++++++--- core/image/netpbm/netpbm.odin | 13 ++++++++----- core/image/png/png.odin | 5 ++--- core/image/qoi/qoi.odin | 5 ++--- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/core/image/general_loader.odin b/core/image/general_loader.odin index 4cc8569ab..3acffb452 100644 --- a/core/image/general_loader.odin +++ b/core/image/general_loader.odin @@ -4,13 +4,20 @@ import "core:mem" import "core:os" Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) +Destroy_Proc :: #type proc(img: ^Image) @(private) _internal_loaders: [Which_File_Type]Loader_Proc +_internal_destroyers: [Which_File_Type]Destroy_Proc -register_loader :: proc(kind: Which_File_Type, loader: Loader_Proc) { +register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) { + assert(loader != nil) + assert(destroyer != nil) assert(_internal_loaders[kind] == nil) _internal_loaders[kind] = loader + + assert(_internal_destroyers[kind] == nil) + _internal_destroyers[kind] = destroyer } load :: proc{ @@ -33,7 +40,6 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont if ok { return load_from_bytes(data, options, allocator) } else { - img = new(Image, allocator) - return img, .Unable_To_Read_File + return nil, .Unable_To_Read_File } } diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index bfaf49fc6..778ec2c5e 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -755,9 +755,12 @@ _register :: proc() { loader :: proc(data: []byte, options: image.Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) { return load_from_bytes(data, allocator) } - image.register_loader(.PBM, loader) - image.register_loader(.PGM, loader) - image.register_loader(.PPM, loader) - image.register_loader(.PAM, loader) - image.register_loader(.PFM, loader) + destroyer :: proc(img: ^Image) { + _ = destroy(img) + } + image.register(.PBM, loader, destroyer) + image.register(.PGM, loader, destroyer) + image.register(.PPM, loader, destroyer) + image.register(.PAM, loader, destroyer) + image.register(.PFM, loader, destroyer) } \ No newline at end of file diff --git a/core/image/png/png.odin b/core/image/png/png.odin index f91ad2b66..ea888d0ad 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -344,8 +344,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont if ok { return load_from_bytes(data, options) } else { - img = new(Image) - return img, .Unable_To_Read_File + return nil, .Unable_To_Read_File } } @@ -1642,5 +1641,5 @@ load :: proc{load_from_file, load_from_bytes, load_from_context} @(init, private) _register :: proc() { - image.register_loader(.PNG, load_from_bytes) + image.register(.PNG, load_from_bytes, destroy) } \ No newline at end of file diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index 884f4963b..f10f2ff56 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -198,8 +198,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont if ok { return load_from_bytes(data, options) } else { - img = new(Image) - return img, .Unable_To_Read_File + return nil, .Unable_To_Read_File } } @@ -407,5 +406,5 @@ qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) { @(init, private) _register :: proc() { - image.register_loader(.QOI, load_from_bytes) + image.register(.QOI, load_from_bytes, destroy) } \ No newline at end of file From c516fb947f7aafd00363dfcdaefa79f96e4f4ee5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 15:11:23 +0100 Subject: [PATCH 074/254] Add `image.destroy` --- core/image/common.odin | 1 + core/image/general_loader.odin | 13 +++++++++++++ core/image/netpbm/netpbm.odin | 7 ++----- core/image/png/png.odin | 1 + core/image/qoi/qoi.odin | 1 + core/image/which.odin | 22 +++++++++++----------- 6 files changed, 29 insertions(+), 16 deletions(-) diff --git a/core/image/common.odin b/core/image/common.odin index 75a649e52..28129a6e1 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -54,6 +54,7 @@ Image :: struct { */ background: Maybe(RGB_Pixel_16), metadata: Image_Metadata, + which: Which_File_Type, } Image_Metadata :: union { diff --git a/core/image/general_loader.odin b/core/image/general_loader.odin index 3acffb452..79f5fb737 100644 --- a/core/image/general_loader.odin +++ b/core/image/general_loader.odin @@ -43,3 +43,16 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont return nil, .Unable_To_Read_File } } + +destroy :: proc(img: ^Image, allocator := context.allocator) -> bool { + if img == nil { + return true + } + context.allocator = allocator + destroyer := _internal_destroyers[img.which] + if destroyer != nil { + destroyer(img) + } + free(img) + return true +} \ No newline at end of file diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index 778ec2c5e..5a504cd7c 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -47,6 +47,7 @@ load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^ context.allocator = allocator img = new(Image) + img.which = .NetPBM header: Header; defer header_destroy(&header) header_size: int @@ -758,9 +759,5 @@ _register :: proc() { destroyer :: proc(img: ^Image) { _ = destroy(img) } - image.register(.PBM, loader, destroyer) - image.register(.PGM, loader, destroyer) - image.register(.PPM, loader, destroyer) - image.register(.PAM, loader, destroyer) - image.register(.PFM, loader, destroyer) + image.register(.NetPBM, loader, destroyer) } \ No newline at end of file diff --git a/core/image/png/png.odin b/core/image/png/png.odin index ea888d0ad..35fdb58d8 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -372,6 +372,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a if img == nil { img = new(Image) } + img.which = .PNG info := new(image.PNG_Info) img.metadata = info diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index f10f2ff56..29a17d4f4 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -224,6 +224,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a if img == nil { img = new(Image) } + img.which = .QOI if .return_metadata in options { info := new(image.QOI_Info) diff --git a/core/image/which.odin b/core/image/which.odin index 30cb78405..82cb03ce6 100644 --- a/core/image/which.odin +++ b/core/image/which.odin @@ -14,7 +14,7 @@ Which_File_Type :: enum { JPEG, JPEG_2000, JPEG_XL, - PBM, PGM, PPM, PAM, PFM, // NetPBM family + NetPBM, // NetPBM family PIC, // Softimage PIC PNG, // Portable Network Graphics PSD, // Photoshop PSD @@ -111,16 +111,16 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { switch s[2] { case '\t', '\n', '\r': switch s[1] { - case '1', '4': - return .PBM - case '2', '5': - return .PGM - case '3', '6': - return .PPM - case '7': - return .PAM - case 'F', 'f': - return .PFM + case '1', '4': // PBM + return .NetPBM + case '2', '5': // PGM + return .NetPBM + case '3', '6': // PPM + return .NetPBM + case '7': // PAM + return .NetPBM + case 'F', 'f': // PFM + return .NetPBM } } case s[:8] == "\x89PNG\r\n\x1a\n": From e8485ee7e7b5487bad1e195235a93543c82b630a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 15:15:04 +0100 Subject: [PATCH 075/254] Correction to `image.destroy` --- core/image/common.odin | 2 +- core/image/general_loader.odin | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/image/common.odin b/core/image/common.odin index 28129a6e1..baacd64d9 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -57,7 +57,7 @@ Image :: struct { which: Which_File_Type, } -Image_Metadata :: union { +Image_Metadata :: union #shared_nil { ^Netpbm_Info, ^PNG_Info, ^QOI_Info, diff --git a/core/image/general_loader.odin b/core/image/general_loader.odin index 79f5fb737..bf0061311 100644 --- a/core/image/general_loader.odin +++ b/core/image/general_loader.odin @@ -52,7 +52,9 @@ destroy :: proc(img: ^Image, allocator := context.allocator) -> bool { destroyer := _internal_destroyers[img.which] if destroyer != nil { destroyer(img) + } else { + assert(img.metadata == nil) + free(img) } - free(img) return true } \ No newline at end of file From 22dc02064795a72d5ea4704195638e6f10d7b434 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 15:16:14 +0100 Subject: [PATCH 076/254] Destroy pixel buffer --- core/image/general_loader.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/image/general_loader.odin b/core/image/general_loader.odin index bf0061311..21a662967 100644 --- a/core/image/general_loader.odin +++ b/core/image/general_loader.odin @@ -2,6 +2,7 @@ package image import "core:mem" import "core:os" +import "core:bytes" Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) Destroy_Proc :: #type proc(img: ^Image) @@ -54,6 +55,7 @@ destroy :: proc(img: ^Image, allocator := context.allocator) -> bool { destroyer(img) } else { assert(img.metadata == nil) + bytes.buffer_destroy(&img.pixels) free(img) } return true From d74e4b427d873b56bf6069c674107197fc44774b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 15:16:56 +0100 Subject: [PATCH 077/254] Remove `bool` return on `image.destroy` --- core/image/general_loader.odin | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/image/general_loader.odin b/core/image/general_loader.odin index 21a662967..36629c39e 100644 --- a/core/image/general_loader.odin +++ b/core/image/general_loader.odin @@ -45,9 +45,9 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont } } -destroy :: proc(img: ^Image, allocator := context.allocator) -> bool { +destroy :: proc(img: ^Image, allocator := context.allocator) { if img == nil { - return true + return } context.allocator = allocator destroyer := _internal_destroyers[img.which] @@ -58,5 +58,4 @@ destroy :: proc(img: ^Image, allocator := context.allocator) -> bool { bytes.buffer_destroy(&img.pixels) free(img) } - return true } \ No newline at end of file From f7b18cd86ebce34f4b62ba313a998bc45c513d04 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 May 2022 15:32:28 +0100 Subject: [PATCH 078/254] Add DjVu --- core/image/which.odin | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/image/which.odin b/core/image/which.odin index 82cb03ce6..ab608174f 100644 --- a/core/image/which.odin +++ b/core/image/which.odin @@ -6,6 +6,7 @@ Which_File_Type :: enum { Unknown, BMP, + DjVu, // AT&T DjVu file format EXR, FLIF, GIF, @@ -88,6 +89,11 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { switch { case s[:2] == "BM": return .BMP + case s[:8] == "AT&TFORM": + switch s[12:16] { + case "DJVU", "DJVM": + return .DjVu + } case s[:4] == "\x76\x2f\x31\x01": return .EXR case s[:6] == "GIF87a", s[:6] == "GIF89a": From b5b329378f8b90f2de199a51d8b88056ffe7980a Mon Sep 17 00:00:00 2001 From: Tetralux Date: Sat, 14 May 2022 19:17:58 +0000 Subject: [PATCH 079/254] [os] Linux: Add os.exists(), os.get_env(), os.lookup_env(), os.set_env() exists() does the access() syscall. Renames getenv() to get_env() to match Windows. --- core/os/os_linux.odin | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 9b712cecc..e4ce37567 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -415,6 +415,7 @@ foreign libc { @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="putenv") _unix_putenv :: proc(cstring) -> c.int --- @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- @@ -579,6 +580,11 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { is_file :: proc {is_file_path, is_file_handle} is_dir :: proc {is_dir_path, is_dir_handle} +exists :: proc(path: string) -> bool { + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cpath, O_RDONLY) + return res == 0 +} // NOTE(bill): Uses startup to initialize it @@ -764,13 +770,28 @@ heap_free :: proc(ptr: rawptr) { _unix_free(ptr) } -getenv :: proc(name: string) -> (string, bool) { - path_str := strings.clone_to_cstring(name, context.temp_allocator) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false } - return string(cstr), true + return strings.clone(string(cstr), allocator), true +} + +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +set_env :: proc(key, value: string) -> Errno { + s := strings.concatenate({key, "=", value, "\x00"}, context.temp_allocator) + res := _unix_putenv(strings.unsafe_string_to_cstring(s)) + if res < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE } get_current_directory :: proc() -> string { From 23cb96de022f91e1798f73b920f972845081b370 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 16:37:05 +0100 Subject: [PATCH 080/254] Commit `import _` changes --- src/checker.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index da9a97622..8afc6eb14 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4365,15 +4365,12 @@ void check_add_import_decl(CheckerContext *ctx, Ast *decl) { // import_name = scope->pkg->name; // } - if (import_name.len == 0 || is_blank_ident(import_name)) { - if (id->import_name.string == "") { - String invalid_name = id->fullpath; - invalid_name = get_invalid_import_name(invalid_name); + if (import_name.len == 0) { + String invalid_name = id->fullpath; + invalid_name = get_invalid_import_name(invalid_name); - error(id->token, "Import name %.*s, is not a valid identifier. Perhaps you want to reference the package by a different name like this: import \"%.*s\" ", LIT(invalid_name), LIT(invalid_name)); - } else { - error(token, "Import name, %.*s, cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); - } + error(id->token, "Import name %.*s, is not a valid identifier. Perhaps you want to reference the package by a different name like this: import \"%.*s\" ", LIT(invalid_name), LIT(invalid_name)); + error(token, "Import name, %.*s, cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); } else { GB_ASSERT(id->import_name.pos.line != 0); id->import_name.string = import_name; From be2c7b5c9b7db56c9e72a7b8ba6ba861999ee3be Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 21:53:16 +0100 Subject: [PATCH 081/254] Add numerous different random distribution procedures --- core/math/rand/rand.odin | 252 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 19e475835..7f81dc6b2 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -1,6 +1,7 @@ package rand import "core:intrinsics" +import "core:math" Rand :: struct { state: u64, @@ -119,11 +120,262 @@ int_max :: proc(n: int, r: ^Rand = nil) -> int { } } +// Uniform random distribution [0, 1) float64 :: proc(r: ^Rand = nil) -> f64 { return f64(int63_max(1<<53, r)) / (1 << 53) } +// Uniform random distribution [0, 1) float32 :: proc(r: ^Rand = nil) -> f32 { return f32(float64(r)) } float64_range :: proc(lo, hi: f64, r: ^Rand = nil) -> f64 { return (hi-lo)*float64(r) + lo } float32_range :: proc(lo, hi: f32, r: ^Rand = nil) -> f32 { return (hi-lo)*float32(r) + lo } +float64_uniform :: float64_range +float32_uniform :: float32_range + + +// Triangular Distribution +// See: http://wikipedia.org/wiki/Triangular_distribution +float64_trianglular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 { + if hi-lo == 0 { + return lo + } + lo, hi := lo, hi + u := float64(r) + c := f64(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) + if u > c { + u = 1-u + c = 1-c + lo, hi = hi, lo + } + return lo + (hi - lo) * math.sqrt(u * c) + +} +// Triangular Distribution +// See: http://wikipedia.org/wiki/Triangular_distribution +float32_trianglular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 { + + if hi-lo == 0 { + return lo + } + lo, hi := lo, hi + u := float32(r) + c := f32(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) + if u > c { + u = 1-u + c = 1-c + lo, hi = hi, lo + } + return lo + (hi - lo) * math.sqrt(u * c) +} + + +// Normal/Gaussian Distribution +float64_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { + return norm_float64(r) * stddev + mean +} +// Normal/Gaussian Distribution +float32_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { + return f32(float64_normal(f64(mean), f64(stddev), r)) +} + + +// Log Normal Distribution +float64_log_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { + return math.ln(float64_normal(mean, stddev, r)) +} +// Log Normal Distribution +float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { + return f32(float64_log_normal(f64(mean), f64(stddev), r)) +} + + +// Exponential Distribution +// `lambda` is 1.0/(desired mean). It should be non-zero. +// Return values range from +// 0 to positive infinity if lambda > 0 +// negative infinity to 0 if lambda <= 0 +float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 { + return - math.ln(1 - float64(r)) / lambda +} +// Exponential Distribution +// `lambda` is 1.0/(desired mean). It should be non-zero. +// Return values range from +// 0 to positive infinity if lambda > 0 +// negative infinity to 0 if lambda <= 0 +float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 { + return f32(float64_exponential(f64(lambda), r)) +} + + +// Gamma Distribution (NOT THE GAMMA FUNCTION) +// +// Required: alpha > 0 and beta > 0 +// +// math.pow(x, alpha-1) * math.exp(-x / beta) +// pdf(x) = -------------------------------------------- +// math.gamma(alpha) * math.pow(beta, alpha) +// +// mean is alpha*beta, variance is math.pow(alpha*beta, 2) +float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + if alpha <= 0 || beta <= 0 { + panic(#procedure + ": alpha and beta must be > 0.0") + } + + LOG4 :: 1.3862943611198906188344642429163531361510002687205105082413600189 + SG_MAGIC_CONST :: 2.5040773967762740733732583523868748412194809812852436493487 + + switch { + case alpha > 1: + // R.C.H. Cheng, "The generation of Gamma variables with non-integral shape parameters", Applied Statistics, (1977), 26, No. 1, p71-74 + + ainv := math.sqrt(2 * alpha - 1) + bbb := alpha - LOG4 + ccc := alpha + ainv + for { + u1 := float64(r) + if !(1e-7 < u1 && u1 < 0.9999999) { + continue + } + u2 := 1 - float64(r) + v := math.ln(u1 / (1 - u1)) / ainv + x := alpha * math.exp(v) + z := u1 * u1 * u2 + t := bbb + ccc*v - x + if t + SG_MAGIC_CONST - 4.5 * z >= 0 || t >= math.ln(z) { + return x * beta + } + } + case alpha == 1: + // float64_exponential(1/beta) + return -math.ln(1 - float64(r)) * beta + case: + // ALGORITHM GS of Statistical Computing - Kennedy & Gentle + x: f64 + for { + u := float64(r) + b := (math.e + alpha) / math.e + p := b * u + if p <= 1 { + x = math.pow(p, 1/alpha) + } else { + x = -math.ln((b - p) / alpha) + } + u1 := float64(r) + if p > 1 { + if u1 <= math.pow(x, alpha-1) { + break + } + } else if u1 <= math.exp(-x) { + break + } + } + return x * beta + } +} +// Gamma Distribution (NOT THE GAMMA FUNCTION) +// +// Required: alpha > 0 and beta > 0 +// +// math.pow(x, alpha-1) * math.exp(-x / beta) +// pdf(x) = -------------------------------------------- +// math.gamma(alpha) * math.pow(beta, alpha) +// +// mean is alpha*beta, variance is math.pow(alpha*beta, 2) +float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_gamma(f64(alpha), f64(beta), r)) +} + + +// Beta Distribution +// +// Required: alpha > 0 and beta > 0 +// +// Return values range between 0 and 1 +float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + if alpha <= 0 || beta <= 0 { + panic(#procedure + ": alpha and beta must be > 0.0") + } + // Knuth Vol 2 Ed 3 pg 134 "the beta distribution" + y := float64_gamma(alpha, 1.0, r) + if y != 0 { + return y / (y + float64_gamma(beta, 1.0, r)) + } + return 0 +} +// Beta Distribution +// +// Required: alpha > 0 and beta > 0 +// +// Return values range between 0 and 1 +float32_beta :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_beta(f64(alpha), f64(beta), r)) +} + + +// Pareto distribution, `alpha` is the shape parameter. +// https://wikipedia.org/wiki/Pareto_distribution +float64_pareto :: proc(alpha: f64, r: ^Rand = nil) -> f64 { + return math.pow(1 - float64(r), -1.0 / alpha) +} +// Pareto distribution, `alpha` is the shape parameter. +// https://wikipedia.org/wiki/Pareto_distribution +float32_pareto :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_pareto(f64(alpha), r)) +} + + +// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. +float64_weibull :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + u := 1 - float64(r) + return alpha * math.pow(-math.ln(u), 1.0/beta) +} +// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. +float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_weibull(f64(alpha), f64(beta), r)) +} + + +// Circular Data (von Mises) Distribution +// `mean_angle` is the in mean angle between 0 and 2pi radians +// `kappa` is the concentration parameter which must be >= 0 +// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi +float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 { + // Fisher, N.I., "Statistical Analysis of Circular Data", Cambridge University Press, 1993. + + mu := mean_angle + if kappa <= 1e-6 { + return math.TAU * float64(r) + } + + s := 0.5 / kappa + t := s + math.sqrt(1 + s*s) + z: f64 + for { + u1 := float64(r) + z = math.cos(math.TAU * 0.5 * u1) + + d := z / (t + z) + u2 := float64(r) + if u2 < 1 - d*d || u2 <= (1-d)*math.exp(d) { + break + } + } + + q := 1.0 / t + f := (q + z) / (1 + q*z) + u3 := float64(r) + if u3 > 0.5 { + return math.mod(mu + math.acos(f), math.TAU) + } else { + return math.mod(mu - math.acos(f), math.TAU) + } +} +// Circular Data (von Mises) Distribution +// `mean_angle` is the in mean angle between 0 and 2pi radians +// `kappa` is the concentration parameter which must be >= 0 +// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi +float32_von_mises :: proc(mean_angle, kappa: f32, r: ^Rand = nil) -> f32 { + return f32(float64_von_mises(f64(mean_angle), f64(kappa), r)) +} + read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) { From 6c6de2a07d23079acd134ee3fe421784173d72fe Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 22:20:25 +0100 Subject: [PATCH 082/254] Move distributions to a separate file --- core/math/rand/distributions.odin | 251 ++++++++++++++++++++++++++++++ core/math/rand/rand.odin | 251 ------------------------------ 2 files changed, 251 insertions(+), 251 deletions(-) create mode 100644 core/math/rand/distributions.odin diff --git a/core/math/rand/distributions.odin b/core/math/rand/distributions.odin new file mode 100644 index 000000000..6bfff64d8 --- /dev/null +++ b/core/math/rand/distributions.odin @@ -0,0 +1,251 @@ +package rand + +import "core:math" + +float64_uniform :: float64_range +float32_uniform :: float32_range + +// Triangular Distribution +// See: http://wikipedia.org/wiki/Triangular_distribution +float64_trianglular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 { + if hi-lo == 0 { + return lo + } + lo, hi := lo, hi + u := float64(r) + c := f64(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) + if u > c { + u = 1-u + c = 1-c + lo, hi = hi, lo + } + return lo + (hi - lo) * math.sqrt(u * c) + +} +// Triangular Distribution +// See: http://wikipedia.org/wiki/Triangular_distribution +float32_trianglular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 { + + if hi-lo == 0 { + return lo + } + lo, hi := lo, hi + u := float32(r) + c := f32(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) + if u > c { + u = 1-u + c = 1-c + lo, hi = hi, lo + } + return lo + (hi - lo) * math.sqrt(u * c) +} + + +// Normal/Gaussian Distribution +float64_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { + return norm_float64(r) * stddev + mean +} +// Normal/Gaussian Distribution +float32_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { + return f32(float64_normal(f64(mean), f64(stddev), r)) +} + + +// Log Normal Distribution +float64_log_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { + return math.ln(float64_normal(mean, stddev, r)) +} +// Log Normal Distribution +float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { + return f32(float64_log_normal(f64(mean), f64(stddev), r)) +} + + +// Exponential Distribution +// `lambda` is 1.0/(desired mean). It should be non-zero. +// Return values range from +// 0 to positive infinity if lambda > 0 +// negative infinity to 0 if lambda <= 0 +float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 { + return - math.ln(1 - float64(r)) / lambda +} +// Exponential Distribution +// `lambda` is 1.0/(desired mean). It should be non-zero. +// Return values range from +// 0 to positive infinity if lambda > 0 +// negative infinity to 0 if lambda <= 0 +float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 { + return f32(float64_exponential(f64(lambda), r)) +} + + +// Gamma Distribution (NOT THE GAMMA FUNCTION) +// +// Required: alpha > 0 and beta > 0 +// +// math.pow(x, alpha-1) * math.exp(-x / beta) +// pdf(x) = -------------------------------------------- +// math.gamma(alpha) * math.pow(beta, alpha) +// +// mean is alpha*beta, variance is math.pow(alpha*beta, 2) +float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + if alpha <= 0 || beta <= 0 { + panic(#procedure + ": alpha and beta must be > 0.0") + } + + LOG4 :: 1.3862943611198906188344642429163531361510002687205105082413600189 + SG_MAGIC_CONST :: 2.5040773967762740733732583523868748412194809812852436493487 + + switch { + case alpha > 1: + // R.C.H. Cheng, "The generation of Gamma variables with non-integral shape parameters", Applied Statistics, (1977), 26, No. 1, p71-74 + + ainv := math.sqrt(2 * alpha - 1) + bbb := alpha - LOG4 + ccc := alpha + ainv + for { + u1 := float64(r) + if !(1e-7 < u1 && u1 < 0.9999999) { + continue + } + u2 := 1 - float64(r) + v := math.ln(u1 / (1 - u1)) / ainv + x := alpha * math.exp(v) + z := u1 * u1 * u2 + t := bbb + ccc*v - x + if t + SG_MAGIC_CONST - 4.5 * z >= 0 || t >= math.ln(z) { + return x * beta + } + } + case alpha == 1: + // float64_exponential(1/beta) + return -math.ln(1 - float64(r)) * beta + case: + // ALGORITHM GS of Statistical Computing - Kennedy & Gentle + x: f64 + for { + u := float64(r) + b := (math.e + alpha) / math.e + p := b * u + if p <= 1 { + x = math.pow(p, 1/alpha) + } else { + x = -math.ln((b - p) / alpha) + } + u1 := float64(r) + if p > 1 { + if u1 <= math.pow(x, alpha-1) { + break + } + } else if u1 <= math.exp(-x) { + break + } + } + return x * beta + } +} +// Gamma Distribution (NOT THE GAMMA FUNCTION) +// +// Required: alpha > 0 and beta > 0 +// +// math.pow(x, alpha-1) * math.exp(-x / beta) +// pdf(x) = -------------------------------------------- +// math.gamma(alpha) * math.pow(beta, alpha) +// +// mean is alpha*beta, variance is math.pow(alpha*beta, 2) +float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_gamma(f64(alpha), f64(beta), r)) +} + + +// Beta Distribution +// +// Required: alpha > 0 and beta > 0 +// +// Return values range between 0 and 1 +float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + if alpha <= 0 || beta <= 0 { + panic(#procedure + ": alpha and beta must be > 0.0") + } + // Knuth Vol 2 Ed 3 pg 134 "the beta distribution" + y := float64_gamma(alpha, 1.0, r) + if y != 0 { + return y / (y + float64_gamma(beta, 1.0, r)) + } + return 0 +} +// Beta Distribution +// +// Required: alpha > 0 and beta > 0 +// +// Return values range between 0 and 1 +float32_beta :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_beta(f64(alpha), f64(beta), r)) +} + + +// Pareto distribution, `alpha` is the shape parameter. +// https://wikipedia.org/wiki/Pareto_distribution +float64_pareto :: proc(alpha: f64, r: ^Rand = nil) -> f64 { + return math.pow(1 - float64(r), -1.0 / alpha) +} +// Pareto distribution, `alpha` is the shape parameter. +// https://wikipedia.org/wiki/Pareto_distribution +float32_pareto :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_pareto(f64(alpha), r)) +} + + +// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. +float64_weibull :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { + u := 1 - float64(r) + return alpha * math.pow(-math.ln(u), 1.0/beta) +} +// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. +float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { + return f32(float64_weibull(f64(alpha), f64(beta), r)) +} + + +// Circular Data (von Mises) Distribution +// `mean_angle` is the in mean angle between 0 and 2pi radians +// `kappa` is the concentration parameter which must be >= 0 +// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi +float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 { + // Fisher, N.I., "Statistical Analysis of Circular Data", Cambridge University Press, 1993. + + mu := mean_angle + if kappa <= 1e-6 { + return math.TAU * float64(r) + } + + s := 0.5 / kappa + t := s + math.sqrt(1 + s*s) + z: f64 + for { + u1 := float64(r) + z = math.cos(math.TAU * 0.5 * u1) + + d := z / (t + z) + u2 := float64(r) + if u2 < 1 - d*d || u2 <= (1-d)*math.exp(d) { + break + } + } + + q := 1.0 / t + f := (q + z) / (1 + q*z) + u3 := float64(r) + if u3 > 0.5 { + return math.mod(mu + math.acos(f), math.TAU) + } else { + return math.mod(mu - math.acos(f), math.TAU) + } +} +// Circular Data (von Mises) Distribution +// `mean_angle` is the in mean angle between 0 and 2pi radians +// `kappa` is the concentration parameter which must be >= 0 +// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi +float32_von_mises :: proc(mean_angle, kappa: f32, r: ^Rand = nil) -> f32 { + return f32(float64_von_mises(f64(mean_angle), f64(kappa), r)) +} diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 7f81dc6b2..80fd8de33 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -1,7 +1,6 @@ package rand import "core:intrinsics" -import "core:math" Rand :: struct { state: u64, @@ -127,256 +126,6 @@ float32 :: proc(r: ^Rand = nil) -> f32 { return f32(float64(r)) } float64_range :: proc(lo, hi: f64, r: ^Rand = nil) -> f64 { return (hi-lo)*float64(r) + lo } float32_range :: proc(lo, hi: f32, r: ^Rand = nil) -> f32 { return (hi-lo)*float32(r) + lo } -float64_uniform :: float64_range -float32_uniform :: float32_range - - -// Triangular Distribution -// See: http://wikipedia.org/wiki/Triangular_distribution -float64_trianglular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 { - if hi-lo == 0 { - return lo - } - lo, hi := lo, hi - u := float64(r) - c := f64(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) - if u > c { - u = 1-u - c = 1-c - lo, hi = hi, lo - } - return lo + (hi - lo) * math.sqrt(u * c) - -} -// Triangular Distribution -// See: http://wikipedia.org/wiki/Triangular_distribution -float32_trianglular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 { - - if hi-lo == 0 { - return lo - } - lo, hi := lo, hi - u := float32(r) - c := f32(0.5) if mode == nil else clamp((mode.?-lo) / (hi-lo), 0, 1) - if u > c { - u = 1-u - c = 1-c - lo, hi = hi, lo - } - return lo + (hi - lo) * math.sqrt(u * c) -} - - -// Normal/Gaussian Distribution -float64_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { - return norm_float64(r) * stddev + mean -} -// Normal/Gaussian Distribution -float32_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { - return f32(float64_normal(f64(mean), f64(stddev), r)) -} - - -// Log Normal Distribution -float64_log_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { - return math.ln(float64_normal(mean, stddev, r)) -} -// Log Normal Distribution -float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { - return f32(float64_log_normal(f64(mean), f64(stddev), r)) -} - - -// Exponential Distribution -// `lambda` is 1.0/(desired mean). It should be non-zero. -// Return values range from -// 0 to positive infinity if lambda > 0 -// negative infinity to 0 if lambda <= 0 -float64_exponential :: proc(lambda: f64, r: ^Rand = nil) -> f64 { - return - math.ln(1 - float64(r)) / lambda -} -// Exponential Distribution -// `lambda` is 1.0/(desired mean). It should be non-zero. -// Return values range from -// 0 to positive infinity if lambda > 0 -// negative infinity to 0 if lambda <= 0 -float32_exponential :: proc(lambda: f32, r: ^Rand = nil) -> f32 { - return f32(float64_exponential(f64(lambda), r)) -} - - -// Gamma Distribution (NOT THE GAMMA FUNCTION) -// -// Required: alpha > 0 and beta > 0 -// -// math.pow(x, alpha-1) * math.exp(-x / beta) -// pdf(x) = -------------------------------------------- -// math.gamma(alpha) * math.pow(beta, alpha) -// -// mean is alpha*beta, variance is math.pow(alpha*beta, 2) -float64_gamma :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { - if alpha <= 0 || beta <= 0 { - panic(#procedure + ": alpha and beta must be > 0.0") - } - - LOG4 :: 1.3862943611198906188344642429163531361510002687205105082413600189 - SG_MAGIC_CONST :: 2.5040773967762740733732583523868748412194809812852436493487 - - switch { - case alpha > 1: - // R.C.H. Cheng, "The generation of Gamma variables with non-integral shape parameters", Applied Statistics, (1977), 26, No. 1, p71-74 - - ainv := math.sqrt(2 * alpha - 1) - bbb := alpha - LOG4 - ccc := alpha + ainv - for { - u1 := float64(r) - if !(1e-7 < u1 && u1 < 0.9999999) { - continue - } - u2 := 1 - float64(r) - v := math.ln(u1 / (1 - u1)) / ainv - x := alpha * math.exp(v) - z := u1 * u1 * u2 - t := bbb + ccc*v - x - if t + SG_MAGIC_CONST - 4.5 * z >= 0 || t >= math.ln(z) { - return x * beta - } - } - case alpha == 1: - // float64_exponential(1/beta) - return -math.ln(1 - float64(r)) * beta - case: - // ALGORITHM GS of Statistical Computing - Kennedy & Gentle - x: f64 - for { - u := float64(r) - b := (math.e + alpha) / math.e - p := b * u - if p <= 1 { - x = math.pow(p, 1/alpha) - } else { - x = -math.ln((b - p) / alpha) - } - u1 := float64(r) - if p > 1 { - if u1 <= math.pow(x, alpha-1) { - break - } - } else if u1 <= math.exp(-x) { - break - } - } - return x * beta - } -} -// Gamma Distribution (NOT THE GAMMA FUNCTION) -// -// Required: alpha > 0 and beta > 0 -// -// math.pow(x, alpha-1) * math.exp(-x / beta) -// pdf(x) = -------------------------------------------- -// math.gamma(alpha) * math.pow(beta, alpha) -// -// mean is alpha*beta, variance is math.pow(alpha*beta, 2) -float32_gamma :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { - return f32(float64_gamma(f64(alpha), f64(beta), r)) -} - - -// Beta Distribution -// -// Required: alpha > 0 and beta > 0 -// -// Return values range between 0 and 1 -float64_beta :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { - if alpha <= 0 || beta <= 0 { - panic(#procedure + ": alpha and beta must be > 0.0") - } - // Knuth Vol 2 Ed 3 pg 134 "the beta distribution" - y := float64_gamma(alpha, 1.0, r) - if y != 0 { - return y / (y + float64_gamma(beta, 1.0, r)) - } - return 0 -} -// Beta Distribution -// -// Required: alpha > 0 and beta > 0 -// -// Return values range between 0 and 1 -float32_beta :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { - return f32(float64_beta(f64(alpha), f64(beta), r)) -} - - -// Pareto distribution, `alpha` is the shape parameter. -// https://wikipedia.org/wiki/Pareto_distribution -float64_pareto :: proc(alpha: f64, r: ^Rand = nil) -> f64 { - return math.pow(1 - float64(r), -1.0 / alpha) -} -// Pareto distribution, `alpha` is the shape parameter. -// https://wikipedia.org/wiki/Pareto_distribution -float32_pareto :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { - return f32(float64_pareto(f64(alpha), r)) -} - - -// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. -float64_weibull :: proc(alpha, beta: f64, r: ^Rand = nil) -> f64 { - u := 1 - float64(r) - return alpha * math.pow(-math.ln(u), 1.0/beta) -} -// Weibull distribution, `alpha` is the scale parameter, `beta` is the shape parameter. -float32_weibull :: proc(alpha, beta: f32, r: ^Rand = nil) -> f32 { - return f32(float64_weibull(f64(alpha), f64(beta), r)) -} - - -// Circular Data (von Mises) Distribution -// `mean_angle` is the in mean angle between 0 and 2pi radians -// `kappa` is the concentration parameter which must be >= 0 -// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi -float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 { - // Fisher, N.I., "Statistical Analysis of Circular Data", Cambridge University Press, 1993. - - mu := mean_angle - if kappa <= 1e-6 { - return math.TAU * float64(r) - } - - s := 0.5 / kappa - t := s + math.sqrt(1 + s*s) - z: f64 - for { - u1 := float64(r) - z = math.cos(math.TAU * 0.5 * u1) - - d := z / (t + z) - u2 := float64(r) - if u2 < 1 - d*d || u2 <= (1-d)*math.exp(d) { - break - } - } - - q := 1.0 / t - f := (q + z) / (1 + q*z) - u3 := float64(r) - if u3 > 0.5 { - return math.mod(mu + math.acos(f), math.TAU) - } else { - return math.mod(mu - math.acos(f), math.TAU) - } -} -// Circular Data (von Mises) Distribution -// `mean_angle` is the in mean angle between 0 and 2pi radians -// `kappa` is the concentration parameter which must be >= 0 -// When `kappa` is zero, the Distribution is a uniform Distribution over the range 0 to 2pi -float32_von_mises :: proc(mean_angle, kappa: f32, r: ^Rand = nil) -> f32 { - return f32(float64_von_mises(f64(mean_angle), f64(kappa), r)) -} - - read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) { pos := i8(0) From 50ddd8dd2649ddbe14d587d6fea4e724842cd92c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 22:45:05 +0100 Subject: [PATCH 083/254] Fix typo --- core/math/rand/distributions.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/math/rand/distributions.odin b/core/math/rand/distributions.odin index 6bfff64d8..05ab084a3 100644 --- a/core/math/rand/distributions.odin +++ b/core/math/rand/distributions.odin @@ -7,7 +7,7 @@ float32_uniform :: float32_range // Triangular Distribution // See: http://wikipedia.org/wiki/Triangular_distribution -float64_trianglular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 { +float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 { if hi-lo == 0 { return lo } @@ -24,7 +24,7 @@ float64_trianglular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f6 } // Triangular Distribution // See: http://wikipedia.org/wiki/Triangular_distribution -float32_trianglular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 { +float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 { if hi-lo == 0 { return lo From 500150b12a05a1c757414724d285cdea8f925bda Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 22:52:11 +0100 Subject: [PATCH 084/254] Correct log normal --- core/math/rand/distributions.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/math/rand/distributions.odin b/core/math/rand/distributions.odin index 05ab084a3..9e38f6517 100644 --- a/core/math/rand/distributions.odin +++ b/core/math/rand/distributions.odin @@ -53,7 +53,7 @@ float32_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { // Log Normal Distribution float64_log_normal :: proc(mean, stddev: f64, r: ^Rand = nil) -> f64 { - return math.ln(float64_normal(mean, stddev, r)) + return math.exp(float64_normal(mean, stddev, r)) } // Log Normal Distribution float32_log_normal :: proc(mean, stddev: f32, r: ^Rand = nil) -> f32 { From 5142955f00fb22cd3b7b69b2836b85867a5c69a2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 22:58:39 +0100 Subject: [PATCH 085/254] Add more distributions --- core/math/rand/distributions.odin | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/math/rand/distributions.odin b/core/math/rand/distributions.odin index 9e38f6517..a42893bf4 100644 --- a/core/math/rand/distributions.odin +++ b/core/math/rand/distributions.odin @@ -249,3 +249,44 @@ float64_von_mises :: proc(mean_angle, kappa: f64, r: ^Rand = nil) -> f64 { float32_von_mises :: proc(mean_angle, kappa: f32, r: ^Rand = nil) -> f32 { return f32(float64_von_mises(f64(mean_angle), f64(kappa), r)) } + + +// Cauchy-Lorentz Distribution +// `x_0` is the location, `gamma` is the scale where `gamma` > 0 +float64_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 { + assert(gamma > 0) + + // Calculated from the inverse CDF + + return math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0 +} +// Cauchy-Lorentz Distribution +// `x_0` is the location, `gamma` is the scale where `gamma` > 0 +float32_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 { + return f32(float64_cauchy_lorentz(f64(x_0), f64(gamma), r)) +} + + +// Log Cauchy-Lorentz Distribution +// `x_0` is the location, `gamma` is the scale where `gamma` > 0 +float64_log_cauchy_lorentz :: proc(x_0, gamma: f64, r: ^Rand = nil) -> f64 { + assert(gamma > 0) + return math.exp(math.tan(math.PI * (float64(r) - 0.5))*gamma + x_0) +} +// Log Cauchy-Lorentz Distribution +// `x_0` is the location, `gamma` is the scale where `gamma` > 0 +float32_log_cauchy_lorentz :: proc(x_0, gamma: f32, r: ^Rand = nil) -> f32 { + return f32(float64_log_cauchy_lorentz(f64(x_0), f64(gamma), r)) +} + + +// Laplace Distribution +// `b` is the scale where `b` > 0 +float64_laplace :: proc(mean, b: f64, r: ^Rand = nil) -> f64 { + assert(b > 0) + p := float64(r)-0.5 + return -math.sign(p)*math.ln(1 - 2*abs(p))*b + mean +} +float32_laplace :: proc(mean, b: f32, r: ^Rand = nil) -> f32 { + return f32(float64_laplace(f64(mean), f64(b), r)) +} \ No newline at end of file From fdcf08410cc7b43bfe69b7ded7d030d95d24284f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 23:03:01 +0100 Subject: [PATCH 086/254] Add Gompertz Distribution --- core/math/rand/distributions.odin | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/core/math/rand/distributions.odin b/core/math/rand/distributions.odin index a42893bf4..ada89afad 100644 --- a/core/math/rand/distributions.odin +++ b/core/math/rand/distributions.odin @@ -25,7 +25,6 @@ float64_triangular :: proc(lo, hi: f64, mode: Maybe(f64), r: ^Rand = nil) -> f64 // Triangular Distribution // See: http://wikipedia.org/wiki/Triangular_distribution float32_triangular :: proc(lo, hi: f32, mode: Maybe(f32), r: ^Rand = nil) -> f32 { - if hi-lo == 0 { return lo } @@ -287,6 +286,27 @@ float64_laplace :: proc(mean, b: f64, r: ^Rand = nil) -> f64 { p := float64(r)-0.5 return -math.sign(p)*math.ln(1 - 2*abs(p))*b + mean } +// Laplace Distribution +// `b` is the scale where `b` > 0 float32_laplace :: proc(mean, b: f32, r: ^Rand = nil) -> f32 { return f32(float64_laplace(f64(mean), f64(b), r)) -} \ No newline at end of file +} + + +// Gompertz Distribution +// `eta` is the shape, `b` is the scale +// Both `eta` and `b` must be > 0 +float64_gompertz :: proc(eta, b: f64, r: ^Rand = nil) -> f64 { + if eta <= 0 || b <= 0 { + panic(#procedure + ": eta and b must be > 0.0") + } + + p := float64(r) + return math.ln(1 - math.ln(1 - p)/eta)/b +} +// Gompertz Distribution +// `eta` is the shape, `b` is the scale +// Both `eta` and `b` must be > 0 +float32_gompertz :: proc(eta, b: f32, r: ^Rand = nil) -> f32 { + return f32(float64_gompertz(f64(eta), f64(b), r)) +} From 2a58bceb5674c9af75dd217a7891869b4316f2f9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 23:43:20 +0100 Subject: [PATCH 087/254] Add `rand.init_as_system` to allow for system-level based random number generation --- core/math/rand/rand.odin | 17 +++++++++++++++++ core/math/rand/system_linux.odin | 27 +++++++++++++++++++++++++++ core/math/rand/system_windows.odin | 12 ++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 core/math/rand/system_linux.odin create mode 100644 core/math/rand/system_windows.odin diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 80fd8de33..f7dfcb3b8 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -5,6 +5,7 @@ import "core:intrinsics" Rand :: struct { state: u64, inc: u64, + is_system: bool, } @@ -29,6 +30,16 @@ init :: proc(r: ^Rand, seed: u64) { _random(r) } +init_as_system :: proc(r: ^Rand) { + if !#defined(_system_random) { + panic(#procedure + " is not supported on this platform yet") + } + r.state = 0 + r.inc = 0 + r.is_system = true +} + +@(private) _random :: proc(r: ^Rand) -> u32 { r := r if r == nil { @@ -36,6 +47,12 @@ _random :: proc(r: ^Rand) -> u32 { // enforce the global random state if necessary with `nil` r = &global_rand } + when #defined(_system_random) { + if r.is_system { + return _system_random() + } + } + old_state := r.state r.state = old_state * 6364136223846793005 + (r.inc|1) xor_shifted := u32(((old_state>>18) ~ old_state) >> 27) diff --git a/core/math/rand/system_linux.odin b/core/math/rand/system_linux.odin new file mode 100644 index 000000000..bfdc8872b --- /dev/null +++ b/core/math/rand/system_linux.odin @@ -0,0 +1,27 @@ +package rand + +import "core:sys/unix" + +_system_random :: proc() -> u32 { + for { + value: u32 + ret := unix.sys_getrandom(([^]u8)(&value), 4, 0) + if ret < 0 { + switch ret { + case -4: // EINTR + // Call interupted by a signal handler, just retry the request. + continue + case -38: // ENOSYS + // The kernel is apparently prehistoric (< 3.17 circa 2014) + // and does not support getrandom. + panic("getrandom not available in kernel") + case: + // All other failures are things that should NEVER happen + // unless the kernel interface changes (ie: the Linux + // developers break userland). + panic("getrandom failed") + } + } + return value + } +} \ No newline at end of file diff --git a/core/math/rand/system_windows.odin b/core/math/rand/system_windows.odin new file mode 100644 index 000000000..ee9cd0294 --- /dev/null +++ b/core/math/rand/system_windows.odin @@ -0,0 +1,12 @@ +package rand + +import win32 "core:sys/windows" + +_system_random :: proc() -> u32 { + value: u32 + status := win32.BCryptGenRandom(nil, ([^]u8)(&value), 4, win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG) + if status < 0 { + panic("BCryptGenRandom failed") + } + return value +} \ No newline at end of file From 4eba2bb8d9f4f4ec246d268ee382788062cfff16 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 23:46:32 +0100 Subject: [PATCH 088/254] Add `_system_random` for Darwin --- core/math/rand/system_darwin.odin | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 core/math/rand/system_darwin.odin diff --git a/core/math/rand/system_darwin.odin b/core/math/rand/system_darwin.odin new file mode 100644 index 000000000..f51e4473e --- /dev/null +++ b/core/math/rand/system_darwin.odin @@ -0,0 +1,21 @@ +package rand + +import "core:sys/darwin" + +_system_random :: proc() -> u32 { + for { + value: u32 + ret := darwin.syscall_getentropy(([^]u8)(&value), 4) + if ret < 0 { + switch ret { + case -4: // EINTR + continue + case -78: // ENOSYS + panic("getentropy not available in kernel") + case: + panic("getentropy failed") + } + } + return value + } +} \ No newline at end of file From 5451c9672d16f7a1db26d6c47e9e44b970f1e2c8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 23:48:11 +0100 Subject: [PATCH 089/254] Fix `dynamic_pool_destroy` --- core/mem/allocators.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 118a7f7e6..235391bcd 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -662,6 +662,7 @@ dynamic_pool_destroy :: proc(using pool: ^Dynamic_Pool) { dynamic_pool_free_all(pool) delete(unused_blocks) delete(used_blocks) + delete(out_band_allocations) zero(pool, size_of(pool^)) } From e10105a780d98c31faf4f2bce60a2a9dfabec7ab Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 May 2022 23:50:51 +0100 Subject: [PATCH 090/254] Correct logic for tracking allocator proc for freeing a nil pointer --- core/mem/allocators.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 235391bcd..d006e4574 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -858,7 +858,7 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, result: []byte err: Allocator_Error - if mode == .Free && old_memory not_in data.allocation_map { + if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map { append(&data.bad_free_array, Tracking_Allocator_Bad_Free_Entry{ memory = old_memory, location = loc, From 33895b6d927c70167f3bfa64c6cc1c15c4e428c5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 16 May 2022 01:43:43 +0100 Subject: [PATCH 091/254] Convert all uses of `*_from_slice` to `*_from_bytes` where appropriate --- core/bytes/bytes.odin | 3 ++- core/fmt/fmt.odin | 6 +++--- core/log/file_console_logger.odin | 2 +- core/os/os2/file.odin | 21 +++++++++++-------- core/strings/builder.odin | 5 +++-- core/sys/darwin/xnu_system_call_wrappers.odin | 2 +- core/sys/unix/syscalls_linux.odin | 2 +- core/sys/windows/bcrypt.odin | 2 +- core/text/i18n/gettext.odin | 6 +++--- core/text/i18n/qt_linguist.odin | 6 +++--- vendor/microui/microui.odin | 2 +- 11 files changed, 31 insertions(+), 26 deletions(-) diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 66fd20829..f1737f3c5 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -16,7 +16,8 @@ clone_safe :: proc(s: []byte, allocator := context.allocator, loc := #caller_loc return c[:len(s)], nil } -ptr_from_slice :: proc(str: []byte) -> ^byte { +ptr_from_slice :: ptr_from_bytes +ptr_from_bytes :: proc(str: []byte) -> ^byte { d := transmute(mem.Raw_String)str return d.data } diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index d006d0ef8..4f78cfcce 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -119,17 +119,17 @@ tprintf :: proc(fmt: string, args: ..any) -> string { // bprint procedures return a string using a buffer from an array bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { - sb := strings.builder_from_slice(buf[0:len(buf)]) + sb := strings.builder_from_bytes(buf[0:len(buf)]) return sbprint(buf=&sb, args=args, sep=sep) } // bprintln procedures return a string using a buffer from an array bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { - sb := strings.builder_from_slice(buf[0:len(buf)]) + sb := strings.builder_from_bytes(buf[0:len(buf)]) return sbprintln(buf=&sb, args=args, sep=sep) } // bprintf procedures return a string using a buffer from an array bprintf :: proc(buf: []byte, fmt: string, args: ..any) -> string { - sb := strings.builder_from_slice(buf[0:len(buf)]) + sb := strings.builder_from_bytes(buf[0:len(buf)]) return sbprintf(&sb, fmt, ..args) } diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index cc019617f..7f0d3b07a 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -67,7 +67,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string h = data.file_handle } backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. - buf := strings.builder_from_slice(backing[:]) + buf := strings.builder_from_bytes(backing[:]) do_level_header(options, level, &buf) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 4b271b9ea..eb6d9e366 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -34,17 +34,20 @@ File_Flag :: enum { Trunc, Sparse, Close_On_Exec, + + Unbuffered_IO, } -O_RDONLY :: File_Flags{.Read} -O_WRONLY :: File_Flags{.Write} -O_RDWR :: File_Flags{.Read, .Write} -O_APPEND :: File_Flags{.Append} -O_CREATE :: File_Flags{.Create} -O_EXCL :: File_Flags{.Excl} -O_SYNC :: File_Flags{.Sync} -O_TRUNC :: File_Flags{.Trunc} -O_SPARSE :: File_Flags{.Sparse} +O_RDONLY :: File_Flags{.Read} +O_WRONLY :: File_Flags{.Write} +O_RDWR :: File_Flags{.Read, .Write} +O_APPEND :: File_Flags{.Append} +O_CREATE :: File_Flags{.Create} +O_EXCL :: File_Flags{.Excl} +O_SYNC :: File_Flags{.Sync} +O_TRUNC :: File_Flags{.Trunc} +O_SPARSE :: File_Flags{.Sparse} +O_CLOEXEC :: File_Flags{.Close_On_Exec} diff --git a/core/strings/builder.odin b/core/strings/builder.odin index d6065cf70..d51e21827 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -124,11 +124,11 @@ reset_builder :: proc(b: ^Builder) { used in `fmt.bprint*` bytes: [8]byte // <-- gets filled - builder := strings.builder_from_slice(bytes[:]) + builder := strings.builder_from_bytes(bytes[:]) strings.write_byte(&builder, 'a') -> "a" strings.write_byte(&builder, 'b') -> "ab" */ -builder_from_slice :: proc(backing: []byte) -> Builder { +builder_from_bytes :: proc(backing: []byte) -> Builder { s := transmute(mem.Raw_Slice)backing d := mem.Raw_Dynamic_Array{ data = s.data, @@ -140,6 +140,7 @@ builder_from_slice :: proc(backing: []byte) -> Builder { buf = transmute([dynamic]byte)d, } } +builder_from_slice :: builder_from_bytes // cast the builder byte buffer to a string and return it to_string :: proc(b: Builder) -> string { diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index 4e4227f1f..685f75ffa 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -402,7 +402,7 @@ syscall_openat :: #force_inline proc(fd: int, path: cstring, oflag: u32, mode: u return cast(c.int)intrinsics.syscall(unix_offset_syscall(.openat), uintptr(fd), transmute(uintptr)path, uintptr(oflag), uintptr(mode)) } -syscall_getentropy :: #force_inline proc(buf: ^u8, buflen: u64) -> c.int { +syscall_getentropy :: #force_inline proc(buf: [^]u8, buflen: u64) -> c.int { return cast(c.int)intrinsics.syscall(unix_offset_syscall(.getentropy), uintptr(buf), uintptr(buflen)) } diff --git a/core/sys/unix/syscalls_linux.odin b/core/sys/unix/syscalls_linux.odin index f50ae825b..7300193df 100644 --- a/core/sys/unix/syscalls_linux.odin +++ b/core/sys/unix/syscalls_linux.odin @@ -1522,6 +1522,6 @@ sys_gettid :: proc "contextless" () -> int { return cast(int)intrinsics.syscall(SYS_gettid) } -sys_getrandom :: proc "contextless" (buf: ^byte, buflen: int, flags: uint) -> int { +sys_getrandom :: proc "contextless" (buf: [^]byte, buflen: int, flags: uint) -> int { return cast(int)intrinsics.syscall(SYS_getrandom, buf, cast(uintptr)(buflen), cast(uintptr)(flags)) } diff --git a/core/sys/windows/bcrypt.odin b/core/sys/windows/bcrypt.odin index ed28d5b7f..52eb4b1b6 100644 --- a/core/sys/windows/bcrypt.odin +++ b/core/sys/windows/bcrypt.odin @@ -7,5 +7,5 @@ BCRYPT_USE_SYSTEM_PREFERRED_RNG: DWORD : 0x00000002 @(default_calling_convention="stdcall") foreign bcrypt { - BCryptGenRandom :: proc(hAlgorithm: LPVOID, pBuffer: ^u8, cbBuffer: ULONG, dwFlags: ULONG) -> LONG --- + BCryptGenRandom :: proc(hAlgorithm: LPVOID, pBuffer: [^]u8, cbBuffer: ULONG, dwFlags: ULONG) -> LONG --- } diff --git a/core/text/i18n/gettext.odin b/core/text/i18n/gettext.odin index eed73855b..d99ec1c9b 100644 --- a/core/text/i18n/gettext.odin +++ b/core/text/i18n/gettext.odin @@ -18,7 +18,7 @@ import "core:os" import "core:strings" import "core:bytes" -parse_mo_from_slice :: proc(data: []u8, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { +parse_mo_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { context.allocator = allocator /* An MO file should have at least a 4-byte magic, 2 x 2 byte version info, @@ -126,10 +126,10 @@ parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, plural if !data_ok { return {}, .File_Error } - return parse_mo_from_slice(data, options, pluralizer, allocator) + return parse_mo_from_bytes(data, options, pluralizer, allocator) } -parse_mo :: proc { parse_mo_file, parse_mo_from_slice } +parse_mo :: proc { parse_mo_file, parse_mo_from_bytes } /* Helpers. diff --git a/core/text/i18n/qt_linguist.odin b/core/text/i18n/qt_linguist.odin index 15a88a42f..036a89eeb 100644 --- a/core/text/i18n/qt_linguist.odin +++ b/core/text/i18n/qt_linguist.odin @@ -27,7 +27,7 @@ TS_XML_Options := xml.Options{ expected_doctype = "TS", } -parse_qt_linguist_from_slice :: proc(data: []u8, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { +parse_qt_linguist_from_bytes :: proc(data: []byte, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) { context.allocator = allocator ts, xml_err := xml.parse(data, TS_XML_Options) @@ -150,7 +150,7 @@ parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTION if !data_ok { return {}, .File_Error } - return parse_qt_linguist_from_slice(data, options, pluralizer, allocator) + return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator) } -parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_slice } \ No newline at end of file +parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_bytes } \ No newline at end of file diff --git a/vendor/microui/microui.odin b/vendor/microui/microui.odin index 947f59f40..09a6b8430 100644 --- a/vendor/microui/microui.odin +++ b/vendor/microui/microui.odin @@ -309,7 +309,7 @@ init :: proc(ctx: ^Context) { ctx.draw_frame = default_draw_frame ctx._style = default_style ctx.style = &ctx._style - ctx.text_input = strings.builder_from_slice(ctx._text_store[:]) + ctx.text_input = strings.builder_from_bytes(ctx._text_store[:]) } begin :: proc(ctx: ^Context) { From d1499f3f78e1f65e164fcf68ade937aa46f8943b Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 16 May 2022 13:57:12 -0400 Subject: [PATCH 092/254] make -vet happy --- core/os/os2/file_linux.odin | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 8698ee54d..0f2e810f4 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -370,6 +370,9 @@ _is_file :: proc(name: string) -> bool { } s: _Stat res := unix.sys_stat(name_cstr, &s) + if res < 0 { + return false + } return S_ISREG(s.mode) } @@ -389,6 +392,9 @@ _is_dir :: proc(name: string) -> bool { } s: _Stat res := unix.sys_stat(name_cstr, &s) + if res < 0 { + return false + } return S_ISDIR(s.mode) } From 43432f92ec0780881284398dc151b73450ef6743 Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 16 May 2022 15:21:36 -0400 Subject: [PATCH 093/254] fix git dummy move --- core/sys/windows/kernel32.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index b44aa0305..98b93ffb9 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -58,7 +58,6 @@ foreign kernel32 { LeaveCriticalSection :: proc(CriticalSection: ^CRITICAL_SECTION) --- DeleteCriticalSection :: proc(CriticalSection: ^CRITICAL_SECTION) --- - PathFileExistsW :: proc(lpPathName: LPCWSTR) -> BOOL --- RemoveDirectoryW :: proc(lpPathName: LPCWSTR) -> BOOL --- SetFileAttributesW :: proc(lpFileName: LPCWSTR, dwFileAttributes: DWORD) -> BOOL --- SetLastError :: proc(dwErrCode: DWORD) --- @@ -795,4 +794,4 @@ Control_Event :: enum DWORD { close = 2, logoff = 5, shutdown = 6, -} \ No newline at end of file +} From 5a6836ab99c91250dadb44617f4995c1598537fe Mon Sep 17 00:00:00 2001 From: jason Date: Mon, 16 May 2022 15:28:56 -0400 Subject: [PATCH 094/254] match user.odin and env.odin to master --- core/os/os2/env.odin | 15 ++++++++++++--- core/os/os2/user.odin | 40 +++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index f1a3e40c7..f25290a59 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -1,11 +1,20 @@ package os2 -// get_env gets the value of the environment variable named by the key +// get_env retrieves the value of the environment variable named by the key +// It returns the value, which will be empty if the variable is not present +// To distinguish between an empty value and an unset value, use lookup_env +// NOTE: the value will be allocated with the supplied allocator +get_env :: proc(key: string, allocator := context.allocator) -> string { + value, _ := lookup_env(key, allocator) + return value +} + +// lookup_env gets the value of the environment variable named by the key // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator -get_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { - return _get_env(key, allocator) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return _lookup_env(key, allocator) } // set_env sets the value of the environment variable named by the key diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index 14c0ce961..1fb653b85 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -4,58 +4,56 @@ import "core:strings" import "core:runtime" user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - found: bool #partial switch ODIN_OS { case .Windows: - dir, found = get_env("LocalAppData") - if found { + dir = get_env("LocalAppData") + if dir != "" { dir = strings.clone_safe(dir, allocator) or_return } case .Darwin: - dir, found = get_env("HOME") - if found { + dir = get_env("HOME") + if dir != "" { dir = strings.concatenate_safe({dir, "/Library/Caches"}, allocator) or_return } case: // All other UNIX systems - dir, found = get_env("XDG_CACHE_HOME") - if found { - dir, found = get_env("HOME") - if !found { + dir = get_env("XDG_CACHE_HOME") + if dir == "" { + dir = get_env("HOME") + if dir == "" { return } dir = strings.concatenate_safe({dir, "/.cache"}, allocator) or_return } } - if !found || dir == "" { + if dir == "" { err = .Invalid_Path } return } user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - found: bool #partial switch ODIN_OS { case .Windows: - dir, found = get_env("AppData") - if found { + dir = get_env("AppData") + if dir != "" { dir = strings.clone_safe(dir, allocator) or_return } case .Darwin: - dir, found = get_env("HOME") - if found { + dir = get_env("HOME") + if dir != "" { dir = strings.concatenate_safe({dir, "/Library/Application Support"}, allocator) or_return } case: // All other UNIX systems - dir, found = get_env("XDG_CACHE_HOME") - if !found { - dir, found = get_env("HOME") - if !found { + dir = get_env("XDG_CACHE_HOME") + if dir == "" { + dir = get_env("HOME") + if dir == "" { return } dir = strings.concatenate_safe({dir, "/.config"}, allocator) or_return } } - if !found || dir == "" { + if dir == "" { err = .Invalid_Path } return @@ -67,7 +65,7 @@ user_home_dir :: proc() -> (dir: string, err: Error) { case .Windows: env = "USERPROFILE" } - if v, found := get_env(env); found { + if v := get_env(env); v != "" { return v, nil } return "", .Invalid_Path From a86574da8401a050253b38baf4cffc89f9959860 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 17 May 2022 22:24:18 +0100 Subject: [PATCH 095/254] Use `RtlWaitOnAddress` to allow for a `i64` sized duration rather than `u32` --- core/sync/futex_windows.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/sync/futex_windows.odin b/core/sync/futex_windows.odin index 1c9d8b845..ce662ba9e 100644 --- a/core/sync/futex_windows.odin +++ b/core/sync/futex_windows.odin @@ -5,28 +5,28 @@ package sync import "core:time" foreign import Synchronization "system:Synchronization.lib" - @(default_calling_convention="stdcall") foreign Synchronization { - WaitOnAddress :: proc(Address: rawptr, CompareAddress: rawptr, AddressSize: uint, Timeout: u32) -> b32 --- WakeByAddressSingle :: proc(Address: rawptr) --- WakeByAddressAll :: proc(Address: rawptr) --- } - +foreign import Ntdll "system:Ntdll.lib" +@(default_calling_convention="stdcall") +foreign Ntdll { + RtlWaitOnAddress :: proc(Address: rawptr, CompareAddress: rawptr, AddressSize: uint, Timeout: ^i64) -> i32 --- +} _futex_wait :: proc(f: ^Futex, expect: u32) -> bool { expect := expect - return bool(WaitOnAddress(f, &expect, size_of(expect), ~u32(0))) + return 0 == RtlWaitOnAddress(f, &expect, size_of(expect), nil) } _futex_wait_with_timeout :: proc(f: ^Futex, expect: u32, duration: time.Duration) -> bool { expect := expect - timeout := u32(0) - if duration > 0 { - timeout = u32(duration/1e6) - } - return bool(WaitOnAddress(f, &expect, size_of(expect), timeout)) + // NOTE(bill): for some bizarre reason, this has be a negative number + timeout := -i64(duration / 100) + return 0 == RtlWaitOnAddress(f, &expect, size_of(expect), &timeout) } _futex_signal :: proc(f: ^Futex) { From 0cc67ff5e348aa2811b0dd3fa81805b9ce93ea4f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 17 May 2022 22:42:37 +0100 Subject: [PATCH 096/254] Add a return value to `mem.zero_item` and `mem.zero_slice` which is the same as the input --- core/mem/mem.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/mem/mem.odin b/core/mem/mem.odin index b33f8ba13..46fed4289 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -25,11 +25,13 @@ zero_explicit :: proc "contextless" (data: rawptr, len: int) -> rawptr { intrinsics.atomic_thread_fence(.Seq_Cst) // Prevent reordering return data } -zero_item :: proc "contextless" (item: $P/^$T) { +zero_item :: proc "contextless" (item: $P/^$T) -> P { intrinsics.mem_zero(item, size_of(T)) + return item } -zero_slice :: proc "contextless" (data: $T/[]$E) { +zero_slice :: proc "contextless" (data: $T/[]$E) -> T { zero(raw_data(data), size_of(E)*len(data)) + return data } From 846930a07fc83873fbbcdde33e48600a6d9a2a8f Mon Sep 17 00:00:00 2001 From: Tobias Mollstam Date: Wed, 18 May 2022 07:01:19 +0200 Subject: [PATCH 097/254] emit optnone and noinline for all procs when opt set to minimal --- src/llvm_backend_proc.cpp | 49 +++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a0e9a5da5..a7f9eb013 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -139,35 +139,40 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) lb_add_attribute_to_proc(m, p->value, "noredzone"); } - switch (p->inlining) { - case ProcInlining_inline: - lb_add_attribute_to_proc(m, p->value, "alwaysinline"); - break; - case ProcInlining_no_inline: + if (build_context.optimization_level == 0 && build_context.ODIN_DEBUG) { lb_add_attribute_to_proc(m, p->value, "noinline"); - break; + lb_add_attribute_to_proc(m, p->value, "optnone"); + } else { + switch (p->inlining) { + case ProcInlining_inline: + lb_add_attribute_to_proc(m, p->value, "alwaysinline"); + break; + case ProcInlining_no_inline: + lb_add_attribute_to_proc(m, p->value, "noinline"); + break; + } + + switch (entity->Procedure.optimization_mode) { + case ProcedureOptimizationMode_None: + lb_add_attribute_to_proc(m, p->value, "optnone"); + break; + case ProcedureOptimizationMode_Minimal: + lb_add_attribute_to_proc(m, p->value, "optnone"); + break; + case ProcedureOptimizationMode_Size: + lb_add_attribute_to_proc(m, p->value, "optsize"); + break; + case ProcedureOptimizationMode_Speed: + // TODO(bill): handle this correctly + lb_add_attribute_to_proc(m, p->value, "optsize"); + break; + } } if (entity->flags & EntityFlag_Cold) { lb_add_attribute_to_proc(m, p->value, "cold"); } - switch (entity->Procedure.optimization_mode) { - case ProcedureOptimizationMode_None: - lb_add_attribute_to_proc(m, p->value, "optnone"); - break; - case ProcedureOptimizationMode_Minimal: - lb_add_attribute_to_proc(m, p->value, "optnone"); - break; - case ProcedureOptimizationMode_Size: - lb_add_attribute_to_proc(m, p->value, "optsize"); - break; - case ProcedureOptimizationMode_Speed: - // TODO(bill): handle this correctly - lb_add_attribute_to_proc(m, p->value, "optsize"); - break; - } - lbValue proc_value = {p->value, p->type}; lb_add_entity(m, entity, proc_value); lb_add_member(m, p->name, proc_value); From 57167be2a613b4ef01c8fbe6d292d7f38085e78e Mon Sep 17 00:00:00 2001 From: Tetralux Date: Tue, 17 May 2022 03:11:55 +0000 Subject: [PATCH 098/254] [os] Linux: os.unset_env() --- core/os/os_linux.odin | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index e4ce37567..de3a22187 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -794,6 +794,15 @@ set_env :: proc(key, value: string) -> Errno { return ERROR_NONE } +unset_env :: proc(key: string) -> Errno { + s := strings.clone_to_cstring(key, context.temp_allocator) + res := _unix_putenv(s) + if res < 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + get_current_directory :: proc() -> string { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. From 542e45de269e7dff1ce506db75d80d21df77a975 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 18 May 2022 12:30:26 +0100 Subject: [PATCH 099/254] Increase minimum macOS version to 10.12.0 --- src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 561fa0fca..13c8bd74d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -483,9 +483,9 @@ i32 linker_stage(lbGenerator *gen) { // NOTE: If you change this (although this minimum is as low as you can go with Odin working) // make sure to also change the 'mtriple' param passed to 'opt' if (build_context.metrics.arch == TargetArch_arm64) { - link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0 "); + link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=12.0.0 "); } else { - link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.8.0 "); + link_settings = gb_string_appendc(link_settings, " -mmacosx-version-min=10.12.0 "); } // This points the linker to where the entry point is link_settings = gb_string_appendc(link_settings, " -e _main "); From 223897d224dbf574854e68cf3933c5a9642ee8f1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 18 May 2022 12:30:44 +0100 Subject: [PATCH 100/254] Fix typo --- core/sync/futex_darwin.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index 88e354827..a106faa9c 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -8,7 +8,7 @@ import "core:time" foreign import System "System.framework" foreign System { - __ulock_wait :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_ms: u32) -> c.int --- + __ulock_wait :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_us: u32) -> c.int --- __ulock_wait2 :: proc "c" (operation: u32, addr: rawptr, value: u64, timeout_ns: u64, value2: u64) -> c.int --- __ulock_wake :: proc "c" (operation: u32, addr: rawptr, wake_value: u64) -> c.int --- } From 55556aea77dbcf0c693515ea6c544c54e68dfe24 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 18 May 2022 12:31:25 +0100 Subject: [PATCH 101/254] Add WebGL runtime into the js/runtime.mjs; Allow for multiple WebGL contexts --- vendor/wasm/WebGL/runtime.mjs | 1034 -------------------------------- vendor/wasm/WebGL/webgl.odin | 1 + vendor/wasm/js/runtime.mjs | 1057 ++++++++++++++++++++++++++++++++- vendor/wasm/loader/loader.mjs | 26 +- 4 files changed, 1066 insertions(+), 1052 deletions(-) delete mode 100644 vendor/wasm/WebGL/runtime.mjs diff --git a/vendor/wasm/WebGL/runtime.mjs b/vendor/wasm/WebGL/runtime.mjs deleted file mode 100644 index 4d34b9f4c..000000000 --- a/vendor/wasm/WebGL/runtime.mjs +++ /dev/null @@ -1,1034 +0,0 @@ -class WebGLInterface { - constructor(wasmMemoryInterface, canvasElement, contextSettings) { - this.wasmMemoryInterface = wasmMemoryInterface; - this.ctx = null; - this.ctx_version = 1; - this.counter = 1; - this.lastError = 0; - this.buffers = []; - this.mappedBuffers = {}; - this.programs = []; - this.framebuffers = []; - this.renderbuffers = []; - this.textures = []; - this.uniforms = []; - this.shaders = []; - this.vaos = []; - this.contexts = []; - this.currentContext = null; - this.offscreenCanvases = {}; - this.timerQueriesEXT = []; - this.queries = []; - this.samplers = []; - this.transformFeedbacks = []; - this.syncs = []; - this.programInfos = {}; - - if (contextSettings === undefined) { - contextSettings = {antialias: false}; - } - - this.ctx = canvasElement.getContext("webgl2", contextSettings) || canvasElement.getContext("webgl", contextSettings); - if (!this.ctx) { - return; - } - if (this.ctx.getParameter(0x1F02).indexOf("WebGL 2.0") !== -1) { - this.ctx_version = 2.0; - } else { - this.ctx_version = 1.0; - } - } - - get mem() { - return this.wasmMemoryInterface - } - - assertWebGL2() { - if (this.ctx_version < 2) { - throw new Error("WebGL2 procedure called in a canvas without a WebGL2 context"); - } - } - getNewId(table) { - for (var ret = this.counter++, i = table.length; i < ret; i++) { - table[i] = null; - } - return ret; - } - recordError(errorCode) { - this.lastError || (this.lastError = errorCode); - } - populateUniformTable(program) { - let p = this.programs[program]; - this.programInfos[program] = { - uniforms: {}, - maxUniformLength: 0, - maxAttributeLength: -1, - maxUniformBlockNameLength: -1, - }; - for (let ptable = this.programInfos[program], utable = ptable.uniforms, numUniforms = this.ctx.getProgramParameter(p, this.ctx.ACTIVE_UNIFORMS), i = 0; i < numUniforms; ++i) { - let u = this.ctx.getActiveUniform(p, i); - let name = u.name; - if (ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length + 1), name.indexOf("]", name.length - 1) !== -1) { - name = name.slice(0, name.lastIndexOf("[")); - } - let loc = this.ctx.getUniformLocation(p, name); - if (loc !== null) { - let id = this.getNewId(this.uniforms); - utable[name] = [u.size, id], this.uniforms[id] = loc; - for (let j = 1; j < u.size; ++j) { - let n = name + "[" + j + "]"; - let loc = this.ctx.getUniformLocation(p, n); - let id = this.getNewId(this.uniforms); - this.uniforms[id] = loc; - } - } - } - } - getSource(shader, strings_ptr, strings_length) { - const STRING_SIZE = 2*4; - let source = ""; - for (let i = 0; i < strings_length; i++) { - let ptr = this.mem.loadPtr(strings_ptr + i*STRING_SIZE); - let len = this.mem.loadPtr(strings_ptr + i*STRING_SIZE + 4); - let str = this.mem.loadString(ptr, len); - source += str; - } - return source; - } - - getWebGL1Interface() { - return { - DrawingBufferWidth: () => this.ctx.drawingBufferWidth, - DrawingBufferHeight: () => this.ctx.drawingBufferHeight, - - IsExtensionSupported: (name_ptr, name_len) => { - let name = this.mem.loadString(name_ptr, name_len); - let extensions = this.ctx.getSupportedExtensions(); - return extensions.indexOf(name) !== -1 - }, - - - GetError: () => { - let err = this.lastError; - this.recordError(0); - if (err) { - return err; - } - return this.ctx.getError(); - }, - - GetWebGLVersion: (major_ptr, minor_ptr) => { - let version = this.ctx.getParameter(0x1F02); - if (version.indexOf("WebGL 2.0") !== -1) { - this.mem.storeI32(major_ptr, 2); - this.mem.storeI32(minor_ptr, 0); - return; - } - - this.mem.storeI32(major_ptr, 1); - this.mem.storeI32(minor_ptr, 0); - }, - GetESVersion: (major_ptr, minor_ptr) => { - let version = this.ctx.getParameter(0x1F02); - if (version.indexOf("OpenGL ES 3.0") !== -1) { - this.mem.storeI32(major_ptr, 3); - this.mem.storeI32(minor_ptr, 0); - return; - } - - this.mem.storeI32(major_ptr, 2); - this.mem.storeI32(minor_ptr, 0); - }, - - - ActiveTexture: (x) => { - this.ctx.activeTexture(x); - }, - AttachShader: (program, shader) => { - this.ctx.attachShader(this.programs[program], this.shaders[shader]); - }, - BindAttribLocation: (program, index, name_ptr, name_len) => { - let name = this.mem.loadString(name_ptr, name_len); - this.ctx.bindAttribLocation(this.programs[program], index, name) - }, - BindBuffer: (target, buffer) => { - let bufferObj = buffer ? this.buffers[buffer] : null; - if (target == 35051) { - this.ctx.currentPixelPackBufferBinding = buffer; - } else { - if (target == 35052) { - this.ctx.currentPixelUnpackBufferBinding = buffer; - } - this.ctx.bindBuffer(target, bufferObj) - } - }, - BindFramebuffer: (target, buffer) => { - // TODO: BindFramebuffer - }, - BindTexture: (target, texture) => { - this.ctx.bindTexture(target, texture ? this.textures[texture] : null) - }, - BlendColor: (red, green, blue, alpha) => { - this.ctx.blendColor(red, green, blue, alpha); - }, - BlendEquation: (mode) => { - this.ctx.blendEquation(mode); - }, - BlendFunc: (sfactor, dfactor) => { - this.ctx.blendFunc(sfactor, dfactor); - }, - BlendFuncSeparate: (srcRGB, dstRGB, srcAlpha, dstAlpha) => { - this.ctx.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); - }, - - - BufferData: (target, size, data, usage) => { - if (data) { - this.ctx.bufferData(target, this.mem.loadBytes(data, size), usage); - } else { - this.ctx.bufferData(target, size, usage); - } - }, - BufferSubData: (target, offset, size, data) => { - if (data) { - this.ctx.bufferSubData(target, offset, this.mem.loadBytes(data, size)); - } else { - this.ctx.bufferSubData(target, offset, null); - } - }, - - - Clear: (x) => { - this.ctx.clear(x); - }, - ClearColor: (r, g, b, a) => { - this.ctx.clearColor(r, g, b, a); - }, - ClearDepth: (x) => { - this.ctx.clearDepth(x); - }, - ClearStencil: (x) => { - this.ctx.clearStencil(x); - }, - ColorMask: (r, g, b, a) => { - this.ctx.colorMask(!!r, !!g, !!b, !!a); - }, - CompileShader: (shader) => { - this.ctx.compileShader(this.shaders[shader]); - }, - - - CompressedTexImage2D: (target, level, internalformat, width, height, border, imageSize, data) => { - if (data) { - this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, this.mem.loadBytes(data, imageSize)); - } else { - this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, null); - } - }, - CompressedTexSubImage2D: (target, level, xoffset, yoffset, width, height, format, imageSize, data) => { - if (data) { - this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, this.mem.loadBytes(data, imageSize)); - } else { - this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, null); - } - }, - - CopyTexImage2D: (target, level, internalformat, x, y, width, height, border) => { - this.ctx.copyTexImage2D(target, level, internalformat, x, y, width, height, border); - }, - CopyTexSubImage2D: (target, level, xoffset, yoffset, x, y, width, height) => { - this.ctx.copyTexImage2D(target, level, xoffset, yoffset, x, y, width, height); - }, - - - CreateBuffer: () => { - let buffer = this.ctx.createBuffer(); - if (!buffer) { - this.recordError(1282); - return 0; - } - let id = this.getNewId(this.buffers); - buffer.name = id - this.buffers[id] = buffer; - return id; - }, - CreateFramebuffer: () => { - let buffer = this.ctx.createFramebuffer(); - let id = this.getNewId(this.framebuffers); - buffer.name = id - this.framebuffers[id] = buffer; - return id; - }, - CreateProgram: () => { - let program = this.ctx.createProgram(); - let id = this.getNewId(this.programs); - program.name = id; - this.programs[id] = program; - return id; - }, - CreateRenderbuffer: () => { - let buffer = this.ctx.createRenderbuffer(); - let id = this.getNewId(this.renderbuffers); - buffer.name = id; - this.renderbuffers[id] = buffer; - return id; - }, - CreateShader: (shaderType) => { - let shader = this.ctx.createShader(shaderType); - let id = this.getNewId(this.shaders); - shader.name = id; - this.shaders[id] = shader; - return id; - }, - CreateTexture: () => { - let texture = this.ctx.createTexture(); - if (!texture) { - this.recordError(1282) - return 0; - } - let id = this.getNewId(this.textures); - texture.name = id; - this.textures[id] = texture; - return id; - }, - - - CullFace: (mode) => { - this.ctx.cullFace(mode); - }, - - - DeleteBuffer: (id) => { - let obj = this.buffers[id]; - if (obj && id != 0) { - this.ctx.deleteBuffer(obj); - this.buffers[id] = null; - } - }, - DeleteFramebuffer: (id) => { - let obj = this.framebuffers[id]; - if (obj && id != 0) { - this.ctx.deleteFramebuffer(obj); - this.framebuffers[id] = null; - } - }, - DeleteProgram: (id) => { - let obj = this.programs[id]; - if (obj && id != 0) { - this.ctx.deleteProgram(obj); - this.programs[id] = null; - } - }, - DeleteRenderbuffer: (id) => { - let obj = this.renderbuffers[id]; - if (obj && id != 0) { - this.ctx.deleteRenderbuffer(obj); - this.renderbuffers[id] = null; - } - }, - DeleteShader: (id) => { - let obj = this.shaders[id]; - if (obj && id != 0) { - this.ctx.deleteShader(obj); - this.shaders[id] = null; - } - }, - DeleteTexture: (id) => { - let obj = this.textures[id]; - if (obj && id != 0) { - this.ctx.deleteTexture(obj); - this.textures[id] = null; - } - }, - - - DepthFunc: (func) => { - this.ctx.depthFunc(func); - }, - DepthMask: (flag) => { - this.ctx.depthMask(!!flag); - }, - DepthRange: (zNear, zFar) => { - this.ctx.depthRange(zNear, zFar); - }, - DetachShader: (program, shader) => { - this.ctx.detachShader(this.programs[program], this.shaders[shader]); - }, - Disable: (cap) => { - this.ctx.disable(cap); - }, - DisableVertexAttribArray: (index) => { - this.ctx.disableVertexAttribArray(index); - }, - DrawArrays: (mode, first, count) => { - this.ctx.drawArrays(mode, first, count); - }, - DrawElements: (mode, count, type, indices) => { - this.ctx.drawElements(mode, count, type, indices); - }, - - - Enable: (cap) => { - this.ctx.enable(cap); - }, - EnableVertexAttribArray: (index) => { - this.ctx.enableVertexAttribArray(index); - }, - Finish: () => { - this.ctx.finish(); - }, - Flush: () => { - this.ctx.flush(); - }, - FramebufferRenderBuffer: (target, attachment, renderbuffertarget, renderbuffer) => { - this.ctx.framebufferRenderBuffer(target, attachment, renderbuffertarget, this.renderbuffers[renderbuffer]); - }, - FramebufferTexture2D: (target, attachment, textarget, texture, level) => { - this.ctx.framebufferTexture2D(target, attachment, textarget, this.textures[texture], level); - }, - FrontFace: (mode) => { - this.ctx.frontFace(mode); - }, - - - GenerateMipmap: (target) => { - this.ctx.generateMipmap(target); - }, - - - GetAttribLocation: (program, name_ptr, name_len) => { - let name = this.mem.loadString(name_ptr, name_len); - return this.ctx.getAttribLocation(this.programs[program], name); - }, - - - - GetProgramParameter: (program, pname) => { - return this.ctx.getProgramParameter(this.programs[program], pname) - }, - GetProgramInfoLog: (program, buf_ptr, buf_len, length_ptr) => { - let log = this.ctx.getProgramInfoLog(this.programs[program]); - if (log === null) { - log = "(unknown error)"; - } - if (buf_len > 0 && buf_ptr) { - let n = Math.min(buf_len, log.length); - log = log.substring(0, n); - this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(log)) - - this.mem.storeInt(length_ptr, n); - } - }, - GetShaderInfoLog: (shader, buf_ptr, buf_len, length_ptr) => { - let log = this.ctx.getShaderInfoLog(this.shaders[shader]); - if (log === null) { - log = "(unknown error)"; - } - if (buf_len > 0 && buf_ptr) { - let n = Math.min(buf_len, log.length); - log = log.substring(0, n); - this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(log)) - - this.mem.storeInt(length_ptr, n); - } - }, - GetShaderiv: (shader, pname, p) => { - if (p) { - if (pname == 35716) { - let log = this.ctx.getShaderInfoLog(this.shaders[shader]); - if (log === null) { - log = "(unknown error)"; - } - this.mem.storeInt(p, log.length+1); - } else if (pname == 35720) { - let source = this.ctx.getShaderSource(this.shaders[shader]); - let sourceLength = (source === null || source.length == 0) ? 0 : source.length+1; - this.mem.storeInt(p, sourceLength); - } else { - let param = this.ctx.getShaderParameter(this.shaders[shader], pname); - this.mem.storeI32(p, param); - } - } else { - this.recordError(1281); - } - }, - - - GetUniformLocation: (program, name_ptr, name_len) => { - let name = this.mem.loadString(name_ptr, name_len); - let arrayOffset = 0; - if (name.indexOf("]", name.length - 1) !== -1) { - let ls = name.lastIndexOf("["), - arrayIndex = name.slice(ls + 1, -1); - if (arrayIndex.length > 0 && (arrayOffset = parseInt(arrayIndex)) < 0) { - return -1; - } - name = name.slice(0, ls) - } - var ptable = this.programInfos[program]; - if (!ptable) { - return -1; - } - var uniformInfo = ptable.uniforms[name]; - return (uniformInfo && arrayOffset < uniformInfo[0]) ? uniformInfo[1] + arrayOffset : -1 - }, - - - GetVertexAttribOffset: (index, pname) => { - return this.ctx.getVertexAttribOffset(index, pname); - }, - - - Hint: (target, mode) => { - this.ctx.hint(target, mode); - }, - - - IsBuffer: (buffer) => this.ctx.isBuffer(this.buffers[buffer]), - IsEnabled: (enabled) => this.ctx.isEnabled(this.enableds[enabled]), - IsFramebuffer: (framebuffer) => this.ctx.isFramebuffer(this.framebuffers[framebuffer]), - IsProgram: (program) => this.ctx.isProgram(this.programs[program]), - IsRenderbuffer: (renderbuffer) => this.ctx.isRenderbuffer(this.renderbuffers[renderbuffer]), - IsShader: (shader) => this.ctx.isShader(this.shaders[shader]), - IsTexture: (texture) => this.ctx.isTexture(this.textures[texture]), - - LineWidth: (width) => { - this.ctx.lineWidth(width); - }, - LinkProgram: (program) => { - this.ctx.linkProgram(this.programs[program]); - this.programInfos[program] = null; - this.populateUniformTable(program); - }, - PixelStorei: (pname, param) => { - this.ctx.pixelStorei(pname, param); - }, - PolygonOffset: (factor, units) => { - this.ctx.polygonOffset(factor, units); - }, - - - ReadnPixels: (x, y, width, height, format, type, bufSize, data) => { - this.ctx.readPixels(x, y, width, format, type, this.mem.loadBytes(data, bufSize)); - }, - RenderbufferStorage: (target, internalformat, width, height) => { - this.ctx.renderbufferStorage(target, internalformat, width, height); - }, - SampleCoverage: (value, invert) => { - this.ctx.sampleCoverage(value, !!invert); - }, - Scissor: (x, y, width, height) => { - this.ctx.scissor(x, y, width, height); - }, - ShaderSource: (shader, strings_ptr, strings_length) => { - let source = this.getSource(shader, strings_ptr, strings_length); - this.ctx.shaderSource(this.shaders[shader], source); - }, - - StencilFunc: (func, ref, mask) => { - this.ctx.stencilFunc(func, ref, mask); - }, - StencilFuncSeparate: (face, func, ref, mask) => { - this.ctx.stencilFuncSeparate(face, func, ref, mask); - }, - StencilMask: (mask) => { - this.ctx.stencilMask(mask); - }, - StencilMaskSeparate: (face, mask) => { - this.ctx.stencilMaskSeparate(face, mask); - }, - StencilOp: (fail, zfail, zpass) => { - this.ctx.stencilOp(fail, zfail, zpass); - }, - StencilOpSeparate: (face, fail, zfail, zpass) => { - this.ctx.stencilOpSeparate(face, fail, zfail, zpass); - }, - - - TexImage2D: (target, level, internalformat, width, height, border, format, type, size, data) => { - if (data) { - this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, this.mem.loadBytes(data, size)); - } else { - this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, null); - } - }, - TexParameterf: (target, pname, param) => { - this.ctx.texParameterf(target, pname, param); - }, - TexParameteri: (target, pname, param) => { - this.ctx.texParameteri(target, pname, param); - }, - TexSubImage2D: (target, level, xoffset, yoffset, width, height, format, type, size, data) => { - this.ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, this.mem.loadBytes(data, size)); - }, - - - Uniform1f: (location, v0) => { this.ctx.uniform1f(this.uniforms[location], v0); }, - Uniform2f: (location, v0, v1) => { this.ctx.uniform2f(this.uniforms[location], v0, v1); }, - Uniform3f: (location, v0, v1, v2) => { this.ctx.uniform3f(this.uniforms[location], v0, v1, v2); }, - Uniform4f: (location, v0, v1, v2, v3) => { this.ctx.uniform4f(this.uniforms[location], v0, v1, v2, v3); }, - - Uniform1i: (location, v0) => { this.ctx.uniform1i(this.uniforms[location], v0); }, - Uniform2i: (location, v0, v1) => { this.ctx.uniform2i(this.uniforms[location], v0, v1); }, - Uniform3i: (location, v0, v1, v2) => { this.ctx.uniform3i(this.uniforms[location], v0, v1, v2); }, - Uniform4i: (location, v0, v1, v2, v3) => { this.ctx.uniform4i(this.uniforms[location], v0, v1, v2, v3); }, - - UniformMatrix2fv: (location, addr) => { - let array = this.mem.loadF32Array(addr, 2*2); - this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); - }, - UniformMatrix3fv: (location, addr) => { - let array = this.mem.loadF32Array(addr, 3*3); - this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); - }, - UniformMatrix4fv: (location, addr) => { - let array = this.mem.loadF32Array(addr, 4*4); - this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); - }, - - UseProgram: (program) => { - if (program) this.ctx.useProgram(this.programs[program]); - }, - ValidateProgram: (program) => { - if (program) this.ctx.validateProgram(this.programs[program]); - }, - - - VertexAttrib1f: (index, x) => { - this.ctx.vertexAttrib1f(index, x); - }, - VertexAttrib2f: (index, x, y) => { - this.ctx.vertexAttrib2f(index, x, y); - }, - VertexAttrib3f: (index, x, y, z) => { - this.ctx.vertexAttrib3f(index, x, y, z); - }, - VertexAttrib4f: (index, x, y, z, w) => { - this.ctx.vertexAttrib4f(index, x, y, z, w); - }, - VertexAttribPointer: (index, size, type, normalized, stride, ptr) => { - this.ctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); - }, - - Viewport: (x, y, w, h) => { - this.ctx.viewport(x, y, w, h); - }, - }; - } - - getWebGL2Interface() { - return { - /* Buffer objects */ - CopyBufferSubData: (readTarget, writeTarget, readOffset, writeOffset, size) => { - this.assertWebGL2(); - this.ctx.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); - }, - GetBufferSubData: (target, srcByteOffset, dst_buffer_ptr, dst_buffer_len, dstOffset, length) => { - this.assertWebGL2(); - this.ctx.getBufferSubData(target, srcByteOffset, this.mem.loadBytes(dst_buffer_ptr, dst_buffer_len), dstOffset, length); - }, - - /* Framebuffer objects */ - BlitFramebuffer: (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => { - this.assertWebGL2(); - this.ctx.glitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); - }, - FramebufferTextureLayer: (target, attachment, texture, level, layer) => { - this.assertWebGL2(); - this.ctx.framebufferTextureLayer(target, attachment, this.textures[texture], level, layer); - }, - InvalidateFramebuffer: (target, attachments_ptr, attachments_len) => { - this.assertWebGL2(); - let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len); - this.ctx.invalidateFramebuffer(target, attachments); - }, - InvalidateSubFramebuffer: (target, attachments_ptr, attachments_len, x, y, width, height) => { - this.assertWebGL2(); - let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len); - this.ctx.invalidateSubFramebuffer(target, attachments, x, y, width, height); - }, - ReadBuffer: (src) => { - this.assertWebGL2(); - this.ctx.readBuffer(src); - }, - - /* Renderbuffer objects */ - RenderbufferStorageMultisample: (target, samples, internalformat, width, height) => { - this.assertWebGL2(); - this.ctx.renderbufferStorageMultisample(target, samples, internalformat, width, height); - }, - - /* Texture objects */ - - TexStorage3D: (target, levels, internalformat, width, height, depth) => { - this.assertWebGL2(); - this.ctx.texStorage3D(target, level, internalformat, width, heigh, depth); - }, - TexImage3D: (target, level, internalformat, width, height, depth, border, format, type, size, data) => { - this.assertWebGL2(); - if (data) { - this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, this.mem.loadBytes(data, size)); - } else { - this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, null); - } - }, - TexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, size, data) => { - this.assertWebGL2(); - this.ctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, this.mem.loadBytes(data, size)); - }, - CompressedTexImage3D: (target, level, internalformat, width, height, depth, border, imageSize, data) => { - this.assertWebGL2(); - if (data) { - this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, this.mem.loadBytes(data, imageSize)); - } else { - this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, null); - } - }, - CompressedTexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data) => { - this.assertWebGL2(); - if (data) { - this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, this.mem.loadBytes(data, imageSize)); - } else { - this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, null); - } - }, - - CopyTexSubImage3D: (target, level, xoffset, yoffset, zoffset, x, y, width, height) => { - this.assertWebGL2(); - this.ctx.copyTexImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height); - }, - - /* Programs and shaders */ - GetFragDataLocation: (program, name_ptr, name_len) => { - this.assertWebGL2(); - return this.ctx.getFragDataLocation(this.programs[program], this.mem.loadString(name_ptr, name_len)); - }, - - /* Uniforms */ - Uniform1ui: (location, v0) => { - this.assertWebGL2(); - this.ctx.uniform1ui(this.uniforms[location], v0); - }, - Uniform2ui: (location, v0, v1) => { - this.assertWebGL2(); - this.ctx.uniform2ui(this.uniforms[location], v0, v1); - }, - Uniform3ui: (location, v0, v1, v2) => { - this.assertWebGL2(); - this.ctx.uniform3ui(this.uniforms[location], v0, v1, v2); - }, - Uniform4ui: (location, v0, v1, v2, v3) => { - this.assertWebGL2(); - this.ctx.uniform4ui(this.uniforms[location], v0, v1, v2, v3); - }, - - UniformMatrix3x2fv: (location, addr) => { - this.assertWebGL2(); - let array = this.mem.loadF32Array(addr, 3*2); - this.ctx.uniformMatrix3x2fv(this.uniforms[location], false, array); - }, - UniformMatrix4x2fv: (location, addr) => { - this.assertWebGL2(); - let array = this.mem.loadF32Array(addr, 4*2); - this.ctx.uniformMatrix4x2fv(this.uniforms[location], false, array); - }, - UniformMatrix2x3fv: (location, addr) => { - this.assertWebGL2(); - let array = this.mem.loadF32Array(addr, 2*3); - this.ctx.uniformMatrix2x3fv(this.uniforms[location], false, array); - }, - UniformMatrix4x3fv: (location, addr) => { - this.assertWebGL2(); - let array = this.mem.loadF32Array(addr, 4*3); - this.ctx.uniformMatrix4x3fv(this.uniforms[location], false, array); - }, - UniformMatrix2x4fv: (location, addr) => { - this.assertWebGL2(); - let array = this.mem.loadF32Array(addr, 2*4); - this.ctx.uniformMatrix2x4fv(this.uniforms[location], false, array); - }, - UniformMatrix3x4fv: (location, addr) => { - this.assertWebGL2(); - let array = this.mem.loadF32Array(addr, 3*4); - this.ctx.uniformMatrix3x4fv(this.uniforms[location], false, array); - }, - - /* Vertex attribs */ - VertexAttribI4i: (index, x, y, z, w) => { - this.assertWebGL2(); - this.ctx.vertexAttribI4i(index, x, y, z, w); - }, - VertexAttribI4ui: (index, x, y, z, w) => { - this.assertWebGL2(); - this.ctx.vertexAttribI4ui(index, x, y, z, w); - }, - VertexAttribIPointer: (index, size, type, stride, offset) => { - this.assertWebGL2(); - this.ctx.vertexAttribIPointer(index, size, type, stride, offset); - }, - - /* Writing to the drawing buffer */ - VertexAttribDivisor: (index, divisor) => { - this.assertWebGL2(); - this.ctx.vertexAttribDivisor(index, divisor); - }, - DrawArraysInstanced: (mode, first, count, instanceCount) => { - this.assertWebGL2(); - this.ctx.drawArraysInstanced(mode, first, count, instanceCount); - }, - DrawElementsInstanced: (mode, count, type, offset, instanceCount) => { - this.assertWebGL2(); - this.ctx.drawElementsInstanced(mode, count, type, offset, instanceCount); - }, - DrawRangeElements: (mode, start, end, count, type, offset) => { - this.assertWebGL2(); - this.ctx.drawRangeElements(mode, start, end, count, type, offset); - }, - - /* Multiple Render Targets */ - DrawBuffers: (buffers_ptr, buffers_len) => { - this.assertWebGL2(); - let array = this.mem.loadU32Array(buffers_ptr, buffers_len); - this.ctx.drawBuffers(array); - }, - ClearBufferfv: (buffer, drawbuffer, values_ptr, values_len) => { - this.assertWebGL2(); - let array = this.mem.loadF32Array(values_ptr, values_len); - this.ctx.clearBufferfv(buffer, drawbuffer, array); - }, - ClearBufferiv: (buffer, drawbuffer, values_ptr, values_len) => { - this.assertWebGL2(); - let array = this.mem.loadI32Array(values_ptr, values_len); - this.ctx.clearBufferiv(buffer, drawbuffer, array); - }, - ClearBufferuiv: (buffer, drawbuffer, values_ptr, values_len) => { - this.assertWebGL2(); - let array = this.mem.loadU32Array(values_ptr, values_len); - this.ctx.clearBufferuiv(buffer, drawbuffer, array); - }, - ClearBufferfi: (buffer, drawbuffer, depth, stencil) => { - this.assertWebGL2(); - this.ctx.clearBufferfi(buffer, drawbuffer, depth, stencil); - }, - - /* Query Objects */ - CreateQuery: () => { - this.assertWebGL2(); - let query = this.ctx.createQuery(); - let id = this.getNewId(this.queries); - query.name = id; - this.queries[id] = query; - return id; - }, - DeleteQuery: (id) => { - this.assertWebGL2(); - let obj = this.querys[id]; - if (obj && id != 0) { - this.ctx.deleteQuery(obj); - this.querys[id] = null; - } - }, - IsQuery: (query) => { - this.assertWebGL2(); - return this.ctx.isQuery(this.queries[query]); - }, - BeginQuery: (target, query) => { - this.assertWebGL2(); - this.ctx.beginQuery(target, this.queries[query]) - }, - EndQuery: (target) => { - this.assertWebGL2(); - this.ctx.endQuery(target); - }, - GetQuery: (target, pname) => { - this.assertWebGL2(); - let query = this.ctx.getQuery(target, pname); - if (!query) { - return 0; - } - if (this.queries.indexOf(query) !== -1) { - return query.name; - } - let id = this.getNewId(this.queries); - query.name = id; - this.queries[id] = query; - return id; - }, - - /* Sampler Objects */ - CreateSampler: () => { - this.assertWebGL2(); - let sampler = this.ctx.createSampler(); - let id = this.getNewId(this.samplers); - sampler.name = id; - this.samplers[id] = sampler; - return id; - }, - DeleteSampler: (id) => { - this.assertWebGL2(); - let obj = this.samplers[id]; - if (obj && id != 0) { - this.ctx.deleteSampler(obj); - this.samplers[id] = null; - } - }, - IsSampler: (sampler) => { - this.assertWebGL2(); - return this.ctx.isSampler(this.samplers[sampler]); - }, - BindSampler: (unit, sampler) => { - this.assertWebGL2(); - this.ctx.bindSampler(unit, this.samplers[Sampler]); - }, - SamplerParameteri: (sampler, pname, param) => { - this.assertWebGL2(); - this.ctx.samplerParameteri(this.samplers[sampler], pname, param); - }, - SamplerParameterf: (sampler, pname, param) => { - this.assertWebGL2(); - this.ctx.samplerParameterf(this.samplers[sampler], pname, param); - }, - - /* Sync objects */ - FenceSync: (condition, flags) => { - this.assertWebGL2(); - let sync = this.ctx.fenceSync(condition, flags); - let id = this.getNewId(this.syncs); - sync.name = id; - this.syncs[id] = sync; - return id; - }, - IsSync: (sync) => { - this.assertWebGL2(); - return this.ctx.isSync(this.syncs[sync]); - }, - DeleteSync: (id) => { - this.assertWebGL2(); - let obj = this.syncs[id]; - if (obj && id != 0) { - this.ctx.deleteSampler(obj); - this.syncs[id] = null; - } - }, - ClientWaitSync: (sync, flags, timeout) => { - this.assertWebGL2(); - return this.ctx.clientWaitSync(this.syncs[sync], flags, timeout); - }, - WaitSync: (sync, flags, timeout) => { - this.assertWebGL2(); - this.ctx.waitSync(this.syncs[sync], flags, timeout) ; - }, - - - /* Transform Feedback */ - CreateTransformFeedback: () => { - this.assertWebGL2(); - let transformFeedback = this.ctx.createtransformFeedback(); - let id = this.getNewId(this.transformFeedbacks); - transformFeedback.name = id; - this.transformFeedbacks[id] = transformFeedback; - return id; - }, - DeleteTransformFeedback: (id) => { - this.assertWebGL2(); - let obj = this.transformFeedbacks[id]; - if (obj && id != 0) { - this.ctx.deleteTransformFeedback(obj); - this.transformFeedbacks[id] = null; - } - }, - IsTransformFeedback: (tf) => { - this.assertWebGL2(); - return this.ctx.isTransformFeedback(this.transformFeedbacks[tf]); - }, - BindTransformFeedback: (target, tf) => { - this.assertWebGL2(); - this.ctx.bindTransformFeedback(target, this.transformFeedbacks[tf]); - }, - BeginTransformFeedback: (primitiveMode) => { - this.assertWebGL2(); - this.ctx.beginTransformFeedback(primitiveMode); - }, - EndTransformFeedback: () => { - this.assertWebGL2(); - this.ctx.endTransformFeedback(); - }, - TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => { - this.assertWebGL2(); - let varyings = []; - for (let i = 0; i < varyings_len; i++) { - let ptr = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 0*4); - let len = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 1*4); - varyings.push(this.mem.loadString(ptr, len)); - } - this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode); - }, - PauseTransformFeedback: () => { - this.assertWebGL2(); - this.ctx.pauseTransformFeedback(); - }, - ResumeTransformFeedback: () => { - this.assertWebGL2(); - this.ctx.resumeTransformFeedback(); - }, - - - /* Uniform Buffer Objects and Transform Feedback Buffers */ - BindBufferBase: (target, index, buffer) => { - this.assertWebGL2(); - this.ctx.bindBufferBase(target, index, this.buffers[buffer]); - }, - BindBufferRange: (target, index, buffer, offset, size) => { - this.assertWebGL2(); - this.ctx.bindBufferRange(target, index, this.buffers[buffer], offset, size); - }, - GetUniformBlockIndex: (program, uniformBlockName_ptr, uniformBlockName_len) => { - this.assertWebGL2(); - return this.ctx.getUniformBlockIndex(this.programs[program], this.mem.loadString(uniformBlockName_ptr, uniformBlockName_len)); - }, - // any getActiveUniformBlockParameter(WebGLProgram program, GLuint uniformBlockIndex, GLenum pname); - GetActiveUniformBlockName: (program, uniformBlockIndex, buf_ptr, buf_len, length_ptr) => { - this.assertWebGL2(); - let name = this.ctx.getActiveUniformBlockName(this.programs[program], uniformBlockIndex); - - let n = Math.min(buf_len, name.length); - name = name.substring(0, n); - this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(name)) - this.mem.storeInt(length_ptr, n); - }, - UniformBlockBinding: (program, uniformBlockIndex, uniformBlockBinding) => { - this.assertWebGL2(); - this.ctx.uniformBlockBinding(this.programs[program], uniformBlockIndex, uniformBlockBinding); - }, - - /* Vertex Array Objects */ - CreateVertexArray: () => { - this.assertWebGL2(); - let vao = this.ctx.createVertexArray(); - let id = this.getNewId(this.vaos); - vao.name = id; - this.vaos[id] = vao; - return id; - }, - DeleteVertexArray: (id) => { - this.assertWebGL2(); - let obj = this.vaos[id]; - if (obj && id != 0) { - this.ctx.deleteVertexArray(obj); - this.vaos[id] = null; - } - }, - IsVertexArray: (vertexArray) => { - this.assertWebGL2(); - return this.ctx.isVertexArray(this.vaos[vertexArray]); - }, - BindVertexArray: (vertexArray) => { - this.assertWebGL2(); - this.ctx.bindVertexArray(this.vaos[vertexArray]); - }, - }; - } -}; - - -export {WebGLInterface}; \ No newline at end of file diff --git a/vendor/wasm/WebGL/webgl.odin b/vendor/wasm/WebGL/webgl.odin index c211fc526..272e323bc 100644 --- a/vendor/wasm/WebGL/webgl.odin +++ b/vendor/wasm/WebGL/webgl.odin @@ -15,6 +15,7 @@ Texture :: distinct u32 @(default_calling_convention="c") foreign webgl { + SetCurrentContextById :: proc(name: string) -> bool --- DrawingBufferWidth :: proc() -> i32 --- DrawingBufferHeight :: proc() -> i32 --- diff --git a/vendor/wasm/js/runtime.mjs b/vendor/wasm/js/runtime.mjs index 4306c0f32..4e7ab9dcc 100644 --- a/vendor/wasm/js/runtime.mjs +++ b/vendor/wasm/js/runtime.mjs @@ -371,7 +371,1060 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }, }, }; -} +}; -export {WasmMemoryInterface, odinSetupDefaultImports}; \ No newline at end of file +class WebGLInterface { + constructor(wasmMemoryInterface, canvasElement, contextSettings) { + this.wasmMemoryInterface = wasmMemoryInterface; + this.ctxElement = null; + this.ctx = null; + this.ctxVersion = 1.0; + this.counter = 1; + this.lastError = 0; + this.buffers = []; + this.mappedBuffers = {}; + this.programs = []; + this.framebuffers = []; + this.renderbuffers = []; + this.textures = []; + this.uniforms = []; + this.shaders = []; + this.vaos = []; + this.contexts = []; + this.currentContext = null; + this.offscreenCanvases = {}; + this.timerQueriesEXT = []; + this.queries = []; + this.samplers = []; + this.transformFeedbacks = []; + this.syncs = []; + this.programInfos = {}; + this.contextSettings = {antialias: false}; + + this.setCurrentContext(canvasElement, contextSettings); + } + + get mem() { + return this.wasmMemoryInterface + } + + setCurrentContext(element, contextSettings) { + if (!element) { + return false; + } + if (this.ctxElement == element) { + return true; + } + + if (contextSettings) { + this.contextSettings = contextSettings; + } + this.ctx = element.getContext("webgl2", this.contextSettings) || element.getContext("webgl", this.contextSettings); + if (!this.ctx) { + return false; + } + this.ctxElement = element; + if (this.ctx.getParameter(0x1F02).indexOf("WebGL 2.0") !== -1) { + this.ctxVersion = 2.0; + } else { + this.ctxVersion = 1.0; + } + return true; + } + + assertWebGL2() { + if (this.ctxVersion < 2) { + throw new Error("WebGL2 procedure called in a canvas without a WebGL2 context"); + } + } + getNewId(table) { + for (var ret = this.counter++, i = table.length; i < ret; i++) { + table[i] = null; + } + return ret; + } + recordError(errorCode) { + this.lastError || (this.lastError = errorCode); + } + populateUniformTable(program) { + let p = this.programs[program]; + this.programInfos[program] = { + uniforms: {}, + maxUniformLength: 0, + maxAttributeLength: -1, + maxUniformBlockNameLength: -1, + }; + for (let ptable = this.programInfos[program], utable = ptable.uniforms, numUniforms = this.ctx.getProgramParameter(p, this.ctx.ACTIVE_UNIFORMS), i = 0; i < numUniforms; ++i) { + let u = this.ctx.getActiveUniform(p, i); + let name = u.name; + if (ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length + 1), name.indexOf("]", name.length - 1) !== -1) { + name = name.slice(0, name.lastIndexOf("[")); + } + let loc = this.ctx.getUniformLocation(p, name); + if (loc !== null) { + let id = this.getNewId(this.uniforms); + utable[name] = [u.size, id], this.uniforms[id] = loc; + for (let j = 1; j < u.size; ++j) { + let n = name + "[" + j + "]"; + let loc = this.ctx.getUniformLocation(p, n); + let id = this.getNewId(this.uniforms); + this.uniforms[id] = loc; + } + } + } + } + getSource(shader, strings_ptr, strings_length) { + const STRING_SIZE = 2*4; + let source = ""; + for (let i = 0; i < strings_length; i++) { + let ptr = this.mem.loadPtr(strings_ptr + i*STRING_SIZE); + let len = this.mem.loadPtr(strings_ptr + i*STRING_SIZE + 4); + let str = this.mem.loadString(ptr, len); + source += str; + } + return source; + } + + getWebGL1Interface() { + return { + SetCurrentContextById: (name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + let element = document.getElementById(name); + return this.setCurrentContext(element, this.contextSettings); + }, + + DrawingBufferWidth: () => this.ctx.drawingBufferWidth, + DrawingBufferHeight: () => this.ctx.drawingBufferHeight, + + IsExtensionSupported: (name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + let extensions = this.ctx.getSupportedExtensions(); + return extensions.indexOf(name) !== -1 + }, + + + GetError: () => { + let err = this.lastError; + this.recordError(0); + if (err) { + return err; + } + return this.ctx.getError(); + }, + + GetWebGLVersion: (major_ptr, minor_ptr) => { + let version = this.ctx.getParameter(0x1F02); + if (version.indexOf("WebGL 2.0") !== -1) { + this.mem.storeI32(major_ptr, 2); + this.mem.storeI32(minor_ptr, 0); + return; + } + + this.mem.storeI32(major_ptr, 1); + this.mem.storeI32(minor_ptr, 0); + }, + GetESVersion: (major_ptr, minor_ptr) => { + let version = this.ctx.getParameter(0x1F02); + if (version.indexOf("OpenGL ES 3.0") !== -1) { + this.mem.storeI32(major_ptr, 3); + this.mem.storeI32(minor_ptr, 0); + return; + } + + this.mem.storeI32(major_ptr, 2); + this.mem.storeI32(minor_ptr, 0); + }, + + + ActiveTexture: (x) => { + this.ctx.activeTexture(x); + }, + AttachShader: (program, shader) => { + this.ctx.attachShader(this.programs[program], this.shaders[shader]); + }, + BindAttribLocation: (program, index, name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + this.ctx.bindAttribLocation(this.programs[program], index, name) + }, + BindBuffer: (target, buffer) => { + let bufferObj = buffer ? this.buffers[buffer] : null; + if (target == 35051) { + this.ctx.currentPixelPackBufferBinding = buffer; + } else { + if (target == 35052) { + this.ctx.currentPixelUnpackBufferBinding = buffer; + } + this.ctx.bindBuffer(target, bufferObj) + } + }, + BindFramebuffer: (target, buffer) => { + // TODO: BindFramebuffer + }, + BindTexture: (target, texture) => { + this.ctx.bindTexture(target, texture ? this.textures[texture] : null) + }, + BlendColor: (red, green, blue, alpha) => { + this.ctx.blendColor(red, green, blue, alpha); + }, + BlendEquation: (mode) => { + this.ctx.blendEquation(mode); + }, + BlendFunc: (sfactor, dfactor) => { + this.ctx.blendFunc(sfactor, dfactor); + }, + BlendFuncSeparate: (srcRGB, dstRGB, srcAlpha, dstAlpha) => { + this.ctx.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha); + }, + + + BufferData: (target, size, data, usage) => { + if (data) { + this.ctx.bufferData(target, this.mem.loadBytes(data, size), usage); + } else { + this.ctx.bufferData(target, size, usage); + } + }, + BufferSubData: (target, offset, size, data) => { + if (data) { + this.ctx.bufferSubData(target, offset, this.mem.loadBytes(data, size)); + } else { + this.ctx.bufferSubData(target, offset, null); + } + }, + + + Clear: (x) => { + this.ctx.clear(x); + }, + ClearColor: (r, g, b, a) => { + this.ctx.clearColor(r, g, b, a); + }, + ClearDepth: (x) => { + this.ctx.clearDepth(x); + }, + ClearStencil: (x) => { + this.ctx.clearStencil(x); + }, + ColorMask: (r, g, b, a) => { + this.ctx.colorMask(!!r, !!g, !!b, !!a); + }, + CompileShader: (shader) => { + this.ctx.compileShader(this.shaders[shader]); + }, + + + CompressedTexImage2D: (target, level, internalformat, width, height, border, imageSize, data) => { + if (data) { + this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, this.mem.loadBytes(data, imageSize)); + } else { + this.ctx.compressedTexImage2D(target, level, internalformat, width, height, border, null); + } + }, + CompressedTexSubImage2D: (target, level, xoffset, yoffset, width, height, format, imageSize, data) => { + if (data) { + this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, this.mem.loadBytes(data, imageSize)); + } else { + this.ctx.compressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, null); + } + }, + + CopyTexImage2D: (target, level, internalformat, x, y, width, height, border) => { + this.ctx.copyTexImage2D(target, level, internalformat, x, y, width, height, border); + }, + CopyTexSubImage2D: (target, level, xoffset, yoffset, x, y, width, height) => { + this.ctx.copyTexImage2D(target, level, xoffset, yoffset, x, y, width, height); + }, + + + CreateBuffer: () => { + let buffer = this.ctx.createBuffer(); + if (!buffer) { + this.recordError(1282); + return 0; + } + let id = this.getNewId(this.buffers); + buffer.name = id + this.buffers[id] = buffer; + return id; + }, + CreateFramebuffer: () => { + let buffer = this.ctx.createFramebuffer(); + let id = this.getNewId(this.framebuffers); + buffer.name = id + this.framebuffers[id] = buffer; + return id; + }, + CreateProgram: () => { + let program = this.ctx.createProgram(); + let id = this.getNewId(this.programs); + program.name = id; + this.programs[id] = program; + return id; + }, + CreateRenderbuffer: () => { + let buffer = this.ctx.createRenderbuffer(); + let id = this.getNewId(this.renderbuffers); + buffer.name = id; + this.renderbuffers[id] = buffer; + return id; + }, + CreateShader: (shaderType) => { + let shader = this.ctx.createShader(shaderType); + let id = this.getNewId(this.shaders); + shader.name = id; + this.shaders[id] = shader; + return id; + }, + CreateTexture: () => { + let texture = this.ctx.createTexture(); + if (!texture) { + this.recordError(1282) + return 0; + } + let id = this.getNewId(this.textures); + texture.name = id; + this.textures[id] = texture; + return id; + }, + + + CullFace: (mode) => { + this.ctx.cullFace(mode); + }, + + + DeleteBuffer: (id) => { + let obj = this.buffers[id]; + if (obj && id != 0) { + this.ctx.deleteBuffer(obj); + this.buffers[id] = null; + } + }, + DeleteFramebuffer: (id) => { + let obj = this.framebuffers[id]; + if (obj && id != 0) { + this.ctx.deleteFramebuffer(obj); + this.framebuffers[id] = null; + } + }, + DeleteProgram: (id) => { + let obj = this.programs[id]; + if (obj && id != 0) { + this.ctx.deleteProgram(obj); + this.programs[id] = null; + } + }, + DeleteRenderbuffer: (id) => { + let obj = this.renderbuffers[id]; + if (obj && id != 0) { + this.ctx.deleteRenderbuffer(obj); + this.renderbuffers[id] = null; + } + }, + DeleteShader: (id) => { + let obj = this.shaders[id]; + if (obj && id != 0) { + this.ctx.deleteShader(obj); + this.shaders[id] = null; + } + }, + DeleteTexture: (id) => { + let obj = this.textures[id]; + if (obj && id != 0) { + this.ctx.deleteTexture(obj); + this.textures[id] = null; + } + }, + + + DepthFunc: (func) => { + this.ctx.depthFunc(func); + }, + DepthMask: (flag) => { + this.ctx.depthMask(!!flag); + }, + DepthRange: (zNear, zFar) => { + this.ctx.depthRange(zNear, zFar); + }, + DetachShader: (program, shader) => { + this.ctx.detachShader(this.programs[program], this.shaders[shader]); + }, + Disable: (cap) => { + this.ctx.disable(cap); + }, + DisableVertexAttribArray: (index) => { + this.ctx.disableVertexAttribArray(index); + }, + DrawArrays: (mode, first, count) => { + this.ctx.drawArrays(mode, first, count); + }, + DrawElements: (mode, count, type, indices) => { + this.ctx.drawElements(mode, count, type, indices); + }, + + + Enable: (cap) => { + this.ctx.enable(cap); + }, + EnableVertexAttribArray: (index) => { + this.ctx.enableVertexAttribArray(index); + }, + Finish: () => { + this.ctx.finish(); + }, + Flush: () => { + this.ctx.flush(); + }, + FramebufferRenderBuffer: (target, attachment, renderbuffertarget, renderbuffer) => { + this.ctx.framebufferRenderBuffer(target, attachment, renderbuffertarget, this.renderbuffers[renderbuffer]); + }, + FramebufferTexture2D: (target, attachment, textarget, texture, level) => { + this.ctx.framebufferTexture2D(target, attachment, textarget, this.textures[texture], level); + }, + FrontFace: (mode) => { + this.ctx.frontFace(mode); + }, + + + GenerateMipmap: (target) => { + this.ctx.generateMipmap(target); + }, + + + GetAttribLocation: (program, name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + return this.ctx.getAttribLocation(this.programs[program], name); + }, + + + + GetProgramParameter: (program, pname) => { + return this.ctx.getProgramParameter(this.programs[program], pname) + }, + GetProgramInfoLog: (program, buf_ptr, buf_len, length_ptr) => { + let log = this.ctx.getProgramInfoLog(this.programs[program]); + if (log === null) { + log = "(unknown error)"; + } + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, log.length); + log = log.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(log)) + + this.mem.storeInt(length_ptr, n); + } + }, + GetShaderInfoLog: (shader, buf_ptr, buf_len, length_ptr) => { + let log = this.ctx.getShaderInfoLog(this.shaders[shader]); + if (log === null) { + log = "(unknown error)"; + } + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, log.length); + log = log.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(log)) + + this.mem.storeInt(length_ptr, n); + } + }, + GetShaderiv: (shader, pname, p) => { + if (p) { + if (pname == 35716) { + let log = this.ctx.getShaderInfoLog(this.shaders[shader]); + if (log === null) { + log = "(unknown error)"; + } + this.mem.storeInt(p, log.length+1); + } else if (pname == 35720) { + let source = this.ctx.getShaderSource(this.shaders[shader]); + let sourceLength = (source === null || source.length == 0) ? 0 : source.length+1; + this.mem.storeInt(p, sourceLength); + } else { + let param = this.ctx.getShaderParameter(this.shaders[shader], pname); + this.mem.storeI32(p, param); + } + } else { + this.recordError(1281); + } + }, + + + GetUniformLocation: (program, name_ptr, name_len) => { + let name = this.mem.loadString(name_ptr, name_len); + let arrayOffset = 0; + if (name.indexOf("]", name.length - 1) !== -1) { + let ls = name.lastIndexOf("["), + arrayIndex = name.slice(ls + 1, -1); + if (arrayIndex.length > 0 && (arrayOffset = parseInt(arrayIndex)) < 0) { + return -1; + } + name = name.slice(0, ls) + } + var ptable = this.programInfos[program]; + if (!ptable) { + return -1; + } + var uniformInfo = ptable.uniforms[name]; + return (uniformInfo && arrayOffset < uniformInfo[0]) ? uniformInfo[1] + arrayOffset : -1 + }, + + + GetVertexAttribOffset: (index, pname) => { + return this.ctx.getVertexAttribOffset(index, pname); + }, + + + Hint: (target, mode) => { + this.ctx.hint(target, mode); + }, + + + IsBuffer: (buffer) => this.ctx.isBuffer(this.buffers[buffer]), + IsEnabled: (enabled) => this.ctx.isEnabled(this.enableds[enabled]), + IsFramebuffer: (framebuffer) => this.ctx.isFramebuffer(this.framebuffers[framebuffer]), + IsProgram: (program) => this.ctx.isProgram(this.programs[program]), + IsRenderbuffer: (renderbuffer) => this.ctx.isRenderbuffer(this.renderbuffers[renderbuffer]), + IsShader: (shader) => this.ctx.isShader(this.shaders[shader]), + IsTexture: (texture) => this.ctx.isTexture(this.textures[texture]), + + LineWidth: (width) => { + this.ctx.lineWidth(width); + }, + LinkProgram: (program) => { + this.ctx.linkProgram(this.programs[program]); + this.programInfos[program] = null; + this.populateUniformTable(program); + }, + PixelStorei: (pname, param) => { + this.ctx.pixelStorei(pname, param); + }, + PolygonOffset: (factor, units) => { + this.ctx.polygonOffset(factor, units); + }, + + + ReadnPixels: (x, y, width, height, format, type, bufSize, data) => { + this.ctx.readPixels(x, y, width, format, type, this.mem.loadBytes(data, bufSize)); + }, + RenderbufferStorage: (target, internalformat, width, height) => { + this.ctx.renderbufferStorage(target, internalformat, width, height); + }, + SampleCoverage: (value, invert) => { + this.ctx.sampleCoverage(value, !!invert); + }, + Scissor: (x, y, width, height) => { + this.ctx.scissor(x, y, width, height); + }, + ShaderSource: (shader, strings_ptr, strings_length) => { + let source = this.getSource(shader, strings_ptr, strings_length); + this.ctx.shaderSource(this.shaders[shader], source); + }, + + StencilFunc: (func, ref, mask) => { + this.ctx.stencilFunc(func, ref, mask); + }, + StencilFuncSeparate: (face, func, ref, mask) => { + this.ctx.stencilFuncSeparate(face, func, ref, mask); + }, + StencilMask: (mask) => { + this.ctx.stencilMask(mask); + }, + StencilMaskSeparate: (face, mask) => { + this.ctx.stencilMaskSeparate(face, mask); + }, + StencilOp: (fail, zfail, zpass) => { + this.ctx.stencilOp(fail, zfail, zpass); + }, + StencilOpSeparate: (face, fail, zfail, zpass) => { + this.ctx.stencilOpSeparate(face, fail, zfail, zpass); + }, + + + TexImage2D: (target, level, internalformat, width, height, border, format, type, size, data) => { + if (data) { + this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, this.mem.loadBytes(data, size)); + } else { + this.ctx.texImage2D(target, level, internalformat, width, height, border, format, type, null); + } + }, + TexParameterf: (target, pname, param) => { + this.ctx.texParameterf(target, pname, param); + }, + TexParameteri: (target, pname, param) => { + this.ctx.texParameteri(target, pname, param); + }, + TexSubImage2D: (target, level, xoffset, yoffset, width, height, format, type, size, data) => { + this.ctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, this.mem.loadBytes(data, size)); + }, + + + Uniform1f: (location, v0) => { this.ctx.uniform1f(this.uniforms[location], v0); }, + Uniform2f: (location, v0, v1) => { this.ctx.uniform2f(this.uniforms[location], v0, v1); }, + Uniform3f: (location, v0, v1, v2) => { this.ctx.uniform3f(this.uniforms[location], v0, v1, v2); }, + Uniform4f: (location, v0, v1, v2, v3) => { this.ctx.uniform4f(this.uniforms[location], v0, v1, v2, v3); }, + + Uniform1i: (location, v0) => { this.ctx.uniform1i(this.uniforms[location], v0); }, + Uniform2i: (location, v0, v1) => { this.ctx.uniform2i(this.uniforms[location], v0, v1); }, + Uniform3i: (location, v0, v1, v2) => { this.ctx.uniform3i(this.uniforms[location], v0, v1, v2); }, + Uniform4i: (location, v0, v1, v2, v3) => { this.ctx.uniform4i(this.uniforms[location], v0, v1, v2, v3); }, + + UniformMatrix2fv: (location, addr) => { + let array = this.mem.loadF32Array(addr, 2*2); + this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); + }, + UniformMatrix3fv: (location, addr) => { + let array = this.mem.loadF32Array(addr, 3*3); + this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); + }, + UniformMatrix4fv: (location, addr) => { + let array = this.mem.loadF32Array(addr, 4*4); + this.ctx.uniformMatrix4fv(this.uniforms[location], false, array); + }, + + UseProgram: (program) => { + if (program) this.ctx.useProgram(this.programs[program]); + }, + ValidateProgram: (program) => { + if (program) this.ctx.validateProgram(this.programs[program]); + }, + + + VertexAttrib1f: (index, x) => { + this.ctx.vertexAttrib1f(index, x); + }, + VertexAttrib2f: (index, x, y) => { + this.ctx.vertexAttrib2f(index, x, y); + }, + VertexAttrib3f: (index, x, y, z) => { + this.ctx.vertexAttrib3f(index, x, y, z); + }, + VertexAttrib4f: (index, x, y, z, w) => { + this.ctx.vertexAttrib4f(index, x, y, z, w); + }, + VertexAttribPointer: (index, size, type, normalized, stride, ptr) => { + this.ctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); + }, + + Viewport: (x, y, w, h) => { + this.ctx.viewport(x, y, w, h); + }, + }; + } + + getWebGL2Interface() { + return { + /* Buffer objects */ + CopyBufferSubData: (readTarget, writeTarget, readOffset, writeOffset, size) => { + this.assertWebGL2(); + this.ctx.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); + }, + GetBufferSubData: (target, srcByteOffset, dst_buffer_ptr, dst_buffer_len, dstOffset, length) => { + this.assertWebGL2(); + this.ctx.getBufferSubData(target, srcByteOffset, this.mem.loadBytes(dst_buffer_ptr, dst_buffer_len), dstOffset, length); + }, + + /* Framebuffer objects */ + BlitFramebuffer: (srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter) => { + this.assertWebGL2(); + this.ctx.glitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); + }, + FramebufferTextureLayer: (target, attachment, texture, level, layer) => { + this.assertWebGL2(); + this.ctx.framebufferTextureLayer(target, attachment, this.textures[texture], level, layer); + }, + InvalidateFramebuffer: (target, attachments_ptr, attachments_len) => { + this.assertWebGL2(); + let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len); + this.ctx.invalidateFramebuffer(target, attachments); + }, + InvalidateSubFramebuffer: (target, attachments_ptr, attachments_len, x, y, width, height) => { + this.assertWebGL2(); + let attachments = this.mem.loadU32Array(attachments_ptr, attachments_len); + this.ctx.invalidateSubFramebuffer(target, attachments, x, y, width, height); + }, + ReadBuffer: (src) => { + this.assertWebGL2(); + this.ctx.readBuffer(src); + }, + + /* Renderbuffer objects */ + RenderbufferStorageMultisample: (target, samples, internalformat, width, height) => { + this.assertWebGL2(); + this.ctx.renderbufferStorageMultisample(target, samples, internalformat, width, height); + }, + + /* Texture objects */ + + TexStorage3D: (target, levels, internalformat, width, height, depth) => { + this.assertWebGL2(); + this.ctx.texStorage3D(target, level, internalformat, width, heigh, depth); + }, + TexImage3D: (target, level, internalformat, width, height, depth, border, format, type, size, data) => { + this.assertWebGL2(); + if (data) { + this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, this.mem.loadBytes(data, size)); + } else { + this.ctx.texImage3D(target, level, internalformat, width, height, depth, border, format, type, null); + } + }, + TexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, size, data) => { + this.assertWebGL2(); + this.ctx.texSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, this.mem.loadBytes(data, size)); + }, + CompressedTexImage3D: (target, level, internalformat, width, height, depth, border, imageSize, data) => { + this.assertWebGL2(); + if (data) { + this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, this.mem.loadBytes(data, imageSize)); + } else { + this.ctx.compressedTexImage3D(target, level, internalformat, width, height, depth, border, null); + } + }, + CompressedTexSubImage3D: (target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data) => { + this.assertWebGL2(); + if (data) { + this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, this.mem.loadBytes(data, imageSize)); + } else { + this.ctx.compressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, null); + } + }, + + CopyTexSubImage3D: (target, level, xoffset, yoffset, zoffset, x, y, width, height) => { + this.assertWebGL2(); + this.ctx.copyTexImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height); + }, + + /* Programs and shaders */ + GetFragDataLocation: (program, name_ptr, name_len) => { + this.assertWebGL2(); + return this.ctx.getFragDataLocation(this.programs[program], this.mem.loadString(name_ptr, name_len)); + }, + + /* Uniforms */ + Uniform1ui: (location, v0) => { + this.assertWebGL2(); + this.ctx.uniform1ui(this.uniforms[location], v0); + }, + Uniform2ui: (location, v0, v1) => { + this.assertWebGL2(); + this.ctx.uniform2ui(this.uniforms[location], v0, v1); + }, + Uniform3ui: (location, v0, v1, v2) => { + this.assertWebGL2(); + this.ctx.uniform3ui(this.uniforms[location], v0, v1, v2); + }, + Uniform4ui: (location, v0, v1, v2, v3) => { + this.assertWebGL2(); + this.ctx.uniform4ui(this.uniforms[location], v0, v1, v2, v3); + }, + + UniformMatrix3x2fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 3*2); + this.ctx.uniformMatrix3x2fv(this.uniforms[location], false, array); + }, + UniformMatrix4x2fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 4*2); + this.ctx.uniformMatrix4x2fv(this.uniforms[location], false, array); + }, + UniformMatrix2x3fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 2*3); + this.ctx.uniformMatrix2x3fv(this.uniforms[location], false, array); + }, + UniformMatrix4x3fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 4*3); + this.ctx.uniformMatrix4x3fv(this.uniforms[location], false, array); + }, + UniformMatrix2x4fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 2*4); + this.ctx.uniformMatrix2x4fv(this.uniforms[location], false, array); + }, + UniformMatrix3x4fv: (location, addr) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(addr, 3*4); + this.ctx.uniformMatrix3x4fv(this.uniforms[location], false, array); + }, + + /* Vertex attribs */ + VertexAttribI4i: (index, x, y, z, w) => { + this.assertWebGL2(); + this.ctx.vertexAttribI4i(index, x, y, z, w); + }, + VertexAttribI4ui: (index, x, y, z, w) => { + this.assertWebGL2(); + this.ctx.vertexAttribI4ui(index, x, y, z, w); + }, + VertexAttribIPointer: (index, size, type, stride, offset) => { + this.assertWebGL2(); + this.ctx.vertexAttribIPointer(index, size, type, stride, offset); + }, + + /* Writing to the drawing buffer */ + VertexAttribDivisor: (index, divisor) => { + this.assertWebGL2(); + this.ctx.vertexAttribDivisor(index, divisor); + }, + DrawArraysInstanced: (mode, first, count, instanceCount) => { + this.assertWebGL2(); + this.ctx.drawArraysInstanced(mode, first, count, instanceCount); + }, + DrawElementsInstanced: (mode, count, type, offset, instanceCount) => { + this.assertWebGL2(); + this.ctx.drawElementsInstanced(mode, count, type, offset, instanceCount); + }, + DrawRangeElements: (mode, start, end, count, type, offset) => { + this.assertWebGL2(); + this.ctx.drawRangeElements(mode, start, end, count, type, offset); + }, + + /* Multiple Render Targets */ + DrawBuffers: (buffers_ptr, buffers_len) => { + this.assertWebGL2(); + let array = this.mem.loadU32Array(buffers_ptr, buffers_len); + this.ctx.drawBuffers(array); + }, + ClearBufferfv: (buffer, drawbuffer, values_ptr, values_len) => { + this.assertWebGL2(); + let array = this.mem.loadF32Array(values_ptr, values_len); + this.ctx.clearBufferfv(buffer, drawbuffer, array); + }, + ClearBufferiv: (buffer, drawbuffer, values_ptr, values_len) => { + this.assertWebGL2(); + let array = this.mem.loadI32Array(values_ptr, values_len); + this.ctx.clearBufferiv(buffer, drawbuffer, array); + }, + ClearBufferuiv: (buffer, drawbuffer, values_ptr, values_len) => { + this.assertWebGL2(); + let array = this.mem.loadU32Array(values_ptr, values_len); + this.ctx.clearBufferuiv(buffer, drawbuffer, array); + }, + ClearBufferfi: (buffer, drawbuffer, depth, stencil) => { + this.assertWebGL2(); + this.ctx.clearBufferfi(buffer, drawbuffer, depth, stencil); + }, + + /* Query Objects */ + CreateQuery: () => { + this.assertWebGL2(); + let query = this.ctx.createQuery(); + let id = this.getNewId(this.queries); + query.name = id; + this.queries[id] = query; + return id; + }, + DeleteQuery: (id) => { + this.assertWebGL2(); + let obj = this.querys[id]; + if (obj && id != 0) { + this.ctx.deleteQuery(obj); + this.querys[id] = null; + } + }, + IsQuery: (query) => { + this.assertWebGL2(); + return this.ctx.isQuery(this.queries[query]); + }, + BeginQuery: (target, query) => { + this.assertWebGL2(); + this.ctx.beginQuery(target, this.queries[query]) + }, + EndQuery: (target) => { + this.assertWebGL2(); + this.ctx.endQuery(target); + }, + GetQuery: (target, pname) => { + this.assertWebGL2(); + let query = this.ctx.getQuery(target, pname); + if (!query) { + return 0; + } + if (this.queries.indexOf(query) !== -1) { + return query.name; + } + let id = this.getNewId(this.queries); + query.name = id; + this.queries[id] = query; + return id; + }, + + /* Sampler Objects */ + CreateSampler: () => { + this.assertWebGL2(); + let sampler = this.ctx.createSampler(); + let id = this.getNewId(this.samplers); + sampler.name = id; + this.samplers[id] = sampler; + return id; + }, + DeleteSampler: (id) => { + this.assertWebGL2(); + let obj = this.samplers[id]; + if (obj && id != 0) { + this.ctx.deleteSampler(obj); + this.samplers[id] = null; + } + }, + IsSampler: (sampler) => { + this.assertWebGL2(); + return this.ctx.isSampler(this.samplers[sampler]); + }, + BindSampler: (unit, sampler) => { + this.assertWebGL2(); + this.ctx.bindSampler(unit, this.samplers[Sampler]); + }, + SamplerParameteri: (sampler, pname, param) => { + this.assertWebGL2(); + this.ctx.samplerParameteri(this.samplers[sampler], pname, param); + }, + SamplerParameterf: (sampler, pname, param) => { + this.assertWebGL2(); + this.ctx.samplerParameterf(this.samplers[sampler], pname, param); + }, + + /* Sync objects */ + FenceSync: (condition, flags) => { + this.assertWebGL2(); + let sync = this.ctx.fenceSync(condition, flags); + let id = this.getNewId(this.syncs); + sync.name = id; + this.syncs[id] = sync; + return id; + }, + IsSync: (sync) => { + this.assertWebGL2(); + return this.ctx.isSync(this.syncs[sync]); + }, + DeleteSync: (id) => { + this.assertWebGL2(); + let obj = this.syncs[id]; + if (obj && id != 0) { + this.ctx.deleteSampler(obj); + this.syncs[id] = null; + } + }, + ClientWaitSync: (sync, flags, timeout) => { + this.assertWebGL2(); + return this.ctx.clientWaitSync(this.syncs[sync], flags, timeout); + }, + WaitSync: (sync, flags, timeout) => { + this.assertWebGL2(); + this.ctx.waitSync(this.syncs[sync], flags, timeout) ; + }, + + + /* Transform Feedback */ + CreateTransformFeedback: () => { + this.assertWebGL2(); + let transformFeedback = this.ctx.createtransformFeedback(); + let id = this.getNewId(this.transformFeedbacks); + transformFeedback.name = id; + this.transformFeedbacks[id] = transformFeedback; + return id; + }, + DeleteTransformFeedback: (id) => { + this.assertWebGL2(); + let obj = this.transformFeedbacks[id]; + if (obj && id != 0) { + this.ctx.deleteTransformFeedback(obj); + this.transformFeedbacks[id] = null; + } + }, + IsTransformFeedback: (tf) => { + this.assertWebGL2(); + return this.ctx.isTransformFeedback(this.transformFeedbacks[tf]); + }, + BindTransformFeedback: (target, tf) => { + this.assertWebGL2(); + this.ctx.bindTransformFeedback(target, this.transformFeedbacks[tf]); + }, + BeginTransformFeedback: (primitiveMode) => { + this.assertWebGL2(); + this.ctx.beginTransformFeedback(primitiveMode); + }, + EndTransformFeedback: () => { + this.assertWebGL2(); + this.ctx.endTransformFeedback(); + }, + TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => { + this.assertWebGL2(); + let varyings = []; + for (let i = 0; i < varyings_len; i++) { + let ptr = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 0*4); + let len = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 1*4); + varyings.push(this.mem.loadString(ptr, len)); + } + this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode); + }, + PauseTransformFeedback: () => { + this.assertWebGL2(); + this.ctx.pauseTransformFeedback(); + }, + ResumeTransformFeedback: () => { + this.assertWebGL2(); + this.ctx.resumeTransformFeedback(); + }, + + + /* Uniform Buffer Objects and Transform Feedback Buffers */ + BindBufferBase: (target, index, buffer) => { + this.assertWebGL2(); + this.ctx.bindBufferBase(target, index, this.buffers[buffer]); + }, + BindBufferRange: (target, index, buffer, offset, size) => { + this.assertWebGL2(); + this.ctx.bindBufferRange(target, index, this.buffers[buffer], offset, size); + }, + GetUniformBlockIndex: (program, uniformBlockName_ptr, uniformBlockName_len) => { + this.assertWebGL2(); + return this.ctx.getUniformBlockIndex(this.programs[program], this.mem.loadString(uniformBlockName_ptr, uniformBlockName_len)); + }, + // any getActiveUniformBlockParameter(WebGLProgram program, GLuint uniformBlockIndex, GLenum pname); + GetActiveUniformBlockName: (program, uniformBlockIndex, buf_ptr, buf_len, length_ptr) => { + this.assertWebGL2(); + let name = this.ctx.getActiveUniformBlockName(this.programs[program], uniformBlockIndex); + + let n = Math.min(buf_len, name.length); + name = name.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(name)) + this.mem.storeInt(length_ptr, n); + }, + UniformBlockBinding: (program, uniformBlockIndex, uniformBlockBinding) => { + this.assertWebGL2(); + this.ctx.uniformBlockBinding(this.programs[program], uniformBlockIndex, uniformBlockBinding); + }, + + /* Vertex Array Objects */ + CreateVertexArray: () => { + this.assertWebGL2(); + let vao = this.ctx.createVertexArray(); + let id = this.getNewId(this.vaos); + vao.name = id; + this.vaos[id] = vao; + return id; + }, + DeleteVertexArray: (id) => { + this.assertWebGL2(); + let obj = this.vaos[id]; + if (obj && id != 0) { + this.ctx.deleteVertexArray(obj); + this.vaos[id] = null; + } + }, + IsVertexArray: (vertexArray) => { + this.assertWebGL2(); + return this.ctx.isVertexArray(this.vaos[vertexArray]); + }, + BindVertexArray: (vertexArray) => { + this.assertWebGL2(); + this.ctx.bindVertexArray(this.vaos[vertexArray]); + }, + }; + } +}; + + +export {WasmMemoryInterface, odinSetupDefaultImports, WebGLInterface}; \ No newline at end of file diff --git a/vendor/wasm/loader/loader.mjs b/vendor/wasm/loader/loader.mjs index 1e4acbdf3..be434cf36 100644 --- a/vendor/wasm/loader/loader.mjs +++ b/vendor/wasm/loader/loader.mjs @@ -1,24 +1,18 @@ -import {WasmMemoryInterface, odinSetupDefaultImports} from "../js/runtime.mjs"; -import {WebGLInterface} from "../WebGL/runtime.mjs"; +import {WasmMemoryInterface, odinSetupDefaultImports, WebGLInterface} from "./runtime.mjs"; -export async function runWasmCanvas(wasmPath, webglCanvasElement, consoleElement, extraForeignImports) { +export async function runWasmCanvas(wasmPath, consoleElement, extraForeignImports) { let wasmMemoryInterface = new WasmMemoryInterface(); let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); let exports = {}; - if (webglCanvasElement !== undefined) { - let gl_context = new WebGLInterface( - wasmMemoryInterface, - webglCanvasElement, - {antialias: false}, - ); - if (!gl_context.ctx) { - return "WebGL is not available."; - } - imports["webgl"] = gl_context.getWebGL1Interface(); - imports["webgl2"] = gl_context.getWebGL2Interface(); - } + let gl_context = new WebGLInterface( + wasmMemoryInterface, + null, + {antialias: false}, + ); + imports["webgl"] = gl_context.getWebGL1Interface(); + imports["webgl2"] = gl_context.getWebGL2Interface(); if (extraForeignImports !== undefined) { imports = { @@ -60,4 +54,4 @@ export async function runWasmCanvas(wasmPath, webglCanvasElement, consoleElement }; -export {runWasmCanvas}; +export {runWasmCanvas}; \ No newline at end of file From 214b43974dbd4631e43c110819807dcde2f5e64d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 18 May 2022 13:06:29 +0100 Subject: [PATCH 102/254] Add WebGL `ContextAttributes` --- vendor/wasm/WebGL/webgl.odin | 17 +++++++++++++++ vendor/wasm/js/runtime.mjs | 42 ++++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/vendor/wasm/WebGL/webgl.odin b/vendor/wasm/WebGL/webgl.odin index 272e323bc..d3d24f470 100644 --- a/vendor/wasm/WebGL/webgl.odin +++ b/vendor/wasm/WebGL/webgl.odin @@ -13,9 +13,26 @@ Renderbuffer :: distinct u32 Shader :: distinct u32 Texture :: distinct u32 +ContextAttribute :: enum u32 { + disableAlpha = 0, + disableAntialias = 1, + disableDepth = 2, + failIfMajorPerformanceCaveat = 3, + disablePremultipliedAlpha = 4, + preserveDrawingBuffer = 5, + stencil = 6, + desynchronized = 7, +} +ContextAttributes :: distinct bit_set[ContextAttribute; u32] + +DEFAULT_CONTEXT_ATTRIBUTES :: ContextAttributes{} + @(default_calling_convention="c") foreign webgl { + CreateCurrentContextById :: proc(name: string, attributes := DEFAULT_CONTEXT_ATTRIBUTES) -> bool --- + GetCurrentContextAttributes :: proc() -> ContextAttributes --- SetCurrentContextById :: proc(name: string) -> bool --- + DrawingBufferWidth :: proc() -> i32 --- DrawingBufferHeight :: proc() -> i32 --- diff --git a/vendor/wasm/js/runtime.mjs b/vendor/wasm/js/runtime.mjs index 4e7ab9dcc..ce2c24106 100644 --- a/vendor/wasm/js/runtime.mjs +++ b/vendor/wasm/js/runtime.mjs @@ -400,7 +400,6 @@ class WebGLInterface { this.transformFeedbacks = []; this.syncs = []; this.programInfos = {}; - this.contextSettings = {antialias: false}; this.setCurrentContext(canvasElement, contextSettings); } @@ -417,10 +416,8 @@ class WebGLInterface { return true; } - if (contextSettings) { - this.contextSettings = contextSettings; - } - this.ctx = element.getContext("webgl2", this.contextSettings) || element.getContext("webgl", this.contextSettings); + contextSettings = contextSettings ?? {}; + this.ctx = element.getContext("webgl2", contextSettings) || element.getContext("webgl", contextSettings); if (!this.ctx) { return false; } @@ -491,7 +488,40 @@ class WebGLInterface { SetCurrentContextById: (name_ptr, name_len) => { let name = this.mem.loadString(name_ptr, name_len); let element = document.getElementById(name); - return this.setCurrentContext(element, this.contextSettings); + return this.setCurrentContext(element, {alpha: true, antialias: true, depth: true, premultipliedAlpha: true}); + }, + CreateCurrentContextById: (name_ptr, name_len, attributes) => { + let name = this.mem.loadString(name_ptr, name_len); + let element = document.getElementById(name); + + let contextSettings = { + alpha: !(attributes & (1<<0)), + antialias: !(attributes & (1<<1)), + depth: !(attributes & (1<<2)), + failIfMajorPerformanceCaveat: !!(attributes & (1<<3)), + premultipliedAlpha: !(attributes & (1<<4)), + preserveDrawingBuffer: !!(attributes & (1<<5)), + stencil: !!(attributes & (1<<6)), + desynchronized: !!(attributes & (1<<7)), + }; + + return this.setCurrentContext(element, contextSettings); + }, + GetCurrentContextAttributes: () => { + if (!this.ctx) { + return 0; + } + let attrs = this.ctx.getContextAttributes(); + let res = 0; + if (!attrs.alpha) res |= 1<<0; + if (!attrs.antialias) res |= 1<<1; + if (!attrs.depth) res |= 1<<2; + if (attrs.failIfMajorPerformanceCaveat) res |= 1<<3; + if (!attrs.premultipliedAlpha) res |= 1<<4; + if (attrs.preserveDrawingBuffer) res |= 1<<5; + if (attrs.stencil) res |= 1<<6; + if (attrs.desynchronized) res |= 1<<7; + return res; }, DrawingBufferWidth: () => this.ctx.drawingBufferWidth, From 3bb31093fa663afd0368cc4d904ea1eda0694f0e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 18 May 2022 13:08:31 +0100 Subject: [PATCH 103/254] Add documentation for `CreateCurrentContextById` and `SetCurrentContextById` --- vendor/wasm/WebGL/webgl.odin | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vendor/wasm/WebGL/webgl.odin b/vendor/wasm/WebGL/webgl.odin index d3d24f470..04efc785b 100644 --- a/vendor/wasm/WebGL/webgl.odin +++ b/vendor/wasm/WebGL/webgl.odin @@ -29,9 +29,12 @@ DEFAULT_CONTEXT_ATTRIBUTES :: ContextAttributes{} @(default_calling_convention="c") foreign webgl { - CreateCurrentContextById :: proc(name: string, attributes := DEFAULT_CONTEXT_ATTRIBUTES) -> bool --- - GetCurrentContextAttributes :: proc() -> ContextAttributes --- + // CreateCurrentContextById must be called before `GetCurrentContextAttributes` if the user wants to + // set specific attributes, otherwise the default attributes will be set for the WebGL context + CreateCurrentContextById :: proc(name: string, attributes: ContextAttributes) -> bool --- + // Acquire the WebGL context from a canvas element by id SetCurrentContextById :: proc(name: string) -> bool --- + GetCurrentContextAttributes :: proc() -> ContextAttributes --- DrawingBufferWidth :: proc() -> i32 --- DrawingBufferHeight :: proc() -> i32 --- From db8d119cadbf1778516d1e6178bd566cc1609c61 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 20 May 2022 19:15:13 +0200 Subject: [PATCH 104/254] Fix Windows os.make_directory. --- core/os/file_windows.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/os/file_windows.odin b/core/os/file_windows.odin index daabe60f0..ca9beff5d 100644 --- a/core/os/file_windows.odin +++ b/core/os/file_windows.odin @@ -389,7 +389,8 @@ change_directory :: proc(path: string) -> Errno { return Errno(win32.SetCurrentDirectoryW(wpath)) } -make_directory :: proc(path: string, mode: u32) -> Errno { +make_directory :: proc(path: string, mode: u32 = 0) -> Errno { + // Mode is unused on Windows, but is needed on *nix wpath := win32.utf8_to_wstring(path, context.temp_allocator) return Errno(win32.CreateDirectoryW(wpath, nil)) } From e85f1dd9fb79a127792e30abdf0a1e73980cadcd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 20 May 2022 20:00:27 +0200 Subject: [PATCH 105/254] Fix is* proc in libc. --- core/c/libc/math.odin | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/c/libc/math.odin b/core/c/libc/math.odin index 97f77236f..6a7b81850 100644 --- a/core/c/libc/math.odin +++ b/core/c/libc/math.odin @@ -211,19 +211,19 @@ _signbitf :: #force_inline proc(x: float) -> int { return int(transmute(uint32_t)x >> 31) } -isfinite :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { +isfinite :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) { return fpclassify(x) == FP_INFINITE } -isinf :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { +isinf :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) { return fpclassify(x) > FP_INFINITE } -isnan :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { +isnan :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) { return fpclassify(x) == FP_NAN } -isnormal :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { +isnormal :: #force_inline proc(x: $T) -> bool where intrinsics.type_is_float(T) { return fpclassify(x) == FP_NORMAL } @@ -231,27 +231,27 @@ isnormal :: #force_inline proc(x: $T) where intrinsics.type_is_float(T) { // implemented as the relational comparisons, as that would produce an invalid // "sticky" state that propagates and affects maths results. These need // to be implemented natively in Odin assuming isunordered to prevent that. -isgreater :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +isgreater :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x > y } -isgreaterequal :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +isgreaterequal :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x >= y } -isless :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +isless :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x < y } -islessequal :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +islessequal :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x <= y } -islessgreater :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +islessgreater :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { return !isunordered(x, y) && x <= y } -isunordered :: #force_inline proc(x, y: $T) where intrinsics.type_is_float(T) { +isunordered :: #force_inline proc(x, y: $T) -> bool where intrinsics.type_is_float(T) { if isnan(x) { // Force evaluation of y to propagate exceptions for ordering semantics. // To ensure correct semantics of IEEE 754 this cannot be compiled away. From 06884da42b8a6a25f3d12f8ab7cfd08c21f4fa36 Mon Sep 17 00:00:00 2001 From: Tetralux Date: Sat, 21 May 2022 04:45:04 +0000 Subject: [PATCH 106/254] [path/filepath] Change join() to take a []string instead of varargs This makes passing an allocator easier, as you no longer have to resort to named arguments: Before: `join(a, b, c)` became `join(elems={a, b, c}, allocator=ally)` After: `join({a, b, c})` becomes `join({a, b, c}, ally)` --- core/c/frontend/preprocessor/preprocess.odin | 2 +- core/path/filepath/match.odin | 2 +- core/path/filepath/path_unix.odin | 2 +- core/path/filepath/path_windows.odin | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/c/frontend/preprocessor/preprocess.odin b/core/c/frontend/preprocessor/preprocess.odin index 9651cc81c..1db3fafa3 100644 --- a/core/c/frontend/preprocessor/preprocess.odin +++ b/core/c/frontend/preprocessor/preprocess.odin @@ -1276,7 +1276,7 @@ preprocess_internal :: proc(cpp: ^Preprocessor, tok: ^Token) -> ^Token { if start.file != nil { dir = filepath.dir(start.file.name) } - path := filepath.join(dir, filename) + path := filepath.join({dir, filename}) if os.exists(path) { tok = include_file(cpp, tok, path, start.next.next) continue diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 252912710..00a9c9fb0 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -305,7 +305,7 @@ _glob :: proc(dir, pattern: string, matches: ^[dynamic]string, allocator := cont n := fi.name matched := match(pattern, n) or_return if matched { - append(&m, join(dir, n)) + append(&m, join({dir, n})) } } return diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index d0eaa3635..8faf6097c 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -38,7 +38,7 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { return path_str, true } -join :: proc(elems: ..string, allocator := context.allocator) -> string { +join :: proc(elems: []string, allocator := context.allocator) -> string { for e, i in elems { if e != "" { p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 28238dd6e..cdfe3ddbb 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -88,7 +88,7 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { } -join :: proc(elems: ..string, allocator := context.allocator) -> string { +join :: proc(elems: []string, allocator := context.allocator) -> string { for e, i in elems { if e != "" { return join_non_empty(elems[i:], allocator) From 5c647e2f613b5402dc7bc016e4e454e2ac15afb3 Mon Sep 17 00:00:00 2001 From: Cedric Hutchings Date: Sat, 21 May 2022 01:50:59 -0400 Subject: [PATCH 107/254] Fix typo. --- src/parser.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index 1f4093e5f..ab947774b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4043,7 +4043,7 @@ Ast *parse_if_stmt(AstFile *f) { if (build_context.disallow_do) { syntax_error(body, "'do' has been disallowed"); } else if (!ast_on_same_line(cond, body)) { - syntax_error(body, "The body of a 'do' be on the same line as if condition"); + syntax_error(body, "The body of a 'do' must be on the same line as if condition"); } } else { body = parse_block_stmt(f, false); @@ -4065,7 +4065,7 @@ Ast *parse_if_stmt(AstFile *f) { if (build_context.disallow_do) { syntax_error(else_stmt, "'do' has been disallowed"); } else if (!ast_on_same_line(else_token, else_stmt)) { - syntax_error(else_stmt, "The body of a 'do' be on the same line as 'else'"); + syntax_error(else_stmt, "The body of a 'do' must be on the same line as 'else'"); } } break; default: @@ -4100,7 +4100,7 @@ Ast *parse_when_stmt(AstFile *f) { if (build_context.disallow_do) { syntax_error(body, "'do' has been disallowed"); } else if (!ast_on_same_line(cond, body)) { - syntax_error(body, "The body of a 'do' be on the same line as when statement"); + syntax_error(body, "The body of a 'do' must be on the same line as when statement"); } } else { body = parse_block_stmt(f, true); @@ -4122,7 +4122,7 @@ Ast *parse_when_stmt(AstFile *f) { if (build_context.disallow_do) { syntax_error(else_stmt, "'do' has been disallowed"); } else if (!ast_on_same_line(else_token, else_stmt)) { - syntax_error(else_stmt, "The body of a 'do' be on the same line as 'else'"); + syntax_error(else_stmt, "The body of a 'do' must be on the same line as 'else'"); } } break; default: @@ -4197,7 +4197,7 @@ Ast *parse_for_stmt(AstFile *f) { if (build_context.disallow_do) { syntax_error(body, "'do' has been disallowed"); } else if (!ast_on_same_line(token, body)) { - syntax_error(body, "The body of a 'do' be on the same line as the 'for' token"); + syntax_error(body, "The body of a 'do' must be on the same line as the 'for' token"); } } else { body = parse_block_stmt(f, false); @@ -4243,7 +4243,7 @@ Ast *parse_for_stmt(AstFile *f) { if (build_context.disallow_do) { syntax_error(body, "'do' has been disallowed"); } else if (!ast_on_same_line(token, body)) { - syntax_error(body, "The body of a 'do' be on the same line as the 'for' token"); + syntax_error(body, "The body of a 'do' must be on the same line as the 'for' token"); } } else { body = parse_block_stmt(f, false); @@ -4569,7 +4569,7 @@ Ast *parse_unrolled_for_loop(AstFile *f, Token unroll_token) { if (build_context.disallow_do) { syntax_error(body, "'do' has been disallowed"); } else if (!ast_on_same_line(for_token, body)) { - syntax_error(body, "The body of a 'do' be on the same line as the 'for' token"); + syntax_error(body, "The body of a 'do' must be on the same line as the 'for' token"); } } else { body = parse_block_stmt(f, false); From a5bf3b0bc58d0ce88eacaef0c7259186d2f87f8f Mon Sep 17 00:00:00 2001 From: Luxko Date: Sat, 21 May 2022 15:53:20 +0900 Subject: [PATCH 108/254] add dxgi HRESULT constants --- vendor/directx/dxgi/dxgi.odin | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/vendor/directx/dxgi/dxgi.odin b/vendor/directx/dxgi/dxgi.odin index 70c5a9e71..196cb0e96 100644 --- a/vendor/directx/dxgi/dxgi.odin +++ b/vendor/directx/dxgi/dxgi.odin @@ -1138,4 +1138,33 @@ IAdapter3_VTable :: struct { SetVideoMemoryReservation: proc "stdcall" (this: ^IAdapter3, NodeIndex: u32, MemorySegmentGroup: MEMORY_SEGMENT_GROUP, Reservation: u64) -> HRESULT, RegisterVideoMemoryBudgetChangeNotificationEvent: proc "stdcall" (this: ^IAdapter3, hEvent: HANDLE, pdwCookie: ^u32) -> HRESULT, UnregisterVideoMemoryBudgetChangeNotification: proc "stdcall" (this: ^IAdapter3, dwCookie: u32), -} \ No newline at end of file +} + +ERROR_ACCESS_DENIED :HRESULT: -2005270485 //0x887A002B +ERROR_ACCESS_LOST :HRESULT: -2005270490 //0x887A0026 +ERROR_ALREADY_EXISTS :HRESULT: -2005270474 //0x887A0036 +ERROR_CANNOT_PROTECT_CONTENT :HRESULT: -2005270486 //0x887A002A +ERROR_DEVICE_HUNG :HRESULT: -2005270522 //0x887A0006 +ERROR_DEVICE_REMOVED :HRESULT: -2005270523 //0x887A0005 +ERROR_DEVICE_RESET :HRESULT: -2005270521 //0x887A0007 +ERROR_DRIVER_INTERNAL_ERROR :HRESULT: -2005270496 //0x887A0020 +ERROR_FRAME_STATISTICS_DISJOINT :HRESULT: -2005270517 //0x887A000B +ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE :HRESULT: -2005270516 //0x887A000C +ERROR_INVALID_CALL :HRESULT: -2005270527 //0x887A0001 +ERROR_MORE_DATA :HRESULT: -2005270525 //0x887A0003 +ERROR_NAME_ALREADY_EXISTS :HRESULT: -2005270484 //0x887A002C +ERROR_NONEXCLUSIVE :HRESULT: -2005270495 //0x887A0021 +ERROR_NOT_CURRENTLY_AVAILABLE :HRESULT: -2005270494 //0x887A0022 +ERROR_NOT_FOUND :HRESULT: -2005270526 //0x887A0002 +ERROR_REMOTE_CLIENT_DISCONNECTED :HRESULT: -2005270493 //0x887A0023 +ERROR_REMOTE_OUTOFMEMORY :HRESULT: -2005270492 //0x887A0024 +ERROR_RESTRICT_TO_OUTPUT_STALE :HRESULT: -2005270487 //0x887A0029 +ERROR_SDK_COMPONENT_MISSING :HRESULT: -2005270483 //0x887A002D +ERROR_SESSION_DISCONNECTED :HRESULT: -2005270488 //0x887A0028 +ERROR_UNSUPPORTED :HRESULT: -2005270524 //0x887A0004 +ERROR_WAIT_TIMEOUT :HRESULT: -2005270489 //0x887A0027 +ERROR_WAS_STILL_DRAWING :HRESULT: -2005270518 //0x887A000A + +STATUS_OCCLUDED :HRESULT: 142213121 //0x087A0001 +STATUS_MODE_CHANGED :HRESULT: 142213127 //0x087A0007 +STATUS_MODE_CHANGE_IN_PROGRESS :HRESULT: 142213128 //0x087A0008 \ No newline at end of file From c20b5cbd10faf0f526a231ef3b4298f7647dbeff Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 11:39:35 +0100 Subject: [PATCH 109/254] Change wasm/js/runtime.mjs to a normal .js file; Add interfaces and functions to a global `odin` variable --- vendor/wasm/js/{runtime.mjs => runtime.js} | 635 +++++++++++---------- 1 file changed, 346 insertions(+), 289 deletions(-) rename vendor/wasm/js/{runtime.mjs => runtime.js} (96%) diff --git a/vendor/wasm/js/runtime.mjs b/vendor/wasm/js/runtime.js similarity index 96% rename from vendor/wasm/js/runtime.mjs rename to vendor/wasm/js/runtime.js index ce2c24106..9b35f4595 100644 --- a/vendor/wasm/js/runtime.mjs +++ b/vendor/wasm/js/runtime.js @@ -1,7 +1,9 @@ +(function() { class WasmMemoryInterface { constructor() { this.memory = null; this.exports = null; + this.listenerMap = {}; } setMemory(memory) { @@ -10,7 +12,6 @@ class WasmMemoryInterface { setExports(exports) { this.exports = exports; - this.listenerMap = {}; } get mem() { @@ -90,292 +91,8 @@ class WasmMemoryInterface { storeUint(addr, value) { this.mem.setUint32 (addr, value, true); } }; -function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { - const MAX_INFO_CONSOLE_LINES = 512; - let infoConsoleLines = new Array(); - const addConsoleLine = (line) => { - if (!line) { - return; - } - if (line.endsWith("\n")) { - line = line.substring(0, line.length-1); - } else if (infoConsoleLines.length > 0) { - let prev_line = infoConsoleLines.pop(); - line = prev_line.concat(line); - } - infoConsoleLines.push(line); - - if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { - infoConsoleLines.shift(); - } - - let data = ""; - for (let i = 0; i < infoConsoleLines.length; i++) { - if (i != 0) { - data = data.concat("\n"); - } - data = data.concat(infoConsoleLines[i]); - } - - if (consoleElement) { - let info = consoleElement; - info.innerHTML = data; - info.scrollTop = info.scrollHeight; - } - }; - - let event_temp_data = {}; - - return { - "env": {}, - "odin_env": { - write: (fd, ptr, len) => { - const str = wasmMemoryInterface.loadString(ptr, len); - if (fd == 1) { - addConsoleLine(str); - return; - } else if (fd == 2) { - addConsoleLine(str); - return; - } else { - throw new Error("Invalid fd to 'write'" + stripNewline(str)); - } - }, - trap: () => { throw new Error() }, - alert: (ptr, len) => { alert(wasmMemoryInterface.loadString(ptr, len)) }, - abort: () => { Module.abort() }, - evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); }, - - time_now: () => { - return performance.now() * 1e6; - }, - - sqrt: (x) => Math.sqrt(x), - sin: (x) => Math.sin(x), - cos: (x) => Math.cos(x), - pow: (x) => Math.pow(x), - fmuladd: (x, y, z) => x*y + z, - ln: (x) => Math.log(x), - exp: (x) => Math.exp(x), - ldexp: (x) => Math.ldexp(x), - }, - "odin_dom": { - init_event_raw: (ep) => { - const W = 4; - let offset = ep; - let off = (amount, alignment) => { - if (alignment === undefined) { - alignment = Math.min(amount, W); - } - if (offset % alignment != 0) { - offset += alignment - (offset%alignment); - } - let x = offset; - offset += amount; - return x; - }; - - let wmi = wasmMemoryInterface; - - let e = event_temp_data.event; - - wmi.storeU32(off(4), event_temp_data.name_code); - if (e.target == document) { - wmi.storeU32(off(4), 1); - } else if (e.target == window) { - wmi.storeU32(off(4), 2); - } else { - wmi.storeU32(off(4), 0); - } - if (e.currentTarget == document) { - wmi.storeU32(off(4), 1); - } else if (e.currentTarget == window) { - wmi.storeU32(off(4), 2); - } else { - wmi.storeU32(off(4), 0); - } - - wmi.storeUint(off(W), event_temp_data.id_ptr); - wmi.storeUint(off(W), event_temp_data.id_len); - - wmi.storeF64(off(8), e.timeStamp*1e-3); - - wmi.storeU8(off(1), e.eventPhase); - wmi.storeU8(off(1), !!e.bubbles); - wmi.storeU8(off(1), !!e.cancelable); - wmi.storeU8(off(1), !!e.composed); - wmi.storeU8(off(1), !!e.isComposing); - wmi.storeU8(off(1), !!e.isTrusted); - - let base = off(0, 8); - if (e instanceof MouseEvent) { - wmi.storeI64(off(8), e.screenX); - wmi.storeI64(off(8), e.screenY); - wmi.storeI64(off(8), e.clientX); - wmi.storeI64(off(8), e.clientY); - wmi.storeI64(off(8), e.offsetX); - wmi.storeI64(off(8), e.offsetY); - wmi.storeI64(off(8), e.pageX); - wmi.storeI64(off(8), e.pageY); - wmi.storeI64(off(8), e.movementX); - wmi.storeI64(off(8), e.movementY); - - wmi.storeU8(off(1), !!e.ctrlKey); - wmi.storeU8(off(1), !!e.shiftKey); - wmi.storeU8(off(1), !!e.altKey); - wmi.storeU8(off(1), !!e.metaKey); - - wmi.storeI16(off(2), e.button); - wmi.storeU16(off(2), e.buttons); - } else if (e instanceof KeyboardEvent) { - let keyOffset = off(W*2, W); - let codeOffet = off(W*2, W); - wmi.storeU8(off(1), e.location); - - wmi.storeU8(off(1), !!e.ctrlKey); - wmi.storeU8(off(1), !!e.shiftKey); - wmi.storeU8(off(1), !!e.altKey); - wmi.storeU8(off(1), !!e.metaKey); - - wmi.storeU8(off(1), !!e.repeat); - } else if (e instanceof WheelEvent) { - wmi.storeF64(off(8), e.deltaX); - wmi.storeF64(off(8), e.deltaY); - wmi.storeF64(off(8), e.deltaZ); - wmi.storeU32(off(4), e.deltaMode); - } else if (e instanceof Event) { - if ('scrollX' in e) { - wmi.storeF64(off(8), e.scrollX); - wmi.storeF64(off(8), e.scrollY); - } - } - }, - - add_event_listener: (id_ptr, id_len, name_ptr, name_len, name_code, data, callback, use_capture) => { - let id = wasmMemoryInterface.loadString(id_ptr, id_len); - let name = wasmMemoryInterface.loadString(name_ptr, name_len); - let element = document.getElementById(id); - if (element == undefined) { - return false; - } - - let listener = (e) => { - const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); - event_temp_data.id_ptr = id_ptr; - event_temp_data.id_len = id_len; - event_temp_data.event = e; - event_temp_data.name_code = name_code; - // console.log(e); - wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); - }; - wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; - element.addEventListener(name, listener, !!use_capture); - return true; - }, - - remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback) => { - let id = wasmMemoryInterface.loadString(id_ptr, id_len); - let name = wasmMemoryInterface.loadString(name_ptr, name_len); - let element = document.getElementById(id); - if (element == undefined) { - return false; - } - - let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; - if (listener == undefined) { - return false; - } - element.removeEventListener(name, listener); - return true; - }, - - - add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { - let name = wasmMemoryInterface.loadString(name_ptr, name_len); - let element = window; - let listener = (e) => { - const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); - event_temp_data.id_ptr = 0; - event_temp_data.id_len = 0; - event_temp_data.event = e; - event_temp_data.name_code = name_code; - // console.log(e); - wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); - }; - wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; - element.addEventListener(name, listener, !!use_capture); - return true; - }, - - remove_window_event_listener: (name_ptr, name_len, data, callback) => { - let name = wasmMemoryInterface.loadString(name_ptr, name_len); - let element = window; - let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; - if (listener == undefined) { - return false; - } - element.removeEventListener(name, listener); - return true; - }, - - event_stop_propagation: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.eventStopPropagation(); - } - }, - event_stop_immediate_propagation: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.eventStopImmediatePropagation(); - } - }, - event_prevent_default: () => { - if (event_temp_data && event_temp_data.event) { - event_temp_data.event.eventPreventDefault(); - } - }, - - get_element_value_f64: (id_ptr, id_len) => { - let id = wasmMemoryInterface.loadString(id_ptr, id_len); - let element = document.getElementById(id); - return element ? element.value : 0; - }, - get_element_value_string: (id_ptr, id_len, buf_ptr, buf_len) => { - let id = wasmMemoryInterface.loadString(id_ptr, id_len); - let element = document.getElementById(id); - if (element) { - let str = element.value; - if (buf_len > 0 && buf_ptr) { - let n = Math.min(buf_len, str.length); - str = str.substring(0, n); - this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(str)) - return n; - } - } - return 0; - }, - get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => { - let id = wasmMemoryInterface.loadString(id_ptr, id_len); - let element = document.getElementById(id); - if (element) { - let values = wasmMemoryInterface.loadF64Array(ptr_array2_f64, 2); - values[0] = element.min; - values[1] = element.max; - } - }, - set_element_value: (id_ptr, id_len, value) => { - let id = wasmMemoryInterface.loadString(id_ptr, id_len); - let element = document.getElementById(id); - if (element) { - element.value = value; - } - }, - }, - }; -}; - - class WebGLInterface { - constructor(wasmMemoryInterface, canvasElement, contextSettings) { + constructor(wasmMemoryInterface) { this.wasmMemoryInterface = wasmMemoryInterface; this.ctxElement = null; this.ctx = null; @@ -400,8 +117,6 @@ class WebGLInterface { this.transformFeedbacks = []; this.syncs = []; this.programInfos = {}; - - this.setCurrentContext(canvasElement, contextSettings); } get mem() { @@ -1457,4 +1172,346 @@ class WebGLInterface { }; -export {WasmMemoryInterface, odinSetupDefaultImports, WebGLInterface}; \ No newline at end of file + +function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { + const MAX_INFO_CONSOLE_LINES = 512; + let infoConsoleLines = new Array(); + const addConsoleLine = (line) => { + if (!line) { + return; + } + if (line.endsWith("\n")) { + line = line.substring(0, line.length-1); + } else if (infoConsoleLines.length > 0) { + let prev_line = infoConsoleLines.pop(); + line = prev_line.concat(line); + } + infoConsoleLines.push(line); + + if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { + infoConsoleLines.shift(); + } + + let data = ""; + for (let i = 0; i < infoConsoleLines.length; i++) { + if (i != 0) { + data = data.concat("\n"); + } + data = data.concat(infoConsoleLines[i]); + } + + if (consoleElement) { + let info = consoleElement; + info.innerHTML = data; + info.scrollTop = info.scrollHeight; + } + }; + + let event_temp_data = {}; + + let webglContext = new WebGLInterface(wasmMemoryInterface); + return { + "env": {}, + "odin_env": { + write: (fd, ptr, len) => { + const str = wasmMemoryInterface.loadString(ptr, len); + if (fd == 1) { + addConsoleLine(str); + return; + } else if (fd == 2) { + addConsoleLine(str); + return; + } else { + throw new Error("Invalid fd to 'write'" + stripNewline(str)); + } + }, + trap: () => { throw new Error() }, + alert: (ptr, len) => { alert(wasmMemoryInterface.loadString(ptr, len)) }, + abort: () => { Module.abort() }, + evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); }, + + time_now: () => { + return performance.now() * 1e6; + }, + + sqrt: (x) => Math.sqrt(x), + sin: (x) => Math.sin(x), + cos: (x) => Math.cos(x), + pow: (x) => Math.pow(x), + fmuladd: (x, y, z) => x*y + z, + ln: (x) => Math.log(x), + exp: (x) => Math.exp(x), + ldexp: (x) => Math.ldexp(x), + }, + "odin_dom": { + init_event_raw: (ep) => { + const W = 4; + let offset = ep; + let off = (amount, alignment) => { + if (alignment === undefined) { + alignment = Math.min(amount, W); + } + if (offset % alignment != 0) { + offset += alignment - (offset%alignment); + } + let x = offset; + offset += amount; + return x; + }; + + let wmi = wasmMemoryInterface; + + let e = event_temp_data.event; + + wmi.storeU32(off(4), event_temp_data.name_code); + if (e.target == document) { + wmi.storeU32(off(4), 1); + } else if (e.target == window) { + wmi.storeU32(off(4), 2); + } else { + wmi.storeU32(off(4), 0); + } + if (e.currentTarget == document) { + wmi.storeU32(off(4), 1); + } else if (e.currentTarget == window) { + wmi.storeU32(off(4), 2); + } else { + wmi.storeU32(off(4), 0); + } + + wmi.storeUint(off(W), event_temp_data.id_ptr); + wmi.storeUint(off(W), event_temp_data.id_len); + + wmi.storeF64(off(8), e.timeStamp*1e-3); + + wmi.storeU8(off(1), e.eventPhase); + wmi.storeU8(off(1), !!e.bubbles); + wmi.storeU8(off(1), !!e.cancelable); + wmi.storeU8(off(1), !!e.composed); + wmi.storeU8(off(1), !!e.isComposing); + wmi.storeU8(off(1), !!e.isTrusted); + + let base = off(0, 8); + if (e instanceof MouseEvent) { + wmi.storeI64(off(8), e.screenX); + wmi.storeI64(off(8), e.screenY); + wmi.storeI64(off(8), e.clientX); + wmi.storeI64(off(8), e.clientY); + wmi.storeI64(off(8), e.offsetX); + wmi.storeI64(off(8), e.offsetY); + wmi.storeI64(off(8), e.pageX); + wmi.storeI64(off(8), e.pageY); + wmi.storeI64(off(8), e.movementX); + wmi.storeI64(off(8), e.movementY); + + wmi.storeU8(off(1), !!e.ctrlKey); + wmi.storeU8(off(1), !!e.shiftKey); + wmi.storeU8(off(1), !!e.altKey); + wmi.storeU8(off(1), !!e.metaKey); + + wmi.storeI16(off(2), e.button); + wmi.storeU16(off(2), e.buttons); + } else if (e instanceof KeyboardEvent) { + let keyOffset = off(W*2, W); + let codeOffet = off(W*2, W); + wmi.storeU8(off(1), e.location); + + wmi.storeU8(off(1), !!e.ctrlKey); + wmi.storeU8(off(1), !!e.shiftKey); + wmi.storeU8(off(1), !!e.altKey); + wmi.storeU8(off(1), !!e.metaKey); + + wmi.storeU8(off(1), !!e.repeat); + } else if (e instanceof WheelEvent) { + wmi.storeF64(off(8), e.deltaX); + wmi.storeF64(off(8), e.deltaY); + wmi.storeF64(off(8), e.deltaZ); + wmi.storeU32(off(4), e.deltaMode); + } else if (e instanceof Event) { + if ('scrollX' in e) { + wmi.storeF64(off(8), e.scrollX); + wmi.storeF64(off(8), e.scrollY); + } + } + }, + + add_event_listener: (id_ptr, id_len, name_ptr, name_len, name_code, data, callback, use_capture) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = document.getElementById(id); + if (element == undefined) { + return false; + } + + let listener = (e) => { + const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); + event_temp_data.id_ptr = id_ptr; + event_temp_data.id_len = id_len; + event_temp_data.event = e; + event_temp_data.name_code = name_code; + // console.log(e); + wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); + }; + wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = document.getElementById(id); + if (element == undefined) { + return false; + } + + let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; + if (listener == undefined) { + return false; + } + element.removeEventListener(name, listener); + return true; + }, + + + add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = window; + let listener = (e) => { + const odin_ctx = wasmMemoryInterface.exports.default_context_ptr(); + event_temp_data.id_ptr = 0; + event_temp_data.id_len = 0; + event_temp_data.event = e; + event_temp_data.name_code = name_code; + // console.log(e); + wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); + }; + wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + remove_window_event_listener: (name_ptr, name_len, data, callback) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = window; + let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; + if (listener == undefined) { + return false; + } + element.removeEventListener(name, listener); + return true; + }, + + event_stop_propagation: () => { + if (event_temp_data && event_temp_data.event) { + event_temp_data.event.eventStopPropagation(); + } + }, + event_stop_immediate_propagation: () => { + if (event_temp_data && event_temp_data.event) { + event_temp_data.event.eventStopImmediatePropagation(); + } + }, + event_prevent_default: () => { + if (event_temp_data && event_temp_data.event) { + event_temp_data.event.eventPreventDefault(); + } + }, + + get_element_value_f64: (id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + return element ? element.value : 0; + }, + get_element_value_string: (id_ptr, id_len, buf_ptr, buf_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + if (element) { + let str = element.value; + if (buf_len > 0 && buf_ptr) { + let n = Math.min(buf_len, str.length); + str = str.substring(0, n); + this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder("utf-8").encode(str)) + return n; + } + } + return 0; + }, + get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + if (element) { + let values = wasmMemoryInterface.loadF64Array(ptr_array2_f64, 2); + values[0] = element.min; + values[1] = element.max; + } + }, + set_element_value: (id_ptr, id_len, value) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + if (element) { + element.value = value; + } + }, + }, + + "webgl": webglContext.getWebGL1Interface(), + "webgl2": webglContext.getWebGL2Interface(), + }; +}; + +async function runWasmCanvas(wasmPath, consoleElement, extraForeignImports) { + let wasmMemoryInterface = new WasmMemoryInterface(); + + let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); + let exports = {}; + + if (extraForeignImports !== undefined) { + imports = { + ...imports, + ...extraForeignImports, + }; + } + + const response = await fetch(wasmPath); + const file = await response.arrayBuffer(); + const wasm = await WebAssembly.instantiate(file, imports); + exports = wasm.instance.exports; + wasmMemoryInterface.setExports(exports); + wasmMemoryInterface.setMemory(exports.memory); + + exports._start(); + + if (exports.step) { + const odin_ctx = exports.default_context_ptr(); + + let prevTimeStamp = undefined; + const step = (currTimeStamp) => { + if (prevTimeStamp == undefined) { + prevTimeStamp = currTimeStamp; + } + + const dt = (currTimeStamp - prevTimeStamp)*0.001; + prevTimeStamp = currTimeStamp; + exports.step(dt, odin_ctx); + window.requestAnimationFrame(step); + }; + + window.requestAnimationFrame(step); + } + + exports._end(); + + return; +}; + +window.odin = { + // Interface Types + WasmMemoryInterface: WasmMemoryInterface, + WebGLInterface: WebGLInterface, + + // Functions + setupDefaultImports: odinSetupDefaultImports, + runWasmCanvas: runWasmCanvas, +}; +})(); \ No newline at end of file From 94fda3d48dedf87c63e5368bb8190218e4f11611 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 11:41:50 +0100 Subject: [PATCH 110/254] Rename to `runWasm` --- vendor/wasm/js/runtime.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 9b35f4595..52fe43b6c 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1460,7 +1460,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }; }; -async function runWasmCanvas(wasmPath, consoleElement, extraForeignImports) { +async function runWasm(wasmPath, consoleElement, extraForeignImports) { let wasmMemoryInterface = new WasmMemoryInterface(); let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); @@ -1512,6 +1512,6 @@ window.odin = { // Functions setupDefaultImports: odinSetupDefaultImports, - runWasmCanvas: runWasmCanvas, + runWasm: runWasm, }; })(); \ No newline at end of file From d10a2bc5d579c5e2d535253cb4b6c36dae388b59 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 21 May 2022 12:59:16 +0200 Subject: [PATCH 111/254] Format DXGI constants --- vendor/directx/dxgi/dxgi.odin | 54 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/vendor/directx/dxgi/dxgi.odin b/vendor/directx/dxgi/dxgi.odin index 196cb0e96..ae0cfd17a 100644 --- a/vendor/directx/dxgi/dxgi.odin +++ b/vendor/directx/dxgi/dxgi.odin @@ -1140,31 +1140,31 @@ IAdapter3_VTable :: struct { UnregisterVideoMemoryBudgetChangeNotification: proc "stdcall" (this: ^IAdapter3, dwCookie: u32), } -ERROR_ACCESS_DENIED :HRESULT: -2005270485 //0x887A002B -ERROR_ACCESS_LOST :HRESULT: -2005270490 //0x887A0026 -ERROR_ALREADY_EXISTS :HRESULT: -2005270474 //0x887A0036 -ERROR_CANNOT_PROTECT_CONTENT :HRESULT: -2005270486 //0x887A002A -ERROR_DEVICE_HUNG :HRESULT: -2005270522 //0x887A0006 -ERROR_DEVICE_REMOVED :HRESULT: -2005270523 //0x887A0005 -ERROR_DEVICE_RESET :HRESULT: -2005270521 //0x887A0007 -ERROR_DRIVER_INTERNAL_ERROR :HRESULT: -2005270496 //0x887A0020 -ERROR_FRAME_STATISTICS_DISJOINT :HRESULT: -2005270517 //0x887A000B -ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE :HRESULT: -2005270516 //0x887A000C -ERROR_INVALID_CALL :HRESULT: -2005270527 //0x887A0001 -ERROR_MORE_DATA :HRESULT: -2005270525 //0x887A0003 -ERROR_NAME_ALREADY_EXISTS :HRESULT: -2005270484 //0x887A002C -ERROR_NONEXCLUSIVE :HRESULT: -2005270495 //0x887A0021 -ERROR_NOT_CURRENTLY_AVAILABLE :HRESULT: -2005270494 //0x887A0022 -ERROR_NOT_FOUND :HRESULT: -2005270526 //0x887A0002 -ERROR_REMOTE_CLIENT_DISCONNECTED :HRESULT: -2005270493 //0x887A0023 -ERROR_REMOTE_OUTOFMEMORY :HRESULT: -2005270492 //0x887A0024 -ERROR_RESTRICT_TO_OUTPUT_STALE :HRESULT: -2005270487 //0x887A0029 -ERROR_SDK_COMPONENT_MISSING :HRESULT: -2005270483 //0x887A002D -ERROR_SESSION_DISCONNECTED :HRESULT: -2005270488 //0x887A0028 -ERROR_UNSUPPORTED :HRESULT: -2005270524 //0x887A0004 -ERROR_WAIT_TIMEOUT :HRESULT: -2005270489 //0x887A0027 -ERROR_WAS_STILL_DRAWING :HRESULT: -2005270518 //0x887A000A +ERROR_ACCESS_DENIED :: HRESULT(-2005270485) //0x887A002B +ERROR_ACCESS_LOST :: HRESULT(-2005270490) //0x887A0026 +ERROR_ALREADY_EXISTS :: HRESULT(-2005270474) //0x887A0036 +ERROR_CANNOT_PROTECT_CONTENT :: HRESULT(-2005270486) //0x887A002A +ERROR_DEVICE_HUNG :: HRESULT(-2005270522) //0x887A0006 +ERROR_DEVICE_REMOVED :: HRESULT(-2005270523) //0x887A0005 +ERROR_DEVICE_RESET :: HRESULT(-2005270521) //0x887A0007 +ERROR_DRIVER_INTERNAL_ERROR :: HRESULT(-2005270496) //0x887A0020 +ERROR_FRAME_STATISTICS_DISJOINT :: HRESULT(-2005270517) //0x887A000B +ERROR_GRAPHICS_VIDPN_SOURCE_IN_USE :: HRESULT(-2005270516) //0x887A000C +ERROR_INVALID_CALL :: HRESULT(-2005270527) //0x887A0001 +ERROR_MORE_DATA :: HRESULT(-2005270525) //0x887A0003 +ERROR_NAME_ALREADY_EXISTS :: HRESULT(-2005270484) //0x887A002C +ERROR_NONEXCLUSIVE :: HRESULT(-2005270495) //0x887A0021 +ERROR_NOT_CURRENTLY_AVAILABLE :: HRESULT(-2005270494) //0x887A0022 +ERROR_NOT_FOUND :: HRESULT(-2005270526) //0x887A0002 +ERROR_REMOTE_CLIENT_DISCONNECTED :: HRESULT(-2005270493) //0x887A0023 +ERROR_REMOTE_OUTOFMEMORY :: HRESULT(-2005270492) //0x887A0024 +ERROR_RESTRICT_TO_OUTPUT_STALE :: HRESULT(-2005270487) //0x887A0029 +ERROR_SDK_COMPONENT_MISSING :: HRESULT(-2005270483) //0x887A002D +ERROR_SESSION_DISCONNECTED :: HRESULT(-2005270488) //0x887A0028 +ERROR_UNSUPPORTED :: HRESULT(-2005270524) //0x887A0004 +ERROR_WAIT_TIMEOUT :: HRESULT(-2005270489) //0x887A0027 +ERROR_WAS_STILL_DRAWING :: HRESULT(-2005270518) //0x887A000A -STATUS_OCCLUDED :HRESULT: 142213121 //0x087A0001 -STATUS_MODE_CHANGED :HRESULT: 142213127 //0x087A0007 -STATUS_MODE_CHANGE_IN_PROGRESS :HRESULT: 142213128 //0x087A0008 \ No newline at end of file +STATUS_OCCLUDED :: HRESULT( 142213121) //0x087A0001 +STATUS_MODE_CHANGED :: HRESULT( 142213127) //0x087A0007 +STATUS_MODE_CHANGE_IN_PROGRESS :: HRESULT( 142213128) //0x087A0008 \ No newline at end of file From 0f1153fae28d87bd27225e138372ae341925efee Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 12:11:22 +0100 Subject: [PATCH 112/254] Add `page_alloc` and `page_allocator` --- vendor/wasm/js/memory.odin | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 vendor/wasm/js/memory.odin diff --git a/vendor/wasm/js/memory.odin b/vendor/wasm/js/memory.odin new file mode 100644 index 000000000..9cbbd7ce4 --- /dev/null +++ b/vendor/wasm/js/memory.odin @@ -0,0 +1,46 @@ +//+build js wasm32 +package wasm_js_interface + +import "core:mem" +import "core:intrinsics" + +PAGE_SIZE :: 64 * 1024 +page_alloc :: proc(page_count: int) -> (data: []byte, err: mem.Allocator_Error) { + prev_page_count := intrinsics.wasm_memory_grow(0, uintptr(page_count)) + if prev_page_count < 0 { + return nil, .Out_Of_Memory + } + + ptr := ([^]u8)(uintptr(prev_page_count) * PAGE_SIZE) + return ptr[:page_count * PAGE_SIZE], nil +} + +page_allocator :: proc() -> mem.Allocator { + procedure :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, + location := #caller_location) -> ([]byte, mem.Allocator_Error) { + switch mode { + case .Alloc: + assert(size % PAGE_SIZE == 0) + return page_alloc(size/PAGE_SIZE) + case .Resize, .Free, .Free_All: + return nil, .Mode_Not_Implemented + case .Query_Features: + set := (^mem.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc} + } + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return nil, nil + } + + return { + procedure = procedure, + data = nil, + } +} + From 2612f241c9386f125d57474ccaf21935e3f7c2e8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 12:14:12 +0100 Subject: [PATCH 113/254] Minor clean up --- vendor/wasm/js/memory.odin | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vendor/wasm/js/memory.odin b/vendor/wasm/js/memory.odin index 9cbbd7ce4..f6f4f82a4 100644 --- a/vendor/wasm/js/memory.odin +++ b/vendor/wasm/js/memory.odin @@ -24,15 +24,13 @@ page_allocator :: proc() -> mem.Allocator { case .Alloc: assert(size % PAGE_SIZE == 0) return page_alloc(size/PAGE_SIZE) - case .Resize, .Free, .Free_All: + case .Resize, .Free, .Free_All, .Query_Info: return nil, .Mode_Not_Implemented case .Query_Features: set := (^mem.Allocator_Mode_Set)(old_memory) if set != nil { - set^ = {.Alloc} + set^ = {.Alloc, .Query_Features} } - case .Query_Info: - return nil, .Mode_Not_Implemented } return nil, nil From 9eb4cbcbd2e382a48cd612a3a22eb3ecdcff7df6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 12:32:50 +0100 Subject: [PATCH 114/254] Improve ABI design for wasm32 targets --- src/llvm_abi.cpp | 66 ++++++++++++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index c6ff12f95..f4ee46386 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1057,9 +1057,17 @@ namespace lbAbiArm64 { } namespace lbAbiWasm32 { + /* + NOTE(bill): All of this is custom since there is not an "official" + ABI definition for WASM, especially for Odin. + The approach taken optimizes for passing things in multiple + registers/arguments if possible rather than by pointer. + */ Array compute_arg_types(LLVMContextRef c, LLVMTypeRef *arg_types, unsigned arg_count); lbArgType compute_return_type(LLVMContextRef c, LLVMTypeRef return_type, bool return_is_defined); + enum {MAX_DIRECT_STRUCT_SIZE = 32}; + LB_ABI_INFO(abi_info) { lbFunctionType *ft = gb_alloc_item(permanent_allocator(), lbFunctionType); ft->ctx = c; @@ -1087,7 +1095,7 @@ namespace lbAbiWasm32 { return lb_arg_type_direct(type, nullptr, nullptr, attr); } - bool is_struct_valid_elem_type(LLVMTypeRef type) { + bool is_basic_register_type(LLVMTypeRef type) { switch (LLVMGetTypeKind(type)) { case LLVMHalfTypeKind: case LLVMFloatTypeKind: @@ -1099,7 +1107,33 @@ namespace lbAbiWasm32 { } return false; } - + + bool type_can_be_direct(LLVMTypeRef type) { + LLVMTypeKind kind = LLVMGetTypeKind(type); + i64 sz = lb_sizeof(type); + if (sz == 0) { + return false; + } + if (sz <= MAX_DIRECT_STRUCT_SIZE) { + if (kind == LLVMArrayTypeKind) { + if (is_basic_register_type(LLVMGetElementType(type))) { + return true; + } + } else if (kind == LLVMStructTypeKind) { + unsigned count = LLVMCountStructElementTypes(type); + for (unsigned i = 0; i < count; i++) { + LLVMTypeRef elem = LLVMStructGetTypeAtIndex(type, i); + if (!is_basic_register_type(elem)) { + return false; + } + + } + return true; + } + } + return false; + } + lbArgType is_struct(LLVMContextRef c, LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); GB_ASSERT(kind == LLVMArrayTypeKind || kind == LLVMStructTypeKind); @@ -1108,29 +1142,9 @@ namespace lbAbiWasm32 { if (sz == 0) { return lb_arg_type_ignore(type); } - if (sz <= 16) { - if (kind == LLVMArrayTypeKind) { - LLVMTypeRef elem = LLVMGetElementType(type); - if (is_struct_valid_elem_type(elem)) { - return lb_arg_type_direct(type); - } - } else if (kind == LLVMStructTypeKind) { - bool can_be_direct = true; - unsigned count = LLVMCountStructElementTypes(type); - for (unsigned i = 0; i < count; i++) { - LLVMTypeRef elem = LLVMStructGetTypeAtIndex(type, i); - if (!is_struct_valid_elem_type(elem)) { - can_be_direct = false; - break; - } - - } - if (can_be_direct) { - return lb_arg_type_direct(type); - } - } + if (type_can_be_direct(type)) { + return lb_arg_type_direct(type); } - return lb_arg_type_indirect(type, nullptr); } @@ -1154,6 +1168,10 @@ namespace lbAbiWasm32 { if (!return_is_defined) { return lb_arg_type_direct(LLVMVoidTypeInContext(c)); } else if (lb_is_type_kind(return_type, LLVMStructTypeKind) || lb_is_type_kind(return_type, LLVMArrayTypeKind)) { + if (type_can_be_direct(return_type)) { + return lb_arg_type_direct(return_type); + } + i64 sz = lb_sizeof(return_type); switch (sz) { case 1: return lb_arg_type_direct(return_type, LLVMIntTypeInContext(c, 8), nullptr, nullptr); From e48f41165c54913e07d1ab21bebb6a439524cf7c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 12:58:48 +0100 Subject: [PATCH 115/254] Begin work on Atomics for wasm32 (wait and notify intrinsics) --- core/intrinsics/intrinsics.odin | 9 ++++ core/sync/futex_wasm.odin | 36 +++++++++++++ core/sync/primitives_wasm.odin | 8 +++ src/check_builtin.cpp | 93 +++++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 4 ++ src/llvm_backend_proc.cpp | 45 ++++++++++++++++ vendor/wasm/js/runtime.js | 38 ++++++++++---- 7 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 core/sync/futex_wasm.odin create mode 100644 core/sync/primitives_wasm.odin diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 85859e8c3..d71522936 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -190,6 +190,15 @@ constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_size :: proc(index: uintptr) -> int --- +// `timeout_ns` is maximum number of nanoseconds the calling thread will be blocked for +// A negative value will be blocked forever +// Return value: +// 0 - indicates that the thread blocked and then was woken up +// 1 - the loaded value from `ptr` did not match `expected`, the thread did not block +// 2 - the thread blocked, but the timeout +wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 --- +wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) --- + // Darwin targets only objc_object :: struct{} diff --git a/core/sync/futex_wasm.odin b/core/sync/futex_wasm.odin new file mode 100644 index 000000000..9e96614d6 --- /dev/null +++ b/core/sync/futex_wasm.odin @@ -0,0 +1,36 @@ +//+private +//+build wasm32 +package sync + +import "core:intrinsics" +import "core:time" + +_futex_wait :: proc(f: ^Futex, expected: u32) -> bool { + s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, -1) + return s != 0 +} + +_futex_wait_with_timeout :: proc(f: ^Futex, expected: u32, duration: time.Duration) -> bool { + s := intrinsics.wasm_memory_atomic_wait32((^u32)(f), expected, i64(duration)) + return s != 0 + +} + +_futex_signal :: proc(f: ^Futex) { + loop: for { + s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), 1) + if s >= 1 { + return + } + } +} + +_futex_broadcast :: proc(f: ^Futex) { + loop: for { + s := intrinsics.wasm_memory_atomic_notify32((^u32)(f), ~u32(0)) + if s >= 0 { + return + } + } +} + diff --git a/core/sync/primitives_wasm.odin b/core/sync/primitives_wasm.odin new file mode 100644 index 000000000..283971ac5 --- /dev/null +++ b/core/sync/primitives_wasm.odin @@ -0,0 +1,8 @@ +//+private +//+build wasm32 +package sync + +_current_thread_id :: proc "contextless" () -> int { + // TODO(bill): _current_thread_id for wasm32 + return 0 +} \ No newline at end of file diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 9a5d1c554..65983423b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4473,6 +4473,99 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } break; + case BuiltinProc_wasm_memory_atomic_wait32: + { + if (!is_arch_wasm()) { + error(call, "'%.*s' is only allowed on wasm targets", LIT(builtin_name)); + return false; + } + + Operand ptr = {}; + Operand expected = {}; + Operand timeout = {}; + check_expr(c, &ptr, ce->args[0]); if (ptr.mode == Addressing_Invalid) return false; + check_expr(c, &expected, ce->args[1]); if (expected.mode == Addressing_Invalid) return false; + check_expr(c, &timeout, ce->args[2]); if (timeout.mode == Addressing_Invalid) return false; + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + convert_to_typed(c, &ptr, t_u32_ptr); if (ptr.mode == Addressing_Invalid) return false; + convert_to_typed(c, &expected, t_u32); if (expected.mode == Addressing_Invalid) return false; + convert_to_typed(c, &timeout, t_i64); if (timeout.mode == Addressing_Invalid) return false; + + if (!is_operand_value(ptr) || !check_is_assignable_to(c, &ptr, t_u32_ptr)) { + gbString e = expr_to_string(ptr.expr); + gbString t = type_to_string(ptr.type); + error(ptr.expr, "'%.*s' expected ^u32 for the memory pointer, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (!is_operand_value(expected) || !check_is_assignable_to(c, &expected, t_u32)) { + gbString e = expr_to_string(expected.expr); + gbString t = type_to_string(expected.type); + error(expected.expr, "'%.*s' expected u32 for the 'expected' value, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (!is_operand_value(timeout) || !check_is_assignable_to(c, &timeout, t_i64)) { + gbString e = expr_to_string(timeout.expr); + gbString t = type_to_string(timeout.type); + error(timeout.expr, "'%.*s' expected i64 for the timeout, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_u32; + operand->value = {}; + break; + } + break; + case BuiltinProc_wasm_memory_atomic_notify32: + { + if (!is_arch_wasm()) { + error(call, "'%.*s' is only allowed on wasm targets", LIT(builtin_name)); + return false; + } + + Operand ptr = {}; + Operand waiters = {}; + check_expr(c, &ptr, ce->args[0]); if (ptr.mode == Addressing_Invalid) return false; + check_expr(c, &waiters, ce->args[1]); if (waiters.mode == Addressing_Invalid) return false; + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + convert_to_typed(c, &ptr, t_u32_ptr); if (ptr.mode == Addressing_Invalid) return false; + convert_to_typed(c, &waiters, t_u32); if (waiters.mode == Addressing_Invalid) return false; + + if (!is_operand_value(ptr) || !check_is_assignable_to(c, &ptr, t_u32_ptr)) { + gbString e = expr_to_string(ptr.expr); + gbString t = type_to_string(ptr.type); + error(ptr.expr, "'%.*s' expected ^u32 for the memory pointer, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + if (!is_operand_value(waiters) || !check_is_assignable_to(c, &waiters, t_u32)) { + gbString e = expr_to_string(waiters.expr); + gbString t = type_to_string(waiters.type); + error(waiters.expr, "'%.*s' expected u32 for the 'waiters' value, got '%s' of type %s", LIT(builtin_name), e, t); + gb_string_free(t); + gb_string_free(e); + return false; + } + + operand->mode = Addressing_Value; + operand->type = t_u32; + operand->value = {}; + break; + } + break; + } return true; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index d301cae0c..d407ef7c1 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -218,6 +218,8 @@ BuiltinProc__type_end, BuiltinProc_wasm_memory_grow, BuiltinProc_wasm_memory_size, + BuiltinProc_wasm_memory_atomic_wait32, + BuiltinProc_wasm_memory_atomic_notify32, BuiltinProc_COUNT, }; @@ -438,4 +440,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("wasm_memory_grow"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("wasm_memory_size"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("wasm_memory_atomic_wait32"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("wasm_memory_atomic_notify32"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, }; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a7f9eb013..2539e6e2e 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2187,6 +2187,51 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } + case BuiltinProc_wasm_memory_atomic_wait32: + { + char const *name = "llvm.wasm.memory.atomic.wait32"; + LLVMTypeRef types[1] = { + lb_type(p->module, t_u32), + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + + LLVMValueRef args[3] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_u32_ptr).value; + args[1] = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_u32).value; + args[2] = lb_emit_conv(p, lb_build_expr(p, ce->args[2]), t_i64).value; + + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + + case BuiltinProc_wasm_memory_atomic_notify32: + { + char const *name = "llvm.wasm.memory.atomic.notify"; + LLVMTypeRef types[1] = { + lb_type(p->module, t_u32), + }; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + + LLVMValueRef args[2] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_u32_ptr).value; + args[1] = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_u32).value; + + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + } GB_PANIC("Unhandled built-in procedure %.*s", LIT(builtin_procs[id].name)); diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 52fe43b6c..20ed7acfa 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1176,10 +1176,31 @@ class WebGLInterface { function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { const MAX_INFO_CONSOLE_LINES = 512; let infoConsoleLines = new Array(); + let currentLine = ""; + const addConsoleLine = (line) => { if (!line) { return; } + if (!line.includes("\n")) { + currentLine = currentLine.concat(line); + } else { + let printLast = line.endsWith("\n"); + let lines = line.split("\n"); + for (let i = 0; i < lines.length-1; i++) { + let theLine = lines[i].trim("\r"); + currentLine = currentLine.concat(line); + console.log(currentLine); + currentLine = ""; + } + console.log(lines); + if (printLast) { + console.log(lines[lines.length-1]); + } else { + currentLine = currentLine.concat(lines[lines.length-1]); + } + } + if (line.endsWith("\n")) { line = line.substring(0, line.length-1); } else if (infoConsoleLines.length > 0) { @@ -1191,16 +1212,15 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { infoConsoleLines.shift(); } - - let data = ""; - for (let i = 0; i < infoConsoleLines.length; i++) { - if (i != 0) { - data = data.concat("\n"); - } - data = data.concat(infoConsoleLines[i]); - } - if (consoleElement) { + let data = ""; + for (let i = 0; i < infoConsoleLines.length; i++) { + if (i != 0) { + data = data.concat("\n"); + } + data = data.concat(infoConsoleLines[i]); + } + let info = consoleElement; info.innerHTML = data; info.scrollTop = info.scrollHeight; From b57edb89eb936d93bd4859d15c0d83f53566c29c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 13:18:04 +0100 Subject: [PATCH 116/254] Unify abi for wasm32 and the future wasm64 --- src/llvm_abi.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index f4ee46386..60a07e531 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -1056,7 +1056,7 @@ namespace lbAbiArm64 { } } -namespace lbAbiWasm32 { +namespace lbAbiWasm { /* NOTE(bill): All of this is custom since there is not an "official" ABI definition for WASM, especially for Odin. @@ -1312,13 +1312,8 @@ LB_ABI_INFO(lb_get_abi_info) { case TargetArch_arm64: return lbAbiArm64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); case TargetArch_wasm32: - // TODO(bill): implement wasm32's ABI correct - // NOTE(bill): this ABI is only an issue for WASI compatibility - return lbAbiWasm32::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); case TargetArch_wasm64: - // TODO(bill): implement wasm64's ABI correct - // NOTE(bill): this ABI is only an issue for WASI compatibility - return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); + return lbAbiWasm::abi_info(c, arg_types, arg_count, return_type, return_is_defined, calling_convention); } GB_PANIC("Unsupported ABI"); From da54d0ec8cb29203ead93d40c6aa367687a2c546 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 13:18:11 +0100 Subject: [PATCH 117/254] Fix typo --- src/build_settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index e596e54e5..767ce2149 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1076,7 +1076,7 @@ void init_build_context(TargetMetrics *cross_target) { #endif if (bc->disable_red_zone) { - if (!!is_arch_wasm() && bc->metrics.os == TargetOs_freestanding) { + if (is_arch_wasm() && bc->metrics.os == TargetOs_freestanding) { gb_printf_err("-disable-red-zone is not support for this target"); gb_exit(1); } From 3049e07f72f51fd9868066c602dbca922643cfb0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 13:30:09 +0100 Subject: [PATCH 118/254] Add `mem.DEFAULT_PAGE_SIZE` --- core/mem/alloc.odin | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index bdd2899a9..4e439ac39 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -55,6 +55,11 @@ Allocator :: struct { DEFAULT_ALIGNMENT :: 2*align_of(rawptr) +DEFAULT_PAGE_SIZE :: + 64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64 else + 16 * 1024 when ODIN_OS == .darwin && ODIN_ARCH == .arm64 else + 4 * 1024 + alloc :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> rawptr { if size == 0 { return nil From dfbe68bcfe91e1f54eaa47c685d6229f37656f6c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 13:30:43 +0100 Subject: [PATCH 119/254] Begin to add support for experimental wasm64 --- core/sync/futex_wasm.odin | 2 +- core/sync/primitives_wasm.odin | 2 +- src/build_settings.cpp | 12 +++++++++++- vendor/wasm/js/dom.odin | 2 +- vendor/wasm/js/events.odin | 2 +- vendor/wasm/js/memory.odin | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/core/sync/futex_wasm.odin b/core/sync/futex_wasm.odin index 9e96614d6..a32935143 100644 --- a/core/sync/futex_wasm.odin +++ b/core/sync/futex_wasm.odin @@ -1,5 +1,5 @@ //+private -//+build wasm32 +//+build wasm32, wasm64 package sync import "core:intrinsics" diff --git a/core/sync/primitives_wasm.odin b/core/sync/primitives_wasm.odin index 283971ac5..ac36404d9 100644 --- a/core/sync/primitives_wasm.odin +++ b/core/sync/primitives_wasm.odin @@ -1,5 +1,5 @@ //+private -//+build wasm32 +//+build wasm32, wasm64 package sync _current_thread_id :: proc "contextless" () -> int { diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 767ce2149..8bc889635 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -452,6 +452,15 @@ gb_global TargetMetrics target_js_wasm32 = { str_lit(""), }; +gb_global TargetMetrics target_js_wasm64 = { + TargetOs_js, + TargetArch_wasm64, + 8, + 16, + str_lit("wasm64-js-js"), + str_lit(""), +}; + gb_global TargetMetrics target_wasi_wasm32 = { TargetOs_wasi, TargetArch_wasm32, @@ -504,6 +513,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, { str_lit("wasi_wasm32"), &target_wasi_wasm32 }, { str_lit("js_wasm32"), &target_js_wasm32 }, + { str_lit("js_wasm64"), &target_js_wasm64 }, { str_lit("freestanding_amd64_sysv"), &target_freestanding_amd64_sysv }, }; @@ -1151,7 +1161,7 @@ void init_build_context(TargetMetrics *cross_target) { // link_flags = gb_string_appendc(link_flags, "--export-table "); link_flags = gb_string_appendc(link_flags, "--allow-undefined "); if (bc->metrics.arch == TargetArch_wasm64) { - link_flags = gb_string_appendc(link_flags, "-mwas64 "); + link_flags = gb_string_appendc(link_flags, "-mwasm64 "); } if (bc->no_entry_point) { link_flags = gb_string_appendc(link_flags, "--no-entry "); diff --git a/vendor/wasm/js/dom.odin b/vendor/wasm/js/dom.odin index 9f9f2fa96..044476d41 100644 --- a/vendor/wasm/js/dom.odin +++ b/vendor/wasm/js/dom.odin @@ -1,4 +1,4 @@ -//+build js wasm32 +//+build js wasm32, js wasm64 package wasm_js_interface foreign import dom_lib "odin_dom" diff --git a/vendor/wasm/js/events.odin b/vendor/wasm/js/events.odin index 93ea94ede..12a68937f 100644 --- a/vendor/wasm/js/events.odin +++ b/vendor/wasm/js/events.odin @@ -1,4 +1,4 @@ -//+build js wasm32 +//+build js wasm32, js wasm64 package wasm_js_interface foreign import dom_lib "odin_dom" diff --git a/vendor/wasm/js/memory.odin b/vendor/wasm/js/memory.odin index f6f4f82a4..84bb16d01 100644 --- a/vendor/wasm/js/memory.odin +++ b/vendor/wasm/js/memory.odin @@ -1,4 +1,4 @@ -//+build js wasm32 +//+build js wasm32, js wasm64 package wasm_js_interface import "core:mem" From 184d1c57b10260ccb6e8032c34680c5c9c272c43 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 13:37:00 +0100 Subject: [PATCH 120/254] Change atomic.wait32 and atomic.notify selection --- src/llvm_backend_proc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 2539e6e2e..154be2f1f 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2195,7 +2195,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, }; unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); - LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, nullptr, 0); // types, gb_count_of(types)); Type *t_u32_ptr = alloc_type_pointer(t_u32); @@ -2218,7 +2218,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, }; unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); GB_ASSERT_MSG(id != 0, "Unable to find %s", name, LLVMPrintTypeToString(types[0])); - LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, nullptr, 0); // types, gb_count_of(types)); Type *t_u32_ptr = alloc_type_pointer(t_u32); From 10f1d8c6043f679f3ecd167997af01b8627e5e75 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 13:38:38 +0100 Subject: [PATCH 121/254] Fix typo --- core/mem/alloc.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index 4e439ac39..7416ebdb7 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -57,7 +57,7 @@ DEFAULT_ALIGNMENT :: 2*align_of(rawptr) DEFAULT_PAGE_SIZE :: 64 * 1024 when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64 else - 16 * 1024 when ODIN_OS == .darwin && ODIN_ARCH == .arm64 else + 16 * 1024 when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 else 4 * 1024 alloc :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> rawptr { From d84d2f85e82f04fd5eb3e7169fec0a9d9171caf6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 15:34:12 +0100 Subject: [PATCH 122/254] Add WebGL helper (`CreateProgramHelper`) --- vendor/wasm/WebGL/webgl_helpers.odin | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 vendor/wasm/WebGL/webgl_helpers.odin diff --git a/vendor/wasm/WebGL/webgl_helpers.odin b/vendor/wasm/WebGL/webgl_helpers.odin new file mode 100644 index 000000000..d86086783 --- /dev/null +++ b/vendor/wasm/WebGL/webgl_helpers.odin @@ -0,0 +1,49 @@ +package webgl + +import "core:fmt" + +CreateProgramHelper :: proc(vs_sources, fs_sources: []string) -> (program: Program, ok: bool) { + ok = true + log: [1024]byte + + vs := CreateShader(VERTEX_SHADER) + fs := CreateShader(FRAGMENT_SHADER) + defer DeleteShader(vs) + defer DeleteShader(fs) + ShaderSource(vs, vs_sources) + ShaderSource(fs, fs_sources) + CompileShader(vs) + if GetShaderiv(vs, COMPILE_STATUS) == 0 { + err := GetShaderInfoLog(vs, log[:]) + fmt.eprintln("Vertex shader did not compile successfully", err) + ok = false + return + } + + CompileShader(fs) + if GetShaderiv(fs, COMPILE_STATUS) == 0 { + err := GetShaderInfoLog(fs, log[:]) + fmt.eprintln("Fragment shader did not compile successfully", err) + ok = false + return + } + + program = CreateProgram() + defer if !ok do DeleteProgram(program) + + AttachShader(program, vs) + AttachShader(program, fs) + LinkProgram(program) + DetachShader(program, vs) + DetachShader(program, fs) + + if GetProgramParameter(program, LINK_STATUS) == 0 { + err := GetProgramInfoLog(program, log[:]) + fmt.eprintln("Shader program did not link successfully", err) + ok = false + return + } + + return + +} From f3d225ca4f81aadb71bac1690eea6999d3bd49c9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 16:00:36 +0100 Subject: [PATCH 123/254] Improve `addConsoleLine` --- vendor/wasm/js/runtime.js | 62 +++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 20ed7acfa..17650ee5d 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1172,6 +1172,9 @@ class WebGLInterface { }; +function newlineCount(str, substr) { + return (str.match(/\n/g) || []).length; +}; function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { const MAX_INFO_CONSOLE_LINES = 512; @@ -1182,25 +1185,34 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { if (!line) { return; } - if (!line.includes("\n")) { + + // Print to console + if (line == "\n") { + console.log(currentLine); + currentLine = ""; + } else if (!line.includes("\n")) { currentLine = currentLine.concat(line); } else { - let printLast = line.endsWith("\n"); let lines = line.split("\n"); - for (let i = 0; i < lines.length-1; i++) { - let theLine = lines[i].trim("\r"); - currentLine = currentLine.concat(line); - console.log(currentLine); - currentLine = ""; + let printLast = lines.length > 1 && line.endsWith("\n"); + console.log(currentLine.concat(lines[0])); + currentLine = ""; + for (let i = 1; i < lines.length-1; i++) { + console.log(lines[i]); } - console.log(lines); + let last = lines[lines.length-1]; if (printLast) { - console.log(lines[lines.length-1]); + console.log(last); } else { - currentLine = currentLine.concat(lines[lines.length-1]); + currentLine = last; } } + + // HTML based console + if (!consoleElement) { + return; + } if (line.endsWith("\n")) { line = line.substring(0, line.length-1); } else if (infoConsoleLines.length > 0) { @@ -1212,19 +1224,18 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { infoConsoleLines.shift(); } - if (consoleElement) { - let data = ""; - for (let i = 0; i < infoConsoleLines.length; i++) { - if (i != 0) { - data = data.concat("\n"); - } - data = data.concat(infoConsoleLines[i]); - } - let info = consoleElement; - info.innerHTML = data; - info.scrollTop = info.scrollHeight; + let data = ""; + for (let i = 0; i < infoConsoleLines.length; i++) { + if (i != 0) { + data = data.concat("\n"); + } + data = data.concat(infoConsoleLines[i]); } + + let info = consoleElement; + info.innerHTML = data; + info.scrollTop = info.scrollHeight; }; let event_temp_data = {}; @@ -1369,7 +1380,6 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { event_temp_data.id_len = id_len; event_temp_data.event = e; event_temp_data.name_code = name_code; - // console.log(e); wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); }; wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; @@ -1403,7 +1413,6 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { event_temp_data.id_len = 0; event_temp_data.event = e; event_temp_data.name_code = name_code; - // console.log(e); wasmMemoryInterface.exports.odin_dom_do_event_callback(data, callback, odin_ctx); }; wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; @@ -1414,10 +1423,13 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { remove_window_event_listener: (name_ptr, name_len, data, callback) => { let name = wasmMemoryInterface.loadString(name_ptr, name_len); let element = window; - let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; - if (listener == undefined) { + let key = {data: data, callback: callback}; + let listener = wasmMemoryInterface.listenerMap[key]; + if (!listener) { return false; } + wasmMemoryInterface.listenerMap[key] = undefined; + element.removeEventListener(name, listener); return true; }, From b9d523e0b257dbf1087ddeed6d4ce79cee912d40 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 16:11:10 +0100 Subject: [PATCH 124/254] Add color when writing to `stderr` --- vendor/wasm/js/runtime.js | 42 ++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 17650ee5d..907107e07 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1179,32 +1179,50 @@ function newlineCount(str, substr) { function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { const MAX_INFO_CONSOLE_LINES = 512; let infoConsoleLines = new Array(); - let currentLine = ""; + let currentLine = {}; + currentLine[false] = ""; + currentLine[true] = ""; - const addConsoleLine = (line) => { + const addConsoleLine = (line, isError) => { if (!line) { return; } + const println = (text) => { + let style = [ + "color: #eee", + "background-color: #d20", + "padding: 2px 4px", + "border-radius: 2px", + ]; + + if (isError) { + console.log("%c"+text, style.join(";")); + } else { + console.log(text); + } + + }; + // Print to console if (line == "\n") { - console.log(currentLine); - currentLine = ""; + println(currentLine[isError]); + currentLine[isError] = ""; } else if (!line.includes("\n")) { - currentLine = currentLine.concat(line); + currentLine[isError] = currentLine[isError].concat(line); } else { let lines = line.split("\n"); let printLast = lines.length > 1 && line.endsWith("\n"); - console.log(currentLine.concat(lines[0])); - currentLine = ""; + println(currentLine[isError].concat(lines[0])); + currentLine[isError] = ""; for (let i = 1; i < lines.length-1; i++) { - console.log(lines[i]); + println(lines[i]); } let last = lines[lines.length-1]; if (printLast) { - console.log(last); + println(last); } else { - currentLine = last; + currentLine[isError] = last; } } @@ -1247,10 +1265,10 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { write: (fd, ptr, len) => { const str = wasmMemoryInterface.loadString(ptr, len); if (fd == 1) { - addConsoleLine(str); + addConsoleLine(str, false); return; } else if (fd == 2) { - addConsoleLine(str); + addConsoleLine(str, true); return; } else { throw new Error("Invalid fd to 'write'" + stripNewline(str)); From 72fcf16a394c2690df1fe41334fa59c36713f215 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 16:15:20 +0100 Subject: [PATCH 125/254] Rename to `writeToConsole` --- vendor/wasm/js/runtime.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 907107e07..6cec2d0c0 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1183,7 +1183,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { currentLine[false] = ""; currentLine[true] = ""; - const addConsoleLine = (line, isError) => { + const writeToConsole = (line, isError) => { if (!line) { return; } @@ -1265,10 +1265,10 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { write: (fd, ptr, len) => { const str = wasmMemoryInterface.loadString(ptr, len); if (fd == 1) { - addConsoleLine(str, false); + writeToConsole(str, false); return; } else if (fd == 2) { - addConsoleLine(str, true); + writeToConsole(str, true); return; } else { throw new Error("Invalid fd to 'write'" + stripNewline(str)); From 577fa2d29b87565bbecdd8c1d5fadccac79c404a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 16:35:06 +0100 Subject: [PATCH 126/254] Update `time` procedures for js targets --- core/time/time_js.odin | 25 +++++++++++++++++-------- vendor/wasm/js/runtime.js | 10 ++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/core/time/time_js.odin b/core/time/time_js.odin index 9a7163f38..226f921f9 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -2,22 +2,31 @@ //+build js package time -_IS_SUPPORTED :: false +foreign import "odin_env" + +_IS_SUPPORTED :: true _now :: proc "contextless" () -> Time { - return {} + foreign odin_env { + time_now :: proc "contextless" () -> i64 --- + } + return Time{time_now()} } _sleep :: proc "contextless" (d: Duration) { + foreign odin_env { + time_sleep :: proc "contextless" (ms: u32) --- + } + if d > 0 { + time_sleep(u32(d/1e6)) + } } _tick_now :: proc "contextless" () -> Tick { - // mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { - // q := val / den - // r := val % den - // return q * num + r * num / den - // } - return {} + foreign odin_env { + tick_now :: proc "contextless" () -> i64 --- + } + return Tick{tick_now()} } _yield :: proc "contextless" () { diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 6cec2d0c0..f24c85bcd 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1280,8 +1280,18 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); }, time_now: () => { + // convert ms to ns + return Date.now() * 1e6; + }, + time_tick_now: () => { + // convert ms to ns return performance.now() * 1e6; }, + time_sleep: (duration_ms) => { + if (duration_ms > 0) { + // TODO(bill): Does this even make any sense? + } + }, sqrt: (x) => Math.sqrt(x), sin: (x) => Math.sin(x), From 1eef9552b4ecb73af5bbb66c3a7f8ead9b97821f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 16:35:27 +0100 Subject: [PATCH 127/254] Fix typo --- vendor/wasm/js/runtime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index f24c85bcd..83947165c 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1283,7 +1283,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { // convert ms to ns return Date.now() * 1e6; }, - time_tick_now: () => { + tick_now: () => { // convert ms to ns return performance.now() * 1e6; }, From 2895830ce6c0bd8151448b85dc1b779c34925199 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 16:37:12 +0100 Subject: [PATCH 128/254] Add wasm/js/general.odin --- vendor/wasm/js/events.odin | 2 +- vendor/wasm/js/general.odin | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 vendor/wasm/js/general.odin diff --git a/vendor/wasm/js/events.odin b/vendor/wasm/js/events.odin index 12a68937f..e47a2db49 100644 --- a/vendor/wasm/js/events.odin +++ b/vendor/wasm/js/events.odin @@ -243,7 +243,7 @@ Event :: struct { user_data: rawptr, - callback: proc(e: Event), + callback: proc(e: Event), } @(default_calling_convention="contextless") diff --git a/vendor/wasm/js/general.odin b/vendor/wasm/js/general.odin new file mode 100644 index 000000000..0f6a9589c --- /dev/null +++ b/vendor/wasm/js/general.odin @@ -0,0 +1,12 @@ +//+build js wasm32, js wasm64 +package wasm_js_interface + +foreign import "odin_env" + +@(default_calling_convention="contextless") +foreign odin_env { + trap :: proc() -> ! --- + abort :: proc() -> ! --- + alert :: proc(msg: string) --- + evaluate :: proc(str: string) --- +} \ No newline at end of file From c902615192c073db363a6012ffa3185699cc5cdf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 17:03:44 +0100 Subject: [PATCH 129/254] Improve `writeToConole` logic for the `console.log` difference between stdout and stderr --- vendor/wasm/js/runtime.js | 72 +++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 83947165c..412223eab 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1,3 +1,5 @@ +"use strict"; + (function() { class WasmMemoryInterface { constructor() { @@ -1172,32 +1174,33 @@ class WebGLInterface { }; -function newlineCount(str, substr) { - return (str.match(/\n/g) || []).length; -}; - function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { const MAX_INFO_CONSOLE_LINES = 512; let infoConsoleLines = new Array(); let currentLine = {}; currentLine[false] = ""; currentLine[true] = ""; + let prevIsError = false; const writeToConsole = (line, isError) => { if (!line) { return; } - const println = (text) => { + const println = (text, forceIsError) => { let style = [ "color: #eee", "background-color: #d20", "padding: 2px 4px", "border-radius: 2px", - ]; + ].join(";"); + let doIsError = isError; + if (forceIsError !== undefined) { + doIsError = forceIsError; + } - if (isError) { - console.log("%c"+text, style.join(";")); + if (doIsError) { + console.log("%c"+text, style); } else { console.log(text); } @@ -1226,28 +1229,61 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { } } + if (prevIsError != isError) { + if (prevIsError) { + println(currentLine[prevIsError], prevIsError); + currentLine[prevIsError] = ""; + } + } + prevIsError = isError; + // HTML based console if (!consoleElement) { return; } - if (line.endsWith("\n")) { - line = line.substring(0, line.length-1); - } else if (infoConsoleLines.length > 0) { - let prev_line = infoConsoleLines.pop(); - line = prev_line.concat(line); + const wrap = (x) => { + if (isError) { + return ''+x+''; + } + return x; + }; + + if (line == "\n") { + infoConsoleLines.push(line); + } else if (!line.includes("\n")) { + let prevLine = ""; + if (infoConsoleLines.length > 0) { + prevLine = infoConsoleLines.pop(); + } + infoConsoleLines.push(prevLine.concat(wrap(line))); + } else { + let lines = line.split("\n"); + let lastHasNewline = lines.length > 1 && line.endsWith("\n"); + + let prevLine = ""; + if (infoConsoleLines.length > 0) { + prevLine = infoConsoleLines.pop(); + } + infoConsoleLines.push(prevLine.concat(wrap(lines[0]).concat("\n"))); + + for (let i = 1; i < lines.length-1; i++) { + infoConsoleLines.push(wrap(lines[i]).concat("\n")); + } + let last = lines[lines.length-1]; + if (lastHasNewline) { + infoConsoleLines.push(last.concat("\n")); + } else { + infoConsoleLines.push(last); + } } - infoConsoleLines.push(line); if (infoConsoleLines.length > MAX_INFO_CONSOLE_LINES) { - infoConsoleLines.shift(); + infoConsoleLines.shift(MAX_INFO_CONSOLE_LINES); } let data = ""; for (let i = 0; i < infoConsoleLines.length; i++) { - if (i != 0) { - data = data.concat("\n"); - } data = data.concat(infoConsoleLines[i]); } From d7681d5b06324461e137c218195989d8645c1edf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 17:24:03 +0100 Subject: [PATCH 130/254] Add utilities for `Rect`s --- vendor/wasm/js/dom.odin | 40 +++++++++++++++++++++++++++++++++++++-- vendor/wasm/js/runtime.js | 21 ++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/vendor/wasm/js/dom.odin b/vendor/wasm/js/dom.odin index 044476d41..e4a55e1ea 100644 --- a/vendor/wasm/js/dom.odin +++ b/vendor/wasm/js/dom.odin @@ -6,11 +6,10 @@ foreign import dom_lib "odin_dom" @(default_calling_convention="contextless") foreign dom_lib { get_element_value_f64 :: proc(id: string) -> f64 --- - get_element_min_max :: proc(id: string) -> (min, max: f64) --- set_element_value :: proc(id: string, value: f64) --- } -get_element_value_string :: proc(id: string, buf: []byte) -> string { +get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string { @(default_calling_convention="contextless") foreign dom_lib { @(link_name="get_element_value_string") @@ -20,3 +19,40 @@ get_element_value_string :: proc(id: string, buf: []byte) -> string { return string(buf[:n]) } + + +get_element_min_max :: proc "contextless" (id: string) -> (min, max: f64) { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_element_min_max") + _get_element_min_max :: proc(min_max: ^[2]f64, id: string) --- + } + min_max: [2]f64 + _get_element_min_max(&min_max, id) + return min_max[0], min_max[1] +} + + +Rect :: struct { + x, y, width, height: f64, +} + +get_bounding_client_rect :: proc "contextless" (id: string) -> (rect: Rect) { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_bounding_client_rect") + _get_bounding_client_rect :: proc(rect: ^Rect, id: string) --- + } + _get_bounding_client_rect(&rect, id) + return +} + +get_window_rect :: proc "contextless" () -> (rect: Rect) { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="get_window_rect") + _get_window_rect :: proc(rect: ^Rect) --- + } + _get_window_rect(&rect) + return +} \ No newline at end of file diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 412223eab..f8d28ba96 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1549,6 +1549,27 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { element.value = value; } }, + + get_bounding_client_rect: (rect_ptr, id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + if (element) { + let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4); + let rect = element.getBoundingClientRect(); + values[0] = rect.left; + values[1] = rect.top; + values[2] = rect.right - rect.left; + values[3] = rect.bottom - rect.top; + } + }, + get_window_rect: (rect_ptr) => { + let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4); + values[0] = window.screenX; + values[1] = window.screenY; + values[2] = window.screen.width; + values[3] = window.screen.height; + }, + }, "webgl": webglContext.getWebGL1Interface(), From c2c66aad6066b5d11c94f24965127075ee5cb9c4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 17:29:19 +0100 Subject: [PATCH 131/254] Add `Context_Menu` event kind; Fix `event_prevent_default()` --- vendor/wasm/js/events.odin | 4 ++++ vendor/wasm/js/runtime.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/vendor/wasm/js/events.odin b/vendor/wasm/js/events.odin index e47a2db49..dd9524adb 100644 --- a/vendor/wasm/js/events.odin +++ b/vendor/wasm/js/events.odin @@ -77,6 +77,8 @@ Event_Kind :: enum u32 { Transition_Run, Transition_Cancel, + Context_Menu, + } event_kind_string := [Event_Kind]string{ .Invalid = "", @@ -151,6 +153,8 @@ event_kind_string := [Event_Kind]string{ .Touch_End = "touchend", .Touch_Move = "touchmove", .Touch_Start = "touchstart", + + .Context_Menu = "contextmenu", } Delta_Mode :: enum u32 { diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index f8d28ba96..1d0e869a2 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1510,7 +1510,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }, event_prevent_default: () => { if (event_temp_data && event_temp_data.event) { - event_temp_data.event.eventPreventDefault(); + event_temp_data.event.preventDefault(); } }, From 43b350c5905d5c65372231b42f146034a4450e53 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 21:31:05 +0100 Subject: [PATCH 132/254] Add more procedures for window related positions --- vendor/wasm/js/dom.odin | 30 ++++++++++++++++++++++++------ vendor/wasm/js/runtime.js | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 8 deletions(-) diff --git a/vendor/wasm/js/dom.odin b/vendor/wasm/js/dom.odin index e4a55e1ea..d650dd70a 100644 --- a/vendor/wasm/js/dom.odin +++ b/vendor/wasm/js/dom.odin @@ -6,7 +6,14 @@ foreign import dom_lib "odin_dom" @(default_calling_convention="contextless") foreign dom_lib { get_element_value_f64 :: proc(id: string) -> f64 --- - set_element_value :: proc(id: string, value: f64) --- + set_element_value_f64 :: proc(id: string, value: f64) --- + + set_element_value_string :: proc(id: string, value: string) --- + get_element_value_string_length :: proc(id: string) -> int --- + + device_pixel_ratio :: proc() -> f64 --- + + window_set_scroll :: proc(x, y: f64) --- } get_element_value_string :: proc "contextless" (id: string, buf: []byte) -> string { @@ -47,12 +54,23 @@ get_bounding_client_rect :: proc "contextless" (id: string) -> (rect: Rect) { return } -get_window_rect :: proc "contextless" () -> (rect: Rect) { +window_get_rect :: proc "contextless" () -> (rect: Rect) { @(default_calling_convention="contextless") foreign dom_lib { - @(link_name="get_window_rect") - _get_window_rect :: proc(rect: ^Rect) --- + @(link_name="window_get_rect") + _window_get_rect :: proc(rect: ^Rect) --- } - _get_window_rect(&rect) + _window_get_rect(&rect) return -} \ No newline at end of file +} + +window_get_scroll :: proc "contextless" () -> (x, y: f64) { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="window_get_scroll") + _window_get_scroll :: proc(scroll: ^[2]f64) --- + } + scroll := [2]f64{x, y} + _window_get_scroll(&scroll) + return +} diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 1d0e869a2..424a9a4db 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1533,6 +1533,14 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { } return 0; }, + get_element_value_string_length: (id_ptr, id_len) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let element = document.getElementById(id); + if (element) { + return element.value.length; + } + return 0; + }, get_element_min_max: (ptr_array2_f64, id_ptr, id_len) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); let element = document.getElementById(id); @@ -1542,13 +1550,22 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { values[1] = element.max; } }, - set_element_value: (id_ptr, id_len, value) => { + set_element_value_f64: (id_ptr, id_len, value) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); let element = document.getElementById(id); if (element) { element.value = value; } }, + set_element_value_string: (id_ptr, id_len, value_ptr, value_id) => { + let id = wasmMemoryInterface.loadString(id_ptr, id_len); + let value = wasmMemoryInterface.loadString(value_ptr, value_len); + let element = document.getElementById(id); + if (element) { + element.value = value; + } + }, + get_bounding_client_rect: (rect_ptr, id_ptr, id_len) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); @@ -1562,7 +1579,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { values[3] = rect.bottom - rect.top; } }, - get_window_rect: (rect_ptr) => { + window_get_rect: (rect_ptr) => { let values = wasmMemoryInterface.loadF64Array(rect_ptr, 4); values[0] = window.screenX; values[1] = window.screenY; @@ -1570,6 +1587,19 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { values[3] = window.screen.height; }, + window_get_scroll: (pos_ptr) => { + let values = wasmMemoryInterface.loadF64Array(pos_ptr, 2); + values[0] = window.scrollX; + values[1] = window.scrollY; + }, + window_set_scroll: (x, y) => { + window.scroll(x, y); + }, + + device_pixel_ratio: () => { + return window.devicePixelRatio; + }, + }, "webgl": webglContext.getWebGL1Interface(), From f3432e6bb57f2619cea0a0c738bdd5bd9a3dca74 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 21 May 2022 21:31:24 +0100 Subject: [PATCH 133/254] Rename to `CreateProgramFromStrings` --- vendor/wasm/WebGL/webgl_helpers.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/wasm/WebGL/webgl_helpers.odin b/vendor/wasm/WebGL/webgl_helpers.odin index d86086783..585706fbc 100644 --- a/vendor/wasm/WebGL/webgl_helpers.odin +++ b/vendor/wasm/WebGL/webgl_helpers.odin @@ -2,7 +2,7 @@ package webgl import "core:fmt" -CreateProgramHelper :: proc(vs_sources, fs_sources: []string) -> (program: Program, ok: bool) { +CreateProgramFromStrings :: proc(vs_sources, fs_sources: []string) -> (program: Program, ok: bool) { ok = true log: [1024]byte From f25a3f2a7d01d5ffcea80b7e4a77d9095ff22667 Mon Sep 17 00:00:00 2001 From: Cedric Hutchings Date: Sun, 22 May 2022 15:34:49 -0400 Subject: [PATCH 134/254] Add enums for getting Scancodes from WM_KEYDOWN https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input --- core/sys/windows/key_codes.odin | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/sys/windows/key_codes.odin b/core/sys/windows/key_codes.odin index 2f6e01116..284b0e437 100644 --- a/core/sys/windows/key_codes.odin +++ b/core/sys/windows/key_codes.odin @@ -1,6 +1,14 @@ // +build windows package sys_windows +// https://docs.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input +KF_EXTENDED :: 0x0100 +KF_DLGMODE :: 0x0800 +KF_MENUMODE :: 0x1000 +KF_ALTDOWN :: 0x2000 +KF_REPEAT :: 0x4000 +KF_UP :: 0x8000 + // https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes // Virtual Keys, Standard Set VK_LBUTTON :: 0x01 From 438713af2037511208b52545fd43a6155c017b34 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 23 May 2022 11:33:52 +0100 Subject: [PATCH 135/254] Allow `transmute` on constant expressions --- src/check_expr.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index f578f8c73..bc28583b7 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2786,14 +2786,14 @@ bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type *t) { return false; } - if (o->mode == Addressing_Constant) { - gbString expr_str = expr_to_string(o->expr); - error(o->expr, "Cannot transmute a constant expression: '%s'", expr_str); - gb_string_free(expr_str); - o->mode = Addressing_Invalid; - o->expr = node; - return false; - } + // if (o->mode == Addressing_Constant) { + // gbString expr_str = expr_to_string(o->expr); + // error(o->expr, "Cannot transmute a constant expression: '%s'", expr_str); + // gb_string_free(expr_str); + // o->mode = Addressing_Invalid; + // o->expr = node; + // return false; + // } if (is_type_untyped(o->type)) { gbString expr_str = expr_to_string(o->expr); From eba35a8f7df577296d06e3ca922f2f55bcff372c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 23 May 2022 11:46:44 +0100 Subject: [PATCH 136/254] Allow multi pointers in intrinsics --- src/check_builtin.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 65983423b..55dd6b016 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3082,13 +3082,13 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } - if (!is_type_pointer(dst.type)) { + if (!is_type_pointer(dst.type) && !is_type_multi_pointer(dst.type)) { gbString str = type_to_string(dst.type); error(dst.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); return false; } - if (!is_type_pointer(src.type)) { + if (!is_type_pointer(src.type) && !is_type_multi_pointer(src.type)) { gbString str = type_to_string(src.type); error(src.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); @@ -3130,7 +3130,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } - if (!is_type_pointer(ptr.type)) { + if (!is_type_pointer(ptr.type) && !is_type_multi_pointer(ptr.type)) { gbString str = type_to_string(ptr.type); error(ptr.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); @@ -3174,7 +3174,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 operand->mode = Addressing_Value; operand->type = ptr.type; - if (!is_type_pointer(ptr.type)) { + if (!is_type_pointer(ptr.type) && !is_type_multi_pointer(ptr.type)) { gbString str = type_to_string(ptr.type); error(ptr.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); @@ -3217,7 +3217,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 operand->mode = Addressing_Value; operand->type = t_int; - if (!is_type_pointer(ptr0.type)) { + if (!is_type_pointer(ptr0.type) && !is_type_multi_pointer(ptr0.type)) { gbString str = type_to_string(ptr0.type); error(ptr0.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); @@ -3230,7 +3230,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } - if (!is_type_pointer(ptr1.type)) { + if (!is_type_pointer(ptr1.type) && !is_type_multi_pointer(ptr1.type)) { gbString str = type_to_string(ptr1.type); error(ptr1.expr, "Expected a pointer value for '%.*s', got %s", LIT(builtin_name), str); gb_string_free(str); From 7d2eedee739dbd785eaa476ff840d2c3fb4cb394 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 23 May 2022 11:47:12 +0100 Subject: [PATCH 137/254] Unify `raw_data` in `core:mem` with `core:runtime` --- core/mem/raw.odin | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/core/mem/raw.odin b/core/mem/raw.odin index 9eef4f6e3..0a0780dfd 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -20,20 +20,11 @@ make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} } -raw_array_data :: proc "contextless" (a: $P/^($T/[$N]$E)) -> ^E { - return (^E)(a) -} -raw_string_data :: proc "contextless" (s: $T/string) -> ^byte { - return (transmute(Raw_String)s).data -} -raw_slice_data :: proc "contextless" (a: $T/[]$E) -> ^E { - return cast(^E)(transmute(Raw_Slice)a).data -} -raw_dynamic_array_data :: proc "contextless" (a: $T/[dynamic]$E) -> ^E { - return cast(^E)(transmute(Raw_Dynamic_Array)a).data -} - -raw_data :: proc{raw_array_data, raw_string_data, raw_slice_data, raw_dynamic_array_data} +raw_array_data :: runtime.raw_array_data +raw_string_data :: runtime.raw_string_data +raw_slice_data :: runtime.raw_slice_data +raw_dynamic_array_data :: runtime.raw_dynamic_array_data +raw_data :: runtime.raw_data Poly_Raw_Map_Entry :: struct($Key, $Value: typeid) { From 8c1499dbc2a7d32c7dba12cd9d3c93c51411d373 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 23 May 2022 11:48:05 +0100 Subject: [PATCH 138/254] Make `raw_data` return `[^]T` types --- core/hash/crc32.odin | 10 +++++----- core/hash/hash.odin | 6 +++--- core/runtime/core_builtin.odin | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/hash/crc32.odin b/core/hash/crc32.odin index ccb304472..fead4d74f 100644 --- a/core/hash/crc32.odin +++ b/core/hash/crc32.odin @@ -9,8 +9,8 @@ crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check { length := len(data) for length != 0 && uintptr(buffer) & 7 != 0 { - crc = crc32_table[0][byte(crc) ~ buffer^] ~ (crc >> 8) - buffer = intrinsics.ptr_offset(buffer, 1) + crc = crc32_table[0][byte(crc) ~ buffer[0]] ~ (crc >> 8) + buffer = buffer[1:] length -= 1 } @@ -28,14 +28,14 @@ crc32 :: proc(data: []byte, seed := u32(0)) -> u32 #no_bounds_check { crc32_table[1][buf[6]] ~ crc32_table[0][buf[7]] - buffer = intrinsics.ptr_offset(buffer, 8) + buffer = buffer[8:] length -= 8 } for length != 0 { - crc = crc32_table[0][byte(crc) ~ buffer^] ~ (crc >> 8) - buffer = intrinsics.ptr_offset(buffer, 1) + crc = crc32_table[0][byte(crc) ~ buffer[0]] ~ (crc >> 8) + buffer = buffer[1:] length -= 1 } diff --git a/core/hash/hash.odin b/core/hash/hash.odin index f2152f1b6..63708a096 100644 --- a/core/hash/hash.odin +++ b/core/hash/hash.odin @@ -15,7 +15,7 @@ adler32 :: proc(data: []byte, seed := u32(1)) -> u32 #no_bounds_check { for len(buf) != 0 && uintptr(buffer) & 7 != 0 { a = (a + u64(buf[0])) b = (b + a) - buffer = intrinsics.ptr_offset(buffer, 1) + buffer = buffer[1:] buf = buf[1:] } @@ -130,9 +130,9 @@ murmur32 :: proc(data: []byte, seed := u32(0)) -> u32 { h1: u32 = seed nblocks := len(data)/4 p := raw_data(data) - p1 := mem.ptr_offset(p, 4*nblocks) + p1 := p[4*nblocks:] - for ; p < p1; p = mem.ptr_offset(p, 4) { + for ; p < p1; p = p[4:] { k1 := (cast(^u32)p)^ k1 *= c1_32 diff --git a/core/runtime/core_builtin.odin b/core/runtime/core_builtin.odin index 43b9ee1bf..cb72e397d 100644 --- a/core/runtime/core_builtin.odin +++ b/core/runtime/core_builtin.odin @@ -600,21 +600,21 @@ card :: proc(s: $S/bit_set[$E; $U]) -> int { @builtin -raw_array_data :: proc "contextless" (a: $P/^($T/[$N]$E)) -> ^E { - return (^E)(a) +raw_array_data :: proc "contextless" (a: $P/^($T/[$N]$E)) -> [^]E { + return ([^]E)(a) } @builtin -raw_slice_data :: proc "contextless" (s: $S/[]$E) -> ^E { +raw_slice_data :: proc "contextless" (s: $S/[]$E) -> [^]E { ptr := (transmute(Raw_Slice)s).data - return (^E)(ptr) + return ([^]E)(ptr) } @builtin -raw_dynamic_array_data :: proc "contextless" (s: $S/[dynamic]$E) -> ^E { +raw_dynamic_array_data :: proc "contextless" (s: $S/[dynamic]$E) -> [^]E { ptr := (transmute(Raw_Dynamic_Array)s).data - return (^E)(ptr) + return ([^]E)(ptr) } @builtin -raw_string_data :: proc "contextless" (s: $S/string) -> ^u8 { +raw_string_data :: proc "contextless" (s: $S/string) -> [^]u8 { return (transmute(Raw_String)s).data } From d9f293b2818b30ebe2d29180aa62c3ce432c4582 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 23 May 2022 11:50:05 +0100 Subject: [PATCH 139/254] Add better error message for trying to dereference a multi-pointer --- src/check_expr.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index bc28583b7..013d22913 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -9310,7 +9310,15 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type } else { gbString str = expr_to_string(o->expr); gbString typ = type_to_string(o->type); + begin_error_block(); + error(o->expr, "Cannot dereference '%s' of type '%s'", str, typ); + if (o->type && is_type_multi_pointer(o->type)) { + error_line("\tDid you mean '%s[0]'?\n", str); + } + + end_error_block(); + gb_string_free(typ); gb_string_free(str); o->mode = Addressing_Invalid; From 3ec70c5517062f3d35822253b2072df696b0c55f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 23 May 2022 12:04:19 +0100 Subject: [PATCH 140/254] Merge functionality of `#maybe` with the standard 'union' functionality --- core/encoding/json/unmarshal.odin | 2 +- core/reflect/reflect.odin | 2 +- core/reflect/types.odin | 16 +++++++++++++--- core/runtime/core.odin | 1 - core/runtime/core_builtin.odin | 2 +- src/check_expr.cpp | 1 - src/docs_writer.cpp | 1 - src/llvm_backend_type.cpp | 5 ++--- src/parser.cpp | 1 + src/parser.hpp | 2 +- src/types.cpp | 19 +++++++------------ 11 files changed, 27 insertions(+), 25 deletions(-) diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index bd48011f1..2ff268a21 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -209,7 +209,7 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) { variant := u.variants[0] v.id = variant.id ti = reflect.type_info_base(variant) - if !(u.maybe && reflect.is_pointer(variant)) { + if !reflect.is_pointer_internally(variant) { tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id} assign_int(tag, 1) } diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index 49d7ef9b5..27a83e680 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -654,7 +654,7 @@ union_variant_type_info :: proc(a: any) -> ^Type_Info { } type_info_union_is_pure_maybe :: proc(info: runtime.Type_Info_Union) -> bool { - return info.maybe && len(info.variants) == 1 && is_pointer(info.variants[0]) + return len(info.variants) == 1 && is_pointer(info.variants[0]) } union_variant_typeid :: proc(a: any) -> typeid { diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 2e2149820..b211abb45 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -256,6 +256,17 @@ is_multi_pointer :: proc(info: ^Type_Info) -> bool { _, ok := type_info_base(info).variant.(Type_Info_Multi_Pointer) return ok } +is_pointer_internally :: proc(info: ^Type_Info) -> bool { + if info == nil { return false } + #partial switch v in info.variant { + case Type_Info_Pointer, Type_Info_Multi_Pointer, + Type_Info_Procedure: + return true + case Type_Info_String: + return v.is_cstring + } + return false +} is_procedure :: proc(info: ^Type_Info) -> bool { if info == nil { return false } _, ok := type_info_base(info).variant.(Type_Info_Procedure) @@ -531,9 +542,8 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - case Type_Info_Union: io.write_string(w, "union ", &n) or_return - if info.maybe { - io.write_string(w, "#maybe ", &n) or_return - } + if info.no_nil { io.write_string(w, "#no_nil ", &n) or_return } + if info.shared_nil { io.write_string(w, "#shared_nil ", &n) or_return } if info.custom_align { io.write_string(w, "#align ", &n) or_return io.write_i64(w, i64(ti.align), 10, &n) or_return diff --git a/core/runtime/core.odin b/core/runtime/core.odin index e2933d20a..73d1e6371 100644 --- a/core/runtime/core.odin +++ b/core/runtime/core.odin @@ -135,7 +135,6 @@ Type_Info_Union :: struct { custom_align: bool, no_nil: bool, - maybe: bool, shared_nil: bool, } Type_Info_Enum :: struct { diff --git a/core/runtime/core_builtin.odin b/core/runtime/core_builtin.odin index cb72e397d..4ddc3928a 100644 --- a/core/runtime/core_builtin.odin +++ b/core/runtime/core_builtin.odin @@ -3,7 +3,7 @@ package runtime import "core:intrinsics" @builtin -Maybe :: union($T: typeid) #maybe {T} +Maybe :: union($T: typeid) {T} @builtin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 013d22913..b911771b1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -10063,7 +10063,6 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) { str = gb_string_appendc(str, ") "); } switch (st->kind) { - case UnionType_maybe: str = gb_string_appendc(str, "#maybe "); break; case UnionType_no_nil: str = gb_string_appendc(str, "#no_nil "); break; case UnionType_shared_nil: str = gb_string_appendc(str, "#shared_nil "); break; } diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 0ad10ac49..2f531a45c 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -620,7 +620,6 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { doc_type.kind = OdinDocType_Union; if (type->Union.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Union_polymorphic; } switch (type->Union.kind) { - case UnionType_maybe: doc_type.flags |= OdinDocTypeFlag_Union_maybe; break; case UnionType_no_nil: doc_type.flags |= OdinDocTypeFlag_Union_no_nil; break; case UnionType_shared_nil: doc_type.flags |= OdinDocTypeFlag_Union_shared_nil; break; } diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 7d73956e8..2e7b2788a 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -641,7 +641,7 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da tag = lb_const_ptr_cast(m, variant_ptr, t_type_info_union_ptr); { - LLVMValueRef vals[8] = {}; + LLVMValueRef vals[7] = {}; isize variant_count = gb_max(0, t->Union.variants.count); lbValue memory_types = lb_type_info_member_types_offset(p, variant_count); @@ -676,8 +676,7 @@ void lb_setup_type_info_data(lbProcedure *p) { // NOTE(bill): Setup type_info da vals[4] = lb_const_bool(m, t_bool, t->Union.custom_align != 0).value; vals[5] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_no_nil).value; - vals[6] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_maybe).value; - vals[7] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_shared_nil).value; + vals[6] = lb_const_bool(m, t_bool, t->Union.kind == UnionType_shared_nil).value; for (isize i = 0; i < gb_count_of(vals); i++) { if (vals[i] == nullptr) { diff --git a/src/parser.cpp b/src/parser.cpp index ab947774b..d19e249e5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2529,6 +2529,7 @@ Ast *parse_operand(AstFile *f, bool lhs) { if (maybe) { union_kind = UnionType_maybe; + syntax_error(f->curr_token, "#maybe functionality has now been merged with standard 'union' functionality"); } else if (no_nil) { union_kind = UnionType_no_nil; } else if (shared_nil) { diff --git a/src/parser.hpp b/src/parser.hpp index 698ed7623..dc294b6ce 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -332,7 +332,7 @@ char const *inline_asm_dialect_strings[InlineAsmDialect_COUNT] = { enum UnionTypeKind : u8 { UnionType_Normal = 0, - UnionType_maybe = 1, + UnionType_maybe = 1, // removed UnionType_no_nil = 2, UnionType_shared_nil = 3, }; diff --git a/src/types.cpp b/src/types.cpp index b4dc17256..c79b8e652 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1685,11 +1685,9 @@ bool is_type_map(Type *t) { bool is_type_union_maybe_pointer(Type *t) { t = base_type(t); - if (t->kind == Type_Union && t->Union.kind == UnionType_maybe) { - if (t->Union.variants.count == 1) { - Type *v = t->Union.variants[0]; - return is_type_pointer(v) || is_type_multi_pointer(v); - } + if (t->kind == Type_Union && t->Union.variants.count == 1) { + Type *v = t->Union.variants[0]; + return is_type_internally_pointer_like(v); } return false; } @@ -1697,12 +1695,10 @@ bool is_type_union_maybe_pointer(Type *t) { bool is_type_union_maybe_pointer_original_alignment(Type *t) { t = base_type(t); - if (t->kind == Type_Union && t->Union.kind == UnionType_maybe) { - if (t->Union.variants.count == 1) { - Type *v = t->Union.variants[0]; - if (is_type_pointer(v) || is_type_multi_pointer(v)) { - return type_align_of(v) == type_align_of(t); - } + if (t->kind == Type_Union && t->Union.variants.count == 1) { + Type *v = t->Union.variants[0]; + if (is_type_internally_pointer_like(v)) { + return type_align_of(v) == type_align_of(t); } } return false; @@ -4054,7 +4050,6 @@ gbString write_type_to_string(gbString str, Type *type, bool shorthand=false) { case Type_Union: str = gb_string_appendc(str, "union"); switch (type->Union.kind) { - case UnionType_maybe: str = gb_string_appendc(str, " #maybe"); break; case UnionType_no_nil: str = gb_string_appendc(str, " #no_nil"); break; case UnionType_shared_nil: str = gb_string_appendc(str, " #shared_nil"); break; } From 7002f0a7d74b50f594e2324f5f96639b71ff1041 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 23 May 2022 12:07:44 +0100 Subject: [PATCH 141/254] Update demo.odin --- examples/demo/demo.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/demo/demo.odin b/examples/demo/demo.odin index c50a5bdf8..457aa786a 100644 --- a/examples/demo/demo.odin +++ b/examples/demo/demo.odin @@ -1977,15 +1977,17 @@ constant_literal_expressions :: proc() { } union_maybe :: proc() { - fmt.println("\n#union #maybe") + fmt.println("\n#union based maybe") // NOTE: This is already built-in, and this is just a reimplementation to explain the behaviour - Maybe :: union($T: typeid) #maybe {T} + Maybe :: union($T: typeid) {T} i: Maybe(u8) p: Maybe(^u8) // No tag is stored for pointers, nil is the sentinel value + // Tag size will be as small as needed for the number of variants #assert(size_of(i) == size_of(u8) + size_of(u8)) + // No need to store a tag here, the `nil` state is shared with the variant's `nil` #assert(size_of(p) == size_of(^u8)) i = 123 From 084f431aa510793a6e2011816efebbc71a24c0ff Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 23 May 2022 12:19:33 +0100 Subject: [PATCH 142/254] Correct `check_transmute` operand logic --- src/check_expr.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 013d22913..c674a774e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2844,8 +2844,10 @@ bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type *t) { } } + o->expr = node; o->mode = Addressing_Value; o->type = t; + o->value = {}; return true; } From 3d9d85121d207b41e24214c4acc52fc50271127f Mon Sep 17 00:00:00 2001 From: Cedric Hutchings Date: Mon, 23 May 2022 08:14:05 -0400 Subject: [PATCH 143/254] Clear up Mismatched BE types error message --- src/check_expr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2bb79e21c..7b269e048 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3211,7 +3211,7 @@ void check_binary_expr(CheckerContext *c, Operand *x, Ast *node, Type *type_hint y->type != t_invalid) { gbString xt = type_to_string(x->type); gbString yt = type_to_string(y->type); - gbString expr_str = expr_to_string(x->expr); + gbString expr_str = expr_to_string(node); error(op, "Mismatched types in binary expression '%s' : '%s' vs '%s'", expr_str, xt, yt); gb_string_free(expr_str); gb_string_free(yt); From 54a326f04601e93475c74b5a862d0269c70933b6 Mon Sep 17 00:00:00 2001 From: William Roe Date: Mon, 23 May 2022 13:09:37 +0100 Subject: [PATCH 144/254] [os] Darwin,FreeBSD,OpenBSD: Rename os.getenv to os.get_env Make os.get_env consistent across Unixes This matches the function name and API from env_windows.odin and os_linux.odin, which should be the same everywhere. Meaning: * named get_env and not getenv * return a string (empty if the environment variable is not found) * accept a default value parameter for the allocator (defaulting to context.allocator) * calls lookup_env which returns an extra found boolean value This is so that you don't have to write platform/OS conditionals when getting environment variable values from the stdlib os.get_env/getenv function. --- core/os/os_darwin.odin | 11 ++++++++--- core/os/os_freebsd.odin | 11 ++++++++--- core/os/os_openbsd.odin | 11 ++++++++--- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index c36823e3f..41dc8345c 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -633,13 +633,18 @@ heap_free :: proc(ptr: rawptr) { _unix_free(ptr) } -getenv :: proc(name: string) -> (string, bool) { - path_str := strings.clone_to_cstring(name, context.temp_allocator) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + path_str := strings.clone_to_cstring(key, context.temp_allocator) cstr := _unix_getenv(path_str) if cstr == nil { return "", false } - return string(cstr), true + return strings.clone(string(cstr), allocator), true +} + +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return } get_current_directory :: proc() -> string { diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 6545423d4..a991caafc 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -618,13 +618,18 @@ heap_free :: proc(ptr: rawptr) { _unix_free(ptr) } -getenv :: proc(name: string) -> (string, bool) { - path_str := strings.clone_to_cstring(name, context.temp_allocator) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + path_str := strings.clone_to_cstring(key, context.temp_allocator) cstr := _unix_getenv(path_str) if cstr == nil { return "", false } - return string(cstr), true + return strings.clone(string(cstr), allocator), true +} + +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return } get_current_directory :: proc() -> string { diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index dd230f9b5..9a3dbd874 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -620,13 +620,18 @@ heap_free :: proc(ptr: rawptr) { _unix_free(ptr) } -getenv :: proc(name: string) -> (string, bool) { - path_str := strings.clone_to_cstring(name, context.temp_allocator) +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + path_str := strings.clone_to_cstring(key, context.temp_allocator) cstr := _unix_getenv(path_str) if cstr == nil { return "", false } - return string(cstr), true + return strings.clone(string(cstr), allocator), true +} + +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return } get_current_directory :: proc() -> string { From 3c5124ce68110b780f9cd5aaa3801f1f200b98fe Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 24 May 2022 13:55:39 +0200 Subject: [PATCH 145/254] Fix `odin build examples\demo\` trailing slash handling. --- src/build_settings.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 8bc889635..b458d8308 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1338,7 +1338,12 @@ bool init_build_paths(String init_filename) { } else { // Init filename was not 'current path'. // Contruct the output name from the path elements as usual. - String output_name = remove_directory_from_path(init_filename); + String output_name = init_filename; + // If it ends with a trailing (back)slash, strip it before continuing. + while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) { + output_name.len -= 1; + } + output_name = remove_directory_from_path(output_name); output_name = remove_extension_from_path(output_name); output_name = copy_string(ha, string_trim_whitespace(output_name)); output_path = path_from_string(ha, output_name); From 233b32fd3ea4bb50ad1cc41e819bcdca5c3bf4d4 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 25 May 2022 00:47:29 +0200 Subject: [PATCH 146/254] Correct return value. --- vendor/stb/truetype/stb_truetype.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/stb/truetype/stb_truetype.odin b/vendor/stb/truetype/stb_truetype.odin index b51cb037f..e0c03268e 100644 --- a/vendor/stb/truetype/stb_truetype.odin +++ b/vendor/stb/truetype/stb_truetype.odin @@ -226,7 +226,7 @@ foreign stbtt { // (.ttf) files only contain one font. The number of fonts can be used for // indexing with the previous function where the index is between zero and one // less than the total fonts. If an error occurs, -1 is returned. - GetNumberOfFonts :: proc(data: [^]byte) -> b32 --- + GetNumberOfFonts :: proc(data: [^]byte) -> c.int --- // Each .ttf/.ttc file may have more than one font. Each font has a sequential // index number starting from 0. Call this function to get the font offset for From 831a86599ecd6d5ddf3f691fc94cf9c0effda50b Mon Sep 17 00:00:00 2001 From: WalterPlinge <22519813+WalterPlinge@users.noreply.github.com> Date: Wed, 25 May 2022 02:00:13 +0100 Subject: [PATCH 147/254] Add fallback build paths search using environment variables --- src/build_settings.cpp | 219 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 215 insertions(+), 4 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index b458d8308..4d560bc00 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1186,7 +1186,201 @@ void init_build_context(TargetMetrics *cross_target) { // NOTE(IC): In order to find Visual C++ paths without relying on environment variables. // NOTE(Jeroen): No longer needed in `main.cpp -> linker_stage`. We now resolve those paths in `init_build_paths`. #include "microsoft_craziness.h" -#endif + +// NOTE(WalterPlinge): Environment variables can help to find Visual C++ and WinSDK paths for both +// official and portable installations (like mmozeiko's portable msvc script). This will only use +// the first paths it finds, and won't overwrite any values that `result` already has. +bool find_portable_msvc_installation(gbAllocator allocator, Find_Result_Utf8 *result) { + if (build_context.metrics.arch != TargetArch_amd64 && build_context.metrics.arch != TargetArch_i386) { + return false; + } + + bool sdk_found = false; + if(result->windows_sdk_root.len > 0 + && result->windows_sdk_um_library_path.len > 0 + && result->windows_sdk_ucrt_library_path.len > 0) { + sdk_found = true; + } + + // We can find windows sdk using the following combination of env vars: + // (UniversalCRTSdkDir or WindowsSdkDir) and (WindowsSDKLibVersion or WindowsSDKVersion) + if (!sdk_found) { + // These appear to be suitable env vars used by Visual Studio + char const *win_sdk_ver_env = gb_get_env("WindowsSDKVersion", allocator); + char const *win_sdk_lib_env = gb_get_env("WindowsSDKLibVersion", allocator); + char const *win_sdk_dir_env = gb_get_env("WindowsSdkDir", allocator); + char const *crt_sdk_dir_env = gb_get_env("UniversalCRTSdkDir", allocator); + defer (gb_free(allocator, (void*)win_sdk_ver_env)); + defer (gb_free(allocator, (void*)win_sdk_lib_env)); + defer (gb_free(allocator, (void*)win_sdk_dir_env)); + defer (gb_free(allocator, (void*)crt_sdk_dir_env)); + + // NOTE(WalterPlinge): If any combination is found, let's just assume they are correct + if ((win_sdk_ver_env || win_sdk_lib_env) && (win_sdk_dir_env || crt_sdk_dir_env)) { + //? Maybe we need to handle missing '\' at end of strings, so far it doesn't seem an issue + String dir = win_sdk_dir_env + ? make_string_c(win_sdk_dir_env) + : make_string_c(crt_sdk_dir_env); + String ver = win_sdk_ver_env + ? make_string_c(win_sdk_ver_env) + : make_string_c(win_sdk_lib_env); + + // These have trailing '\' as we are just composing the path + String um_dir = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("um\\x64\\") + : make_string_c("um\\x86\\"); + String ucrt_dir = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("ucrt\\x64\\") + : make_string_c("ucrt\\x86\\"); + + result->windows_sdk_root = concatenate3_strings(allocator, dir, make_string_c("Lib\\"), ver); + result->windows_sdk_um_library_path = concatenate_strings(allocator, result->windows_sdk_root, um_dir); + result->windows_sdk_ucrt_library_path = concatenate_strings(allocator, result->windows_sdk_root, ucrt_dir); + + sdk_found = true; + } + } + + // If we haven't found it yet, we can loop through LIB for specific folders + //? This may not be robust enough using `um\x64` and `ucrt\x64` + if (!sdk_found) { + char const *lib_env = gb_get_env("LIB", allocator); + defer (gb_free(allocator, (void*)lib_env)); + if (lib_env) { + String lib = make_string_c(lib_env); + + // NOTE(WalterPlinge): I don't know if there's a chance for the LIB variable + // to be set without a trailing '\' (apart from manually), so we can just + // check paths without it (see use of `String end` in the loop below) + String um_dir = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("um\\x64") + : make_string_c("um\\x86"); + String ucrt_dir = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("ucrt\\x64") + : make_string_c("ucrt\\x86"); + + isize lo = {0}; + isize hi = {0}; + for (isize c = 0; c <= lib.len; c += 1) { + if (c != lib.len && lib[c] != ';') { + continue; + } + hi = c; + String dir = substring(lib, lo, hi); + defer (lo = hi + 1); + + // Remove the last slash so we can match with the strings above + String end = dir[dir.len - 1] == '\\' + ? substring(dir, 0, dir.len - 1) + : substring(dir, 0, dir.len); + + // Find one and we can make the other + if (string_ends_with(end, um_dir)) { + result->windows_sdk_um_library_path = concatenate_strings(allocator, end, make_string_c("\\")); + break; + } else if (string_ends_with(end, ucrt_dir)) { + result->windows_sdk_ucrt_library_path = concatenate_strings(allocator, end, make_string_c("\\")); + break; + } + } + + // Get the root from the one we found, and make the other + if (result->windows_sdk_um_library_path.len > 0) { + result->windows_sdk_root = substring(result->windows_sdk_um_library_path, 0, result->windows_sdk_um_library_path.len - 1 - um_dir.len); + result->windows_sdk_ucrt_library_path = concatenate3_strings(allocator, result->windows_sdk_root, ucrt_dir, make_string_c("\\")); + } else if (result->windows_sdk_ucrt_library_path.len > 0) { + result->windows_sdk_root = substring(result->windows_sdk_ucrt_library_path, 0, result->windows_sdk_ucrt_library_path.len - 1 - ucrt_dir.len); + result->windows_sdk_um_library_path = concatenate3_strings(allocator, result->windows_sdk_root, um_dir, make_string_c("\\")); + } + + if (result->windows_sdk_root.len > 0) { + sdk_found = true; + } + } + } + + // NOTE(WalterPlinge): So far this function assumes it will only be called if MSVC was + // installed using mmozeiko's portable msvc script, which uses the windows 10 sdk. + // This may need to be changed later if it ends up causing problems. + if (sdk_found && result->windows_sdk_version == 0) { + result->windows_sdk_version = 10; + } + + bool vs_found = false; + if (result->vs_exe_path.len > 0 && result->vs_library_path.len > 0) { + vs_found = true; + } + + // We can find visual studio using VCToolsInstallDir + if (!vs_found) { + char const *vctid_env = gb_get_env("VCToolsInstallDir", allocator); + defer (gb_free(allocator, (void*)vctid_env)); + if (vctid_env) { + String vctid = make_string_c(vctid_env); + String exe = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("bin\\Hostx64\\x64\\") + : make_string_c("bin\\Hostx86\\x86\\"); + String lib = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("lib\\x64\\") + : make_string_c("lib\\x86\\"); + result->vs_exe_path = concatenate_strings(allocator, vctid, exe); + result->vs_library_path = concatenate_strings(allocator, vctid, lib); + vs_found = true; + } + } + + // If we haven't found it yet, we can loop through Path for specific folders + if (!vs_found) { + char const *path_env = gb_get_env("Path", allocator); + defer (gb_free(allocator, (void*)path_env)); + if (path_env) { + String path = make_string_c(path_env); + + String exe = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("bin\\Hostx64\\x64") + : make_string_c("bin\\Hostx86\\x86"); + String lib = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("lib\\x64") + : make_string_c("lib\\x86"); + + isize lo = {0}; + isize hi = {0}; + for (isize c = 0; c <= path.len; c += 1) { + if (c != path.len && path[c] != ';') { + continue; + } + + hi = c; + String dir = substring(path, lo, hi); + defer (lo = hi + 1); + + String end = dir[dir.len - 1] == '\\' + ? substring(dir, 0, dir.len - 1) + : substring(dir, 0, dir.len); + + // check if cl.exe and link.exe exist in this folder + String cl = concatenate_strings(allocator, end, make_string_c("\\cl.exe")); + String link = concatenate_strings(allocator, end, make_string_c("\\link.exe")); + defer (gb_free(allocator, cl.text)); + defer (gb_free(allocator, link.text)); + + if (!string_ends_with(end, exe) || !gb_file_exists((char *)cl.text) || !gb_file_exists((char *)link.text)) { + continue; + } + + String root = substring(end, 0, end.len - exe.len); + + result->vs_exe_path = concatenate_strings(allocator, end, make_string_c("\\")); + result->vs_library_path = concatenate3_strings(allocator, root, lib, make_string_c("\\")); + + vs_found = true; + } + } + } + + return sdk_found && vs_found; +} +#endif defined(GB_SYSTEM_WINDOWS) // NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate. // We've previously called `parse_build_flags`, so `out_filepath` should be set. @@ -1227,11 +1421,28 @@ bool init_build_paths(String init_filename) { if ((bc->command_kind & Command__does_build) && (!bc->ignore_microsoft_magic)) { // NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest. Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); + bool all_found = + find_result.windows_sdk_root.len > 0 && + find_result.windows_sdk_um_library_path.len > 0 && + find_result.windows_sdk_ucrt_library_path.len > 0 && + find_result.vs_exe_path.len > 0 && + find_result.vs_library_path.len > 0; - if (find_result.windows_sdk_version == 0) { - gb_printf_err("Windows SDK not found.\n"); - return false; + if (find_result.windows_sdk_version == 0 || !all_found) { + if (!find_portable_msvc_installation(ha, &find_result)) { + gb_printf_err("Windows SDK not found.\n"); + return false; + } } +#if 0 + printf("windows_sdk_root: %.*s\n", LIT(find_result.windows_sdk_root)); + printf("windows_sdk_um_library_path: %.*s\n", LIT(find_result.windows_sdk_um_library_path)); + printf("windows_sdk_ucrt_library_path: %.*s\n", LIT(find_result.windows_sdk_ucrt_library_path)); + printf("vs_exe_path: %.*s\n", LIT(find_result.vs_exe_path)); + printf("vs_library_path: %.*s\n", LIT(find_result.vs_library_path)); + + gb_exit(1); +#endif if (find_result.windows_sdk_um_library_path.len > 0) { GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); From 8fcf2f5dca8cfcb8eb114bb9efbd6582ed0a2f9a Mon Sep 17 00:00:00 2001 From: WalterPlinge <22519813+WalterPlinge@users.noreply.github.com> Date: Wed, 25 May 2022 02:10:34 +0100 Subject: [PATCH 148/254] a little cleanup --- src/build_settings.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 4d560bc00..19de16f93 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1190,7 +1190,7 @@ void init_build_context(TargetMetrics *cross_target) { // NOTE(WalterPlinge): Environment variables can help to find Visual C++ and WinSDK paths for both // official and portable installations (like mmozeiko's portable msvc script). This will only use // the first paths it finds, and won't overwrite any values that `result` already has. -bool find_portable_msvc_installation(gbAllocator allocator, Find_Result_Utf8 *result) { +bool find_msvc_install_from_env_vars(gbAllocator allocator, Find_Result_Utf8 *result) { if (build_context.metrics.arch != TargetArch_amd64 && build_context.metrics.arch != TargetArch_i386) { return false; } @@ -1380,7 +1380,7 @@ bool find_portable_msvc_installation(gbAllocator allocator, Find_Result_Utf8 *re return sdk_found && vs_found; } -#endif defined(GB_SYSTEM_WINDOWS) +#endif // NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate. // We've previously called `parse_build_flags`, so `out_filepath` should be set. @@ -1429,20 +1429,11 @@ bool init_build_paths(String init_filename) { find_result.vs_library_path.len > 0; if (find_result.windows_sdk_version == 0 || !all_found) { - if (!find_portable_msvc_installation(ha, &find_result)) { + if (!find_msvc_install_from_env_vars(ha, &find_result)) { gb_printf_err("Windows SDK not found.\n"); return false; } } -#if 0 - printf("windows_sdk_root: %.*s\n", LIT(find_result.windows_sdk_root)); - printf("windows_sdk_um_library_path: %.*s\n", LIT(find_result.windows_sdk_um_library_path)); - printf("windows_sdk_ucrt_library_path: %.*s\n", LIT(find_result.windows_sdk_ucrt_library_path)); - printf("vs_exe_path: %.*s\n", LIT(find_result.vs_exe_path)); - printf("vs_library_path: %.*s\n", LIT(find_result.vs_library_path)); - - gb_exit(1); -#endif if (find_result.windows_sdk_um_library_path.len > 0) { GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); From acadbe050cd4337e3eb0f8febd31df94f5270bd2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 11:43:56 +0100 Subject: [PATCH 149/254] Make `core:dynlib` use the private interface convention of other packages --- core/dynlib/lib.odin | 12 ++++++++++++ core/dynlib/lib_unix.odin | 29 +++++++++++++++-------------- core/dynlib/lib_windows.odin | 9 +++++---- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index 00655d650..35617792c 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -1,3 +1,15 @@ package dynlib Library :: distinct rawptr + +load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { + return _load_library(path, global_symbols) +} + +unload_library :: proc(library: Library) -> bool { + return _unload_library(library) +} + +symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { + return _symbol_address(library, symbol) +} diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index e52ade153..7fdcccd6b 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -1,23 +1,24 @@ -// +build linux, darwin, freebsd, openbsd +//+build linux, darwin, freebsd, openbsd +//+private package dynlib import "core:os" -load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { - flags := os.RTLD_NOW - if global_symbols { - flags |= os.RTLD_GLOBAL - } - lib := os.dlopen(path, flags) - return Library(lib), lib != nil +_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { + flags := os.RTLD_NOW + if global_symbols { + flags |= os.RTLD_GLOBAL + } + lib := os.dlopen(path, flags) + return Library(lib), lib != nil } -unload_library :: proc(library: Library) { - os.dlclose(rawptr(library)) +_unload_library :: proc(library: Library) { + os.dlclose(rawptr(library)) } -symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { - ptr = os.dlsym(rawptr(library), symbol) - found = ptr != nil - return +_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { + ptr = os.dlsym(rawptr(library), symbol) + found = ptr != nil + return } diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index b9aae9cf1..d48e43ca2 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -1,10 +1,11 @@ -// +build windows +//+build windows +//+private package dynlib import win32 "core:sys/windows" import "core:strings" -load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { +_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { // NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL wide_path := win32.utf8_to_wstring(path, context.temp_allocator) @@ -12,12 +13,12 @@ load_library :: proc(path: string, global_symbols := false) -> (Library, bool) { return handle, handle != nil } -unload_library :: proc(library: Library) -> bool { +_unload_library :: proc(library: Library) -> bool { ok := win32.FreeLibrary(cast(win32.HMODULE)library) return bool(ok) } -symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { +_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { c_str := strings.clone_to_cstring(symbol, context.temp_allocator) ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str) found = ptr != nil From 95d4ce4aa3db60a46a667268cf91d0ea0d411f7e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 11:46:26 +0100 Subject: [PATCH 150/254] Fix lib_unix.odin --- core/dynlib/lib_unix.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/dynlib/lib_unix.odin b/core/dynlib/lib_unix.odin index 7fdcccd6b..b0cc37e99 100644 --- a/core/dynlib/lib_unix.odin +++ b/core/dynlib/lib_unix.odin @@ -13,8 +13,8 @@ _load_library :: proc(path: string, global_symbols := false) -> (Library, bool) return Library(lib), lib != nil } -_unload_library :: proc(library: Library) { - os.dlclose(rawptr(library)) +_unload_library :: proc(library: Library) -> bool { + return os.dlclose(rawptr(library)) } _symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { From d8e77cd738844b172d1741b1b1d3d4376efd17b5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 11:53:32 +0100 Subject: [PATCH 151/254] Add `#optional_ok` to `dynlib.symbol_address` --- core/dynlib/lib.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index 35617792c..a6c857ee4 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -10,6 +10,6 @@ unload_library :: proc(library: Library) -> bool { return _unload_library(library) } -symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) { +symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok { return _symbol_address(library, symbol) } From 209a1556083e80141da0e4e2142af104d9de3a27 Mon Sep 17 00:00:00 2001 From: WalterPlinge <22519813+WalterPlinge@users.noreply.github.com> Date: Wed, 25 May 2022 14:51:37 +0100 Subject: [PATCH 152/254] fix a double free bug --- src/build_settings.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 19de16f93..f9c417ce2 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1285,11 +1285,14 @@ bool find_msvc_install_from_env_vars(gbAllocator allocator, Find_Result_Utf8 *re } // Get the root from the one we found, and make the other + // NOTE(WalterPlinge): we need to copy the string so that we don't risk a double free if (result->windows_sdk_um_library_path.len > 0) { - result->windows_sdk_root = substring(result->windows_sdk_um_library_path, 0, result->windows_sdk_um_library_path.len - 1 - um_dir.len); + String root = substring(result->windows_sdk_um_library_path, 0, result->windows_sdk_um_library_path.len - 1 - um_dir.len); + result->windows_sdk_root = copy_string(allocator, root); result->windows_sdk_ucrt_library_path = concatenate3_strings(allocator, result->windows_sdk_root, ucrt_dir, make_string_c("\\")); } else if (result->windows_sdk_ucrt_library_path.len > 0) { - result->windows_sdk_root = substring(result->windows_sdk_ucrt_library_path, 0, result->windows_sdk_ucrt_library_path.len - 1 - ucrt_dir.len); + String root = substring(result->windows_sdk_ucrt_library_path, 0, result->windows_sdk_ucrt_library_path.len - 1 - ucrt_dir.len); + result->windows_sdk_root = copy_string(allocator, root); result->windows_sdk_um_library_path = concatenate3_strings(allocator, result->windows_sdk_root, um_dir, make_string_c("\\")); } From b032d5af87ebe8d9dee28698cfa570d3628e58e5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 17:26:18 +0100 Subject: [PATCH 153/254] Make `#simd` an opaque type --- src/check_decl.cpp | 20 +++++++++++++------- src/check_expr.cpp | 24 ++++++++---------------- src/check_type.cpp | 5 +++++ src/common.cpp | 7 +++++++ src/types.cpp | 2 +- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 82ac6c677..d8cad2ce1 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -313,13 +313,19 @@ void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr, Type *def) } named->Named.base = base; - if (is_distinct && is_type_typeid(e->type)) { - error(init_expr, "'distinct' cannot be applied to 'typeid'"); - is_distinct = false; - } - if (is_distinct && is_type_any(e->type)) { - error(init_expr, "'distinct' cannot be applied to 'any'"); - is_distinct = false; + if (is_distinct) { + if (is_type_typeid(e->type)) { + error(init_expr, "'distinct' cannot be applied to 'typeid'"); + is_distinct = false; + } else if (is_type_any(e->type)) { + error(init_expr, "'distinct' cannot be applied to 'any'"); + is_distinct = false; + } else if (is_type_simd_vector(e->type)) { + gbString str = type_to_string(e->type); + error(init_expr, "'distinct' cannot be applied to '%s'", str); + gb_string_free(str); + is_distinct = false; + } } if (!is_distinct) { e->type = bt; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 7b269e048..a4dfade98 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1567,9 +1567,16 @@ bool check_unary_op(CheckerContext *c, Operand *o, Token op) { bool check_binary_op(CheckerContext *c, Operand *o, Token op) { Type *main_type = o->type; + + if (is_type_simd_vector(main_type)) { + error(op, "Operator '%.*s' is not supported on #simd vector types, please use the intrinsics.simd_*", LIT(op.string)); + return false; + } + // TODO(bill): Handle errors correctly Type *type = base_type(core_array_type(main_type)); Type *ct = core_type(type); + switch (op.kind) { case Token_Sub: case Token_SubEq: @@ -1638,14 +1645,6 @@ bool check_binary_op(CheckerContext *c, Operand *o, Token op) { error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string)); return false; } - if (is_type_simd_vector(o->type)) { - switch (op.kind) { - case Token_ModMod: - case Token_ModModEq: - error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string)); - return false; - } - } break; case Token_AndNot: @@ -1654,14 +1653,6 @@ bool check_binary_op(CheckerContext *c, Operand *o, Token op) { error(op, "Operator '%.*s' is only allowed with integers and bit sets", LIT(op.string)); return false; } - if (is_type_simd_vector(o->type)) { - switch (op.kind) { - case Token_AndNot: - case Token_AndNotEq: - error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string)); - return false; - } - } break; case Token_CmpAnd: @@ -7738,6 +7729,7 @@ ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type * } if (cl->elems.count > 0 && cl->elems[0]->kind == Ast_FieldValue) { + // TODO(bill): Why was this decision made for simd? if (is_type_simd_vector(t)) { error(cl->elems[0], "'field = value' is not allowed for SIMD vector literals"); } else { diff --git a/src/check_type.cpp b/src/check_type.cpp index 51f472961..193c42cde 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2802,6 +2802,11 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t *type = alloc_type_array(elem, count, generic_type); goto array_end; } + if (count < 1 || !is_power_of_two(count)) { + error(at->elem, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); + *type = alloc_type_array(elem, count, generic_type); + goto array_end; + } *type = alloc_type_simd_vector(count, elem); } else { diff --git a/src/common.cpp b/src/common.cpp index 94248fb62..77caddfe8 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -47,6 +47,13 @@ void debugf(char const *fmt, ...); #include "range_cache.cpp" +bool is_power_of_two(i64 x) { + if (x <= 0) { + return false; + } + return !(x & (x-1)); +} + int isize_cmp(isize x, isize y) { if (x < y) { return -1; diff --git a/src/types.cpp b/src/types.cpp index c79b8e652..d5ba1a531 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3446,7 +3446,7 @@ i64 type_align_of_internal(Type *t, TypePath *path) { case Type_SimdVector: { // IMPORTANT TODO(bill): Figure out the alignment of vector types - return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align); + return gb_clamp(next_pow2(type_size_of_internal(t, path)), 1, build_context.max_align*2); } case Type_Matrix: From 3b54015e80316af8c13fd83f615b64b611508275 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 17:54:05 +0100 Subject: [PATCH 154/254] Mock out simd intrinsics --- src/check_builtin.cpp | 209 +++++++++++++++++++++++++++++++++- src/check_type.cpp | 2 +- src/checker_builtin_procs.hpp | 57 +++++++++- src/types.cpp | 2 +- 4 files changed, 265 insertions(+), 5 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 55dd6b016..939892707 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -246,7 +246,7 @@ bool is_constant_string(CheckerContext *c, String const &builtin_name, Ast *expr } bool check_builtin_objc_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) { - String builtin_name = builtin_procs[id].name; + String const &builtin_name = builtin_procs[id].name; if (build_context.metrics.os != TargetOs_darwin) { // allow on doc generation (e.g. Metal stuff) @@ -409,6 +409,194 @@ bool check_atomic_memory_order_argument(CheckerContext *c, Ast *expr, String con } + +bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) { + ast_node(ce, CallExpr, call); + + String const &builtin_name = builtin_procs[id].name; + switch (id) { + // Any numeric + case BuiltinProc_simd_add: + case BuiltinProc_simd_sub: + case BuiltinProc_simd_mul: + case BuiltinProc_simd_div: + case BuiltinProc_simd_min: + case BuiltinProc_simd_max: + { + Operand x = {}; + Operand y = {}; + check_expr(c, &x, ce->args[0]); + check_expr(c, &y, ce->args[1]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (y.mode == Addressing_Invalid) { + return false; + } + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!is_type_simd_vector(y.type)) { + error(y.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!are_types_identical(x.type, y.type)) { + gbString xs = type_to_string(x.type); + gbString ys = type_to_string(y.type); + error(x.expr, "'%.*s' expected 2 arguments of the same type, got '%s' vs '%s'", LIT(builtin_name), xs, ys); + gb_string_free(ys); + gb_string_free(xs); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem) && !is_type_float(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + + // Integer only + case BuiltinProc_simd_rem: + case BuiltinProc_simd_shl: + case BuiltinProc_simd_shr: + case BuiltinProc_simd_shl_masked: + case BuiltinProc_simd_shr_masked: + case BuiltinProc_simd_and: + case BuiltinProc_simd_or: + case BuiltinProc_simd_xor: + { + Operand x = {}; + Operand y = {}; + check_expr(c, &x, ce->args[0]); + check_expr(c, &y, ce->args[1]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (y.mode == Addressing_Invalid) { + return false; + } + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!is_type_simd_vector(y.type)) { + error(y.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!are_types_identical(x.type, y.type)) { + gbString xs = type_to_string(x.type); + gbString ys = type_to_string(y.type); + error(x.expr, "'%.*s' expected 2 arguments of the same type, got '%s' vs '%s'", LIT(builtin_name), xs, ys); + gb_string_free(ys); + gb_string_free(xs); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + // Unary + case BuiltinProc_simd_neg: + case BuiltinProc_simd_abs: + { + Operand x = {}; + check_expr(c, &x, ce->args[0]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem) && !is_type_float(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + + // Return integer masks + case BuiltinProc_simd_eq: + case BuiltinProc_simd_ne: + case BuiltinProc_simd_lt: + case BuiltinProc_simd_le: + case BuiltinProc_simd_gt: + case BuiltinProc_simd_ge: + { + // op(#simd[N]T, #simd[N]T) -> #simd[N]V + // where `V` is an integer, `size_of(T) == size_of(V)` + // `V` will all 0s if false and all 1s if true (e.g. 0x00 and 0xff for false and true, respectively) + + Operand x = {}; + Operand y = {}; + check_expr(c, &x, ce->args[0]); + check_expr(c, &y, ce->args[1]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (y.mode == Addressing_Invalid) { + return false; + } + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem) && !is_type_float(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + Type *vt = base_type(x.type); + GB_ASSERT(vt->kind == Type_SimdVector); + i64 count = vt->SimdVector.count; + + i64 sz = type_size_of(elem); + Type *new_elem = nullptr; + + switch (sz) { + case 1: new_elem = t_u8; break; + case 2: new_elem = t_u16; break; + case 4: new_elem = t_u32; break; + case 8: new_elem = t_u64; break; + case 16: + error(x.expr, "'%.*s' not supported 128-bit integer backed simd vector types", LIT(builtin_name)); + return false; + } + + operand->mode = Addressing_Value; + operand->type = alloc_type_simd_vector(count, new_elem); + return true; + } + default: + GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); + } + + return false; +} + + bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 id, Type *type_hint) { ast_node(ce, CallExpr, call); if (ce->inlining != ProcInlining_none) { @@ -479,7 +667,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } - String builtin_name = builtin_procs[id].name; + String const &builtin_name = builtin_procs[id].name; if (ce->args.count > 0) { @@ -491,6 +679,16 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } } + if (BuiltinProc__simd_begin < id && id < BuiltinProc__simd_end) { + bool ok = check_builtin_simd_operation(c, operand, call, id, type_hint); + if (!ok) { + operand->type = t_invalid; + } + operand->mode = Addressing_Value; + operand->value = {}; + return ok; + } + switch (id) { default: GB_PANIC("Implement built-in procedure: %.*s", LIT(builtin_name)); @@ -2720,6 +2918,13 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } + if (count < 1 || !is_power_of_two(count)) { + error(call, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); + operand->mode = Addressing_Type; + operand->type = t_invalid; + return false; + } + operand->mode = Addressing_Type; operand->type = alloc_type_simd_vector(count, elem); break; diff --git a/src/check_type.cpp b/src/check_type.cpp index 193c42cde..1df63e599 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2803,7 +2803,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t goto array_end; } if (count < 1 || !is_power_of_two(count)) { - error(at->elem, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); + error(at->count, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); *type = alloc_type_array(elem, count, generic_type); goto array_end; } diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index d407ef7c1..80467ffb1 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -118,6 +118,35 @@ enum BuiltinProcId { BuiltinProc_fixed_point_div_sat, BuiltinProc_expect, + +BuiltinProc__simd_begin, + BuiltinProc_simd_add, + BuiltinProc_simd_sub, + BuiltinProc_simd_mul, + BuiltinProc_simd_div, + BuiltinProc_simd_rem, + BuiltinProc_simd_shl, // Odin logic + BuiltinProc_simd_shr, // Odin logic + BuiltinProc_simd_shl_masked, // C logic + BuiltinProc_simd_shr_masked, // C logic + + BuiltinProc_simd_and, + BuiltinProc_simd_or, + BuiltinProc_simd_xor, + + BuiltinProc_simd_neg, + BuiltinProc_simd_abs, + + BuiltinProc_simd_min, + BuiltinProc_simd_max, + + BuiltinProc_simd_eq, + BuiltinProc_simd_ne, + BuiltinProc_simd_lt, + BuiltinProc_simd_le, + BuiltinProc_simd_gt, + BuiltinProc_simd_ge, +BuiltinProc__simd_end, // Platform specific intrinsics BuiltinProc_syscall, @@ -342,7 +371,33 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("fixed_point_div_sat"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("expect"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - + + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_div"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_rem"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_shl"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_shr"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_shl_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_shr_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_neg"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_abs"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_min"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_max"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_eq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_ne"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lt"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_le"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_gt"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_ge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + + {STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/types.cpp b/src/types.cpp index d5ba1a531..755f78f1c 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1932,7 +1932,7 @@ bool is_type_valid_vector_elem(Type *t) { return false; } if (is_type_integer(t)) { - return true; + return !is_type_integer_128bit(t); } if (is_type_float(t)) { return true; From 81dd727f750aad45c6468de64a12119a3141de55 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 18:49:17 +0100 Subject: [PATCH 155/254] Implement backend for simd intrinsics --- src/check_builtin.cpp | 58 ++++++++++- src/llvm_backend_proc.cpp | 201 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 4 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 939892707..13eb9f47d 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -464,10 +464,6 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call // Integer only case BuiltinProc_simd_rem: - case BuiltinProc_simd_shl: - case BuiltinProc_simd_shr: - case BuiltinProc_simd_shl_masked: - case BuiltinProc_simd_shr_masked: case BuiltinProc_simd_and: case BuiltinProc_simd_or: case BuiltinProc_simd_xor: @@ -510,6 +506,60 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call operand->type = x.type; return true; } + + case BuiltinProc_simd_shl: + case BuiltinProc_simd_shr: + case BuiltinProc_simd_shl_masked: + case BuiltinProc_simd_shr_masked: + { + Operand x = {}; + Operand y = {}; + check_expr(c, &x, ce->args[0]); + check_expr(c, &y, ce->args[1]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (y.mode == Addressing_Invalid) { + return false; + } + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!is_type_simd_vector(y.type)) { + error(y.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + GB_ASSERT(x.type->kind == Type_SimdVector); + GB_ASSERT(y.type->kind == Type_SimdVector); + Type *xt = x.type; + Type *yt = y.type; + + if (xt->SimdVector.count != yt->SimdVector.count) { + error(x.expr, "'%.*s' mismatched simd vector lengths, got '%lld' vs '%lld'", + LIT(builtin_name), + cast(long long)xt->SimdVector.count, + cast(long long)yt->SimdVector.count); + return false; + } + if (!is_type_integer(base_array_type(x.type))) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + if (!is_type_unsigned(base_array_type(y.type))) { + gbString ys = type_to_string(y.type); + error(y.expr, "'%.*s' expected a #simd type with an unsigned integer element as the shifting operand, got '%s'", LIT(builtin_name), ys); + gb_string_free(ys); + return false; + } + + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + // Unary case BuiltinProc_simd_neg: case BuiltinProc_simd_abs: diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 154be2f1f..82ad6daef 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -981,10 +981,211 @@ lbValue lb_emit_call(lbProcedure *p, lbValue value, Array const &args, return result; } +lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, BuiltinProcId id) { + ast_node(ce, CallExpr, expr); + + lbModule *m = p->module; + + lbValue res = {}; + res.type = tv.type; + + lbValue arg0 = lb_build_expr(p, ce->args[0]); + lbValue arg1 = {}; + + Type *elem = base_array_type(arg0.type); + + bool is_float = is_type_float(elem); + bool is_signed = !is_type_unsigned(elem); + + LLVMOpcode op_code = cast(LLVMOpcode)0; + + switch (id) { + case BuiltinProc_simd_add: + case BuiltinProc_simd_sub: + case BuiltinProc_simd_mul: + case BuiltinProc_simd_div: + case BuiltinProc_simd_rem: + arg1 = lb_build_expr(p, ce->args[1]); + if (is_float) { + switch (id) { + case BuiltinProc_simd_add: op_code = LLVMFAdd; break; + case BuiltinProc_simd_sub: op_code = LLVMFSub; break; + case BuiltinProc_simd_mul: op_code = LLVMFMul; break; + case BuiltinProc_simd_div: op_code = LLVMFDiv; break; + } + } else { + switch (id) { + case BuiltinProc_simd_add: op_code = LLVMAdd; break; + case BuiltinProc_simd_sub: op_code = LLVMSub; break; + case BuiltinProc_simd_mul: op_code = LLVMMul; break; + case BuiltinProc_simd_div: + if (is_signed) { + op_code = LLVMSDiv; + } else { + op_code = LLVMUDiv; + } + break; + case BuiltinProc_simd_rem: + if (is_signed) { + op_code = LLVMSRem; + } else { + op_code = LLVMURem; + } + break; + } + } + if (op_code) { + res.value = LLVMBuildBinOp(p->builder, op_code, arg0.value, arg1.value, ""); + return res; + } + break; + case BuiltinProc_simd_shl: // Odin logic + case BuiltinProc_simd_shr: // Odin logic + case BuiltinProc_simd_shl_masked: // C logic + case BuiltinProc_simd_shr_masked: // C logic + arg1 = lb_build_expr(p, ce->args[1]); + { + i64 sz = type_size_of(elem); + GB_ASSERT(arg0.type->kind == Type_SimdVector); + + i64 count = arg0.type->SimdVector.count; + Type *elem1 = base_array_type(arg1.type); + + bool is_masked = false; + switch (id) { + case BuiltinProc_simd_shl: op_code = LLVMShl; is_masked = false; break; + case BuiltinProc_simd_shr: op_code = is_signed ? LLVMAShr : LLVMLShr; is_masked = false; break; + case BuiltinProc_simd_shl_masked: op_code = LLVMShl; is_masked = true; break; + case BuiltinProc_simd_shr_masked: op_code = is_signed ? LLVMAShr : LLVMLShr; is_masked = true; break; + } + if (op_code) { + LLVMValueRef bit_value = lb_const_int(m, elem1, sz*8 - 1).value; + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); + for (i64 i = 0; i < count; i++) { + values[i] = bit_value; + } + LLVMValueRef bits = LLVMConstVector(values, cast(unsigned)count); + if (is_masked) { + // C logic + LLVMValueRef shift = LLVMBuildAnd(p->builder, arg1.value, bits, ""); + res.value = LLVMBuildBinOp(p->builder, op_code, arg0.value, shift, ""); + } else { + // Odin logic + LLVMValueRef zero = lb_const_nil(m, arg1.type).value; + LLVMValueRef mask = LLVMBuildICmp(p->builder, LLVMIntULE, arg1.value, bits, ""); + LLVMValueRef shift = LLVMBuildBinOp(p->builder, op_code, arg0.value, arg1.value, ""); + res.value = LLVMBuildSelect(p->builder, mask, shift, zero, ""); + } + + return res; + } + } + break; + case BuiltinProc_simd_and: + case BuiltinProc_simd_or: + case BuiltinProc_simd_xor: + arg1 = lb_build_expr(p, ce->args[1]); + switch (id) { + case BuiltinProc_simd_and: op_code = LLVMAnd; break; + case BuiltinProc_simd_or: op_code = LLVMOr; break; + case BuiltinProc_simd_xor: op_code = LLVMXor; break; + } + if (op_code) { + res.value = LLVMBuildBinOp(p->builder, op_code, arg0.value, arg1.value, ""); + return res; + } + break; + case BuiltinProc_simd_neg: + if (is_float) { + res.value = LLVMBuildFNeg(p->builder, arg0.value, ""); + } else { + res.value = LLVMBuildNeg(p->builder, arg0.value, ""); + } + return res; + case BuiltinProc_simd_abs: + if (is_float) { + LLVMValueRef pos = arg0.value; + LLVMValueRef neg = LLVMBuildFNeg(p->builder, pos, ""); + LLVMValueRef cond = LLVMBuildFCmp(p->builder, LLVMRealOGT, pos, neg, ""); + res.value = LLVMBuildSelect(p->builder, cond, pos, neg, ""); + } else { + LLVMValueRef pos = arg0.value; + LLVMValueRef neg = LLVMBuildNeg(p->builder, pos, ""); + LLVMValueRef cond = LLVMBuildICmp(p->builder, is_signed ? LLVMIntSGT : LLVMIntUGT, pos, neg, ""); + res.value = LLVMBuildSelect(p->builder, cond, pos, neg, ""); + } + return res; + case BuiltinProc_simd_min: + if (is_float) { + LLVMValueRef cond = LLVMBuildFCmp(p->builder, LLVMRealOLT, arg0.value, arg1.value, ""); + res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); + } else { + LLVMValueRef cond = LLVMBuildICmp(p->builder, is_signed ? LLVMIntSLT : LLVMIntULT, arg0.value, arg1.value, ""); + res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); + } + return res; + case BuiltinProc_simd_max: + arg1 = lb_build_expr(p, ce->args[1]); + if (is_float) { + LLVMValueRef cond = LLVMBuildFCmp(p->builder, LLVMRealOGT, arg0.value, arg1.value, ""); + res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); + } else { + LLVMValueRef cond = LLVMBuildICmp(p->builder, is_signed ? LLVMIntSGT : LLVMIntUGT, arg0.value, arg1.value, ""); + res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); + } + return res; + case BuiltinProc_simd_eq: + case BuiltinProc_simd_ne: + case BuiltinProc_simd_lt: + case BuiltinProc_simd_le: + case BuiltinProc_simd_gt: + case BuiltinProc_simd_ge: + arg1 = lb_build_expr(p, ce->args[1]); + if (is_float) { + LLVMRealPredicate pred = cast(LLVMRealPredicate)0; + switch (id) { + case BuiltinProc_simd_eq: pred = LLVMRealOEQ; break; + case BuiltinProc_simd_ne: pred = LLVMRealONE; break; + case BuiltinProc_simd_lt: pred = LLVMRealOLT; break; + case BuiltinProc_simd_le: pred = LLVMRealOLE; break; + case BuiltinProc_simd_gt: pred = LLVMRealOGT; break; + case BuiltinProc_simd_ge: pred = LLVMRealOGE; break; + } + if (pred) { + res.value = LLVMBuildFCmp(p->builder, pred, arg0.value, arg1.value, ""); + res.value = LLVMBuildSExtOrBitCast(p->builder, res.value, lb_type(m, tv.type), ""); + return res; + } + } else { + LLVMIntPredicate pred = cast(LLVMIntPredicate)0; + switch (id) { + case BuiltinProc_simd_eq: pred = LLVMIntEQ; break; + case BuiltinProc_simd_ne: pred = LLVMIntNE; break; + case BuiltinProc_simd_lt: pred = is_signed ? LLVMIntSLT :LLVMIntULT; break; + case BuiltinProc_simd_le: pred = is_signed ? LLVMIntSLE :LLVMIntULE; break; + case BuiltinProc_simd_gt: pred = is_signed ? LLVMIntSGT :LLVMIntUGT; break; + case BuiltinProc_simd_ge: pred = is_signed ? LLVMIntSGE :LLVMIntUGE; break; + } + if (pred) { + res.value = LLVMBuildICmp(p->builder, pred, arg0.value, arg1.value, ""); + res.value = LLVMBuildSExtOrBitCast(p->builder, res.value, lb_type(m, tv.type), ""); + return res; + } + } + break; + } + GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[id].name)); + return {}; +} + lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, BuiltinProcId id) { ast_node(ce, CallExpr, expr); + if (BuiltinProc__simd_begin < id && id < BuiltinProc__simd_end) { + return lb_build_builtin_simd_proc(p, expr, tv, id); + } + switch (id) { case BuiltinProc_DIRECTIVE: { ast_node(bd, BasicDirective, ce->proc); From f21e9ee71281ec8665a3009cd1a350ffde3b7046 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 18:59:47 +0100 Subject: [PATCH 156/254] Allow basic casting of simd vectors --- src/check_expr.cpp | 12 ++++++++++++ src/llvm_backend_expr.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a4dfade98..9a7cd5b8b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2688,6 +2688,18 @@ bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) { return true; } + if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { + if (src->SimdVector.count != dst->SimdVector.count) { + return false; + } + Type *elem_src = base_array_type(src); + Type *elem_dst = base_array_type(dst); + Operand x = {}; + x.type = elem_src; + x.mode = Addressing_Value; + return check_is_castable_to(c, &x, elem_dst); + } + return false; } diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 133df4d41..f4b5702bb 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1820,6 +1820,38 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return res; } + if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { + Type *src_elem = core_array_type(src); + Type *dst_elem = core_array_type(dst); + + GB_ASSERT(src->SimdVector.count == dst->SimdVector.count); + + lbValue res = {}; + res.type = t; + if (are_types_identical(src_elem, dst_elem)) { + res.value = value.value; + } else if (is_type_float(src_elem) && is_type_integer(dst_elem)) { + if (is_type_unsigned(dst_elem)) { + res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t), ""); + } else { + res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t), ""); + } + } else if (is_type_integer(src_elem) && is_type_float(dst_elem)) { + if (is_type_unsigned(src_elem)) { + res.value = LLVMBuildUIToFP(p->builder, value.value, lb_type(m, t), ""); + } else { + res.value = LLVMBuildSIToFP(p->builder, value.value, lb_type(m, t), ""); + } + } else if (is_type_integer(src_elem) && is_type_integer(dst_elem)) { + res.value = LLVMBuildIntCast2(p->builder, value.value, lb_type(m, t), !is_type_unsigned(src_elem), ""); + } else if (is_type_float(src_elem) && is_type_float(dst_elem)) { + res.value = LLVMBuildFPCast(p->builder, value.value, lb_type(m, t), ""); + } else { + GB_PANIC("Unhandled simd vector conversion: %s -> %s", type_to_string(src), type_to_string(dst)); + } + return res; + } + // Pointer <-> uintptr if (is_type_pointer(src) && is_type_uintptr(dst)) { lbValue res = {}; From 5c72974167405a37bd397788e3224d773efd9c46 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 19:04:25 +0100 Subject: [PATCH 157/254] Simplify transmute for #simd --- src/llvm_backend_utility.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 037171637..bfd21bedb 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -201,6 +201,11 @@ lbValue lb_emit_transmute(lbProcedure *p, lbValue value, Type *t) { return res; } + if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { + res.value = LLVMBuildBitCast(p->builder, value.value, lb_type(p->module, t), ""); + return res; + } + if (lb_is_type_aggregate(src) || lb_is_type_aggregate(dst)) { lbValue s = lb_address_from_load_or_generate_local(p, value); lbValue d = lb_emit_transmute(p, s, alloc_type_pointer(t)); From 4c4480104de9d6ba520215afb6330c95b0e56b93 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 20:27:14 +0100 Subject: [PATCH 158/254] Add `simd_extract` and `simd_insert` --- src/check_builtin.cpp | 120 +++++++++++++++++++++++++--------- src/checker_builtin_procs.hpp | 6 ++ src/llvm_backend_proc.cpp | 11 ++++ 3 files changed, 105 insertions(+), 32 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 13eb9f47d..ab4cc210c 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -425,14 +425,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call { Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); - check_expr(c, &y, ce->args[1]); - if (x.mode == Addressing_Invalid) { - return false; - } - if (y.mode == Addressing_Invalid) { - return false; - } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &y, x.type); if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -470,14 +465,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call { Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); - check_expr(c, &y, ce->args[1]); - if (x.mode == Addressing_Invalid) { - return false; - } - if (y.mode == Addressing_Invalid) { - return false; - } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &y, x.type); if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -514,14 +504,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call { Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); - check_expr(c, &y, ce->args[1]); - if (x.mode == Addressing_Invalid) { - return false; - } - if (y.mode == Addressing_Invalid) { - return false; - } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &y, x.type); if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -599,14 +584,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); - check_expr(c, &y, ce->args[1]); - if (x.mode == Addressing_Invalid) { - return false; - } - if (y.mode == Addressing_Invalid) { - return false; - } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &y, x.type); if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -639,6 +619,81 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call operand->type = alloc_type_simd_vector(count, new_elem); return true; } + + case BuiltinProc_simd_extract: + { + Operand x = {}; + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem) && !is_type_float(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + i64 max_count = x.type->SimdVector.count; + i64 value = -1; + if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) { + return false; + } + if (max_count < 0) { + error(ce->args[1], "'%.*s' expected a constant integer index, got '%lld'", LIT(builtin_name), cast(long long)value); + return false; + } + + operand->mode = Addressing_Value; + operand->type = elem; + return true; + } + break; + case BuiltinProc_simd_insert: + { + Operand x = {}; + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem) && !is_type_float(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + i64 max_count = x.type->SimdVector.count; + i64 value = -1; + if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) { + return false; + } + if (max_count < 0) { + error(ce->args[1], "'%.*s' expected a constant integer index, got '%lld'", LIT(builtin_name), cast(long long)value); + return false; + } + + Operand y = {}; + check_expr_with_type_hint(c, &y, ce->args[2], elem); if (y.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &y, elem); + if (!are_types_identical(y.type, elem)) { + gbString et = type_to_string(elem); + gbString yt = type_to_string(y.type); + error(y.expr, "'%.*s' expected a type of '%s' to insert, got '%s'", LIT(builtin_name), et, yt); + gb_string_free(yt); + gb_string_free(et); + return false; + } + + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + break; default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); } @@ -736,6 +791,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } operand->mode = Addressing_Value; operand->value = {}; + operand->expr = call; return ok; } diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 80467ffb1..604e9dc8c 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -146,6 +146,9 @@ BuiltinProc__simd_begin, BuiltinProc_simd_le, BuiltinProc_simd_gt, BuiltinProc_simd_ge, + + BuiltinProc_simd_extract, + BuiltinProc_simd_insert, BuiltinProc__simd_end, // Platform specific intrinsics @@ -395,6 +398,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_le"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_gt"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_ge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_extract"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_insert"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 82ad6daef..4af5d9440 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -991,6 +991,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const lbValue arg0 = lb_build_expr(p, ce->args[0]); lbValue arg1 = {}; + lbValue arg2 = {}; Type *elem = base_array_type(arg0.type); @@ -1173,6 +1174,16 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const } } break; + + case BuiltinProc_simd_extract: + arg1 = lb_build_expr(p, ce->args[1]); + res.value = LLVMBuildExtractElement(p->builder, arg0.value, arg1.value, ""); + return res; + case BuiltinProc_simd_insert: + arg1 = lb_build_expr(p, ce->args[1]); + arg2 = lb_build_expr(p, ce->args[2]); + res.value = LLVMBuildInsertElement(p->builder, arg0.value, arg2.value, arg1.value, ""); + return res; } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[id].name)); return {}; From 53f0c6ef1a73ab7afe21ed15d3d88a266af6a03c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 20:31:31 +0100 Subject: [PATCH 159/254] Add ranges for simd compounds literals --- src/check_expr.cpp | 192 ++++++++++++++++++------------------- src/llvm_backend_const.cpp | 85 +++++++++++++--- 2 files changed, 163 insertions(+), 114 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 9a7cd5b8b..3dbecb1c0 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7741,112 +7741,106 @@ ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type * } if (cl->elems.count > 0 && cl->elems[0]->kind == Ast_FieldValue) { - // TODO(bill): Why was this decision made for simd? - if (is_type_simd_vector(t)) { - error(cl->elems[0], "'field = value' is not allowed for SIMD vector literals"); - } else { - RangeCache rc = range_cache_make(heap_allocator()); - defer (range_cache_destroy(&rc)); + RangeCache rc = range_cache_make(heap_allocator()); + defer (range_cache_destroy(&rc)); - for_array(i, cl->elems) { - Ast *elem = cl->elems[i]; - if (elem->kind != Ast_FieldValue) { - error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); + for_array(i, cl->elems) { + Ast *elem = cl->elems[i]; + if (elem->kind != Ast_FieldValue) { + error(elem, "Mixture of 'field = value' and value elements in a literal is not allowed"); + continue; + } + ast_node(fv, FieldValue, elem); + + if (is_ast_range(fv->field)) { + Token op = fv->field->BinaryExpr.op; + + Operand x = {}; + Operand y = {}; + bool ok = check_range(c, fv->field, &x, &y, nullptr); + if (!ok) { continue; } - ast_node(fv, FieldValue, elem); - - if (is_ast_range(fv->field)) { - Token op = fv->field->BinaryExpr.op; - - Operand x = {}; - Operand y = {}; - bool ok = check_range(c, fv->field, &x, &y, nullptr); - if (!ok) { - continue; - } - if (x.mode != Addressing_Constant || !is_type_integer(core_type(x.type))) { - error(x.expr, "Expected a constant integer as an array field"); - continue; - } - - if (y.mode != Addressing_Constant || !is_type_integer(core_type(y.type))) { - error(y.expr, "Expected a constant integer as an array field"); - continue; - } - - i64 lo = exact_value_to_i64(x.value); - i64 hi = exact_value_to_i64(y.value); - i64 max_index = hi; - if (op.kind == Token_RangeHalf) { // ..< (exclusive) - hi -= 1; - } else { // .. (inclusive) - max_index += 1; - } - - bool new_range = range_cache_add_range(&rc, lo, hi); - if (!new_range) { - error(elem, "Overlapping field range index %lld %.*s %lld for %.*s", lo, LIT(op.string), hi, LIT(context_name)); - continue; - } - - - if (max_type_count >= 0 && (lo < 0 || lo >= max_type_count)) { - error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", lo, max_type_count, LIT(context_name)); - continue; - } - if (max_type_count >= 0 && (hi < 0 || hi >= max_type_count)) { - error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", hi, max_type_count, LIT(context_name)); - continue; - } - - if (max < hi) { - max = max_index; - } - - Operand operand = {}; - check_expr_with_type_hint(c, &operand, fv->value, elem_type); - check_assignment(c, &operand, elem_type, context_name); - - is_constant = is_constant && operand.mode == Addressing_Constant; - } else { - Operand op_index = {}; - check_expr(c, &op_index, fv->field); - - if (op_index.mode != Addressing_Constant || !is_type_integer(core_type(op_index.type))) { - error(elem, "Expected a constant integer as an array field"); - continue; - } - // add_type_and_value(c->info, op_index.expr, op_index.mode, op_index.type, op_index.value); - - i64 index = exact_value_to_i64(op_index.value); - - if (max_type_count >= 0 && (index < 0 || index >= max_type_count)) { - error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", index, max_type_count, LIT(context_name)); - continue; - } - - bool new_index = range_cache_add_index(&rc, index); - if (!new_index) { - error(elem, "Duplicate field index %lld for %.*s", index, LIT(context_name)); - continue; - } - - if (max < index+1) { - max = index+1; - } - - Operand operand = {}; - check_expr_with_type_hint(c, &operand, fv->value, elem_type); - check_assignment(c, &operand, elem_type, context_name); - - is_constant = is_constant && operand.mode == Addressing_Constant; + if (x.mode != Addressing_Constant || !is_type_integer(core_type(x.type))) { + error(x.expr, "Expected a constant integer as an array field"); + continue; } - } - cl->max_count = max; + if (y.mode != Addressing_Constant || !is_type_integer(core_type(y.type))) { + error(y.expr, "Expected a constant integer as an array field"); + continue; + } + + i64 lo = exact_value_to_i64(x.value); + i64 hi = exact_value_to_i64(y.value); + i64 max_index = hi; + if (op.kind == Token_RangeHalf) { // ..< (exclusive) + hi -= 1; + } else { // .. (inclusive) + max_index += 1; + } + + bool new_range = range_cache_add_range(&rc, lo, hi); + if (!new_range) { + error(elem, "Overlapping field range index %lld %.*s %lld for %.*s", lo, LIT(op.string), hi, LIT(context_name)); + continue; + } + + + if (max_type_count >= 0 && (lo < 0 || lo >= max_type_count)) { + error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", lo, max_type_count, LIT(context_name)); + continue; + } + if (max_type_count >= 0 && (hi < 0 || hi >= max_type_count)) { + error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", hi, max_type_count, LIT(context_name)); + continue; + } + + if (max < hi) { + max = max_index; + } + + Operand operand = {}; + check_expr_with_type_hint(c, &operand, fv->value, elem_type); + check_assignment(c, &operand, elem_type, context_name); + + is_constant = is_constant && operand.mode == Addressing_Constant; + } else { + Operand op_index = {}; + check_expr(c, &op_index, fv->field); + + if (op_index.mode != Addressing_Constant || !is_type_integer(core_type(op_index.type))) { + error(elem, "Expected a constant integer as an array field"); + continue; + } + // add_type_and_value(c->info, op_index.expr, op_index.mode, op_index.type, op_index.value); + + i64 index = exact_value_to_i64(op_index.value); + + if (max_type_count >= 0 && (index < 0 || index >= max_type_count)) { + error(elem, "Index %lld is out of bounds (0..<%lld) for %.*s", index, max_type_count, LIT(context_name)); + continue; + } + + bool new_index = range_cache_add_index(&rc, index); + if (!new_index) { + error(elem, "Duplicate field index %lld for %.*s", index, LIT(context_name)); + continue; + } + + if (max < index+1) { + max = index+1; + } + + Operand operand = {}; + check_expr_with_type_hint(c, &operand, fv->value, elem_type); + check_assignment(c, &operand, elem_type, context_name); + + is_constant = is_constant && operand.mode == Addressing_Constant; + } } + cl->max_count = max; } else { isize index = 0; for (; index < cl->elems.count; index++) { diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 8f17a1cfb..3a3067dbc 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -819,26 +819,81 @@ lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_loc return lb_const_nil(m, original_type); } GB_ASSERT(elem_type_can_be_constant(elem_type)); - isize total_elem_count = cast(isize)type->SimdVector.count; LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, total_elem_count); - for (isize i = 0; i < elem_count; i++) { - TypeAndValue tav = cl->elems[i]->tav; - GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; - } - LLVMTypeRef et = lb_type(m, elem_type); + if (cl->elems[0]->kind == Ast_FieldValue) { + // TODO(bill): This is O(N*M) and will be quite slow; it should probably be sorted before hand + isize value_index = 0; + for (i64 i = 0; i < total_elem_count; i++) { + bool found = false; - for (isize i = elem_count; i < type->SimdVector.count; i++) { - values[i] = LLVMConstNull(et); - } - for (isize i = 0; i < total_elem_count; i++) { - values[i] = llvm_const_cast(values[i], et); - } + for (isize j = 0; j < elem_count; j++) { + Ast *elem = cl->elems[j]; + ast_node(fv, FieldValue, elem); + if (is_ast_range(fv->field)) { + ast_node(ie, BinaryExpr, fv->field); + TypeAndValue lo_tav = ie->left->tav; + TypeAndValue hi_tav = ie->right->tav; + GB_ASSERT(lo_tav.mode == Addressing_Constant); + GB_ASSERT(hi_tav.mode == Addressing_Constant); - res.value = LLVMConstVector(values, cast(unsigned)total_elem_count); - return res; + TokenKind op = ie->op.kind; + i64 lo = exact_value_to_i64(lo_tav.value); + i64 hi = exact_value_to_i64(hi_tav.value); + if (op != Token_RangeHalf) { + hi += 1; + } + if (lo == i) { + TypeAndValue tav = fv->value->tav; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + for (i64 k = lo; k < hi; k++) { + values[value_index++] = val; + } + + found = true; + i += (hi-lo-1); + break; + } + } else { + TypeAndValue index_tav = fv->field->tav; + GB_ASSERT(index_tav.mode == Addressing_Constant); + i64 index = exact_value_to_i64(index_tav.value); + if (index == i) { + TypeAndValue tav = fv->value->tav; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[value_index++] = val; + found = true; + break; + } + } + } + + if (!found) { + values[value_index++] = LLVMConstNull(lb_type(m, elem_type)); + } + } + + res.value = LLVMConstVector(values, cast(unsigned)total_elem_count); + return res; + } else { + for (isize i = 0; i < elem_count; i++) { + TypeAndValue tav = cl->elems[i]->tav; + GB_ASSERT(tav.mode != Addressing_Invalid); + values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + } + LLVMTypeRef et = lb_type(m, elem_type); + + for (isize i = elem_count; i < total_elem_count; i++) { + values[i] = LLVMConstNull(et); + } + for (isize i = 0; i < total_elem_count; i++) { + values[i] = llvm_const_cast(values[i], et); + } + + res.value = LLVMConstVector(values, cast(unsigned)total_elem_count); + return res; + } } else if (is_type_struct(type)) { ast_node(cl, CompoundLit, value.value_compound); From 0203bb657ed09046dddc958be5322b1cd8c0a589 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 20:39:22 +0100 Subject: [PATCH 160/254] Allow for non-constant simd vector compound types --- src/check_expr.cpp | 2 +- src/llvm_backend_expr.cpp | 96 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3dbecb1c0..9fd6acefd 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7885,7 +7885,7 @@ ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast *node, Type * if (t->kind == Type_SimdVector) { if (!is_constant) { - error(node, "Expected all constant elements for a simd vector"); + // error(node, "Expected all constant elements for a simd vector"); } } diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index f4b5702bb..1b10cd776 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4641,6 +4641,102 @@ lbAddr lb_build_addr(lbProcedure *p, Ast *expr) { break; } + case Type_SimdVector: { + if (cl->elems.count > 0) { + lbValue vector_value = lb_const_value(p->module, type, exact_value_compound(expr)); + defer (lb_addr_store(p, v, vector_value)); + + auto temp_data = array_make(temporary_allocator(), 0, cl->elems.count); + + // NOTE(bill): Separate value, store into their own chunks + for_array(i, cl->elems) { + Ast *elem = cl->elems[i]; + if (elem->kind == Ast_FieldValue) { + ast_node(fv, FieldValue, elem); + if (lb_is_elem_const(fv->value, et)) { + continue; + } + if (is_ast_range(fv->field)) { + ast_node(ie, BinaryExpr, fv->field); + TypeAndValue lo_tav = ie->left->tav; + TypeAndValue hi_tav = ie->right->tav; + GB_ASSERT(lo_tav.mode == Addressing_Constant); + GB_ASSERT(hi_tav.mode == Addressing_Constant); + + TokenKind op = ie->op.kind; + i64 lo = exact_value_to_i64(lo_tav.value); + i64 hi = exact_value_to_i64(hi_tav.value); + if (op != Token_RangeHalf) { + hi += 1; + } + + lbValue value = lb_build_expr(p, fv->value); + + for (i64 k = lo; k < hi; k++) { + lbCompoundLitElemTempData data = {}; + data.value = value; + data.elem_index = cast(i32)k; + array_add(&temp_data, data); + } + + } else { + auto tav = fv->field->tav; + GB_ASSERT(tav.mode == Addressing_Constant); + i64 index = exact_value_to_i64(tav.value); + + lbValue value = lb_build_expr(p, fv->value); + lbCompoundLitElemTempData data = {}; + data.value = lb_emit_conv(p, value, et); + data.expr = fv->value; + data.elem_index = cast(i32)index; + array_add(&temp_data, data); + } + + } else { + if (lb_is_elem_const(elem, et)) { + continue; + } + lbCompoundLitElemTempData data = {}; + data.expr = elem; + data.elem_index = cast(i32)i; + array_add(&temp_data, data); + } + } + + + for_array(i, temp_data) { + lbValue field_expr = temp_data[i].value; + Ast *expr = temp_data[i].expr; + + auto prev_hint = lb_set_copy_elision_hint(p, lb_addr(temp_data[i].gep), expr); + + if (field_expr.value == nullptr) { + field_expr = lb_build_expr(p, expr); + } + Type *t = field_expr.type; + GB_ASSERT(t->kind != Type_Tuple); + lbValue ev = lb_emit_conv(p, field_expr, et); + + if (!p->copy_elision_hint.used) { + temp_data[i].value = ev; + } + + lb_reset_copy_elision_hint(p, prev_hint); + } + + + // TODO(bill): reduce the need for individual `insertelement` if a `shufflevector` + // might be a better option + + for_array(i, temp_data) { + if (temp_data[i].value.value != nullptr) { + LLVMValueRef index = lb_const_int(p->module, t_u32, temp_data[i].elem_index).value; + vector_value.value = LLVMBuildInsertElement(p->builder, vector_value.value, temp_data[i].value.value, index, ""); + } + } + } + break; + } } return v; From b168bf9460491a101f3a7d41c28500a45898ecbf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 21:00:00 +0100 Subject: [PATCH 161/254] Rename `simd_insert` to `simd_replace` --- src/check_builtin.cpp | 2 +- src/checker_builtin_procs.hpp | 4 ++-- src/llvm_backend_proc.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ab4cc210c..64b2ebfce 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -651,7 +651,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } break; - case BuiltinProc_simd_insert: + case BuiltinProc_simd_replace: { Operand x = {}; check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 604e9dc8c..f5d4111bc 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -148,7 +148,7 @@ BuiltinProc__simd_begin, BuiltinProc_simd_ge, BuiltinProc_simd_extract, - BuiltinProc_simd_insert, + BuiltinProc_simd_replace, BuiltinProc__simd_end, // Platform specific intrinsics @@ -400,7 +400,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_ge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_extract"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_insert"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_replace"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 4af5d9440..cfb69c654 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1179,7 +1179,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const arg1 = lb_build_expr(p, ce->args[1]); res.value = LLVMBuildExtractElement(p->builder, arg0.value, arg1.value, ""); return res; - case BuiltinProc_simd_insert: + case BuiltinProc_simd_replace: arg1 = lb_build_expr(p, ce->args[1]); arg2 = lb_build_expr(p, ce->args[2]); res.value = LLVMBuildInsertElement(p->builder, arg0.value, arg2.value, arg1.value, ""); From 1549d01bf76e8c5e13626e57b1f976330a9cdd50 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 21:17:21 +0100 Subject: [PATCH 162/254] Restrict `swizzle` to a power of two for #simd --- src/check_builtin.cpp | 35 +++++++++++++++++++++++++++++++++++ src/check_expr.cpp | 6 +++++- src/check_type.cpp | 6 ++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 64b2ebfce..69e584827 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -694,6 +694,36 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } break; + + // case BuiltinProc_simd_rotate_left: + // { + // Operand x = {}; + // check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + + // if (!is_type_simd_vector(x.type)) { + // error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + // return false; + // } + // Type *elem = base_array_type(x.type); + // if (!is_type_integer(elem) && !is_type_float(elem)) { + // gbString xs = type_to_string(x.type); + // error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + // gb_string_free(xs); + // return false; + // } + + // Operand offset = {}; + // check_expr_with_type_hint(c, &offset, ce->args[1]); if (x.mode == Addressing_Invalid) { return false; } + // convert_to_typed(c, &offset, t_int); + // if (offset.mode != Addressing_Constant) { + // error(offset.expr, "'%.*s' expected a constant integer for the offset", LIT(builtin_name)); + // return false; + // } + + // operand->mode = Addressing_Value; + // operand->type = x.type; + // return true + // } default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); } @@ -1749,6 +1779,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 operand->mode = Addressing_Value; } + if (is_type_simd_vector(type) && !is_power_of_two(arg_count)) { + error(call, "'swizzle' with a #simd vector must have a power of two arguments, got %lld", cast(long long)arg_count); + return false; + } + operand->type = determine_swizzle_array_type(original_type, type_hint, arg_count); break; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 9fd6acefd..a30f83e7e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4119,7 +4119,11 @@ ExactValue get_constant_field(CheckerContext *c, Operand const *operand, Selecti Type *determine_swizzle_array_type(Type *original_type, Type *type_hint, isize new_count) { Type *array_type = base_type(type_deref(original_type)); - GB_ASSERT(array_type->kind == Type_Array); + GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector); + if (array_type->kind == Type_SimdVector) { + Type *elem_type = array_type->SimdVector.elem; + return alloc_type_simd_vector(new_count, elem_type); + } Type *elem_type = array_type->Array.elem; Type *swizzle_array_type = nullptr; diff --git a/src/check_type.cpp b/src/check_type.cpp index 1df63e599..088853810 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2795,14 +2795,16 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t if (name == "soa") { *type = make_soa_struct_fixed(ctx, e, at->elem, elem, count, generic_type); } else if (name == "simd") { - if (!is_type_valid_vector_elem(elem)) { + if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) { gbString str = type_to_string(elem); error(at->elem, "Invalid element type for 'intrinsics.simd_vector', expected an integer or float with no specific endianness, got '%s'", str); gb_string_free(str); *type = alloc_type_array(elem, count, generic_type); goto array_end; } - if (count < 1 || !is_power_of_two(count)) { + if (is_type_polymorphic(elem)) { + count = 1; + } else if (count < 1 || !is_power_of_two(count)) { error(at->count, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); *type = alloc_type_array(elem, count, generic_type); goto array_end; From 63cc8a80a045d48960d85640d11f39237c2f8ca4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 21:29:45 +0100 Subject: [PATCH 163/254] Correct parapoly for #simd --- src/check_expr.cpp | 13 +++++++++++++ src/check_type.cpp | 4 ++-- src/types.cpp | 9 ++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a30f83e7e..fcd7818bc 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1328,6 +1328,19 @@ bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, Type *source, } } return false; + + case Type_SimdVector: + if (source->kind == Type_SimdVector) { + if (poly->SimdVector.generic_count != nullptr) { + if (!polymorphic_assign_index(&poly->SimdVector.generic_count, &poly->SimdVector.count, source->SimdVector.count)) { + return false; + } + } + if (poly->SimdVector.count == source->SimdVector.count) { + return is_polymorphic_type_assignable(c, poly->SimdVector.elem, source->SimdVector.elem, true, modify_type); + } + } + return false; } return false; } diff --git a/src/check_type.cpp b/src/check_type.cpp index 088853810..354ab6e94 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2803,14 +2803,14 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t goto array_end; } if (is_type_polymorphic(elem)) { - count = 1; + // Ignore } else if (count < 1 || !is_power_of_two(count)) { error(at->count, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); *type = alloc_type_array(elem, count, generic_type); goto array_end; } - *type = alloc_type_simd_vector(count, elem); + *type = alloc_type_simd_vector(count, elem, generic_type); } else { error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name)); *type = alloc_type_array(elem, count, generic_type); diff --git a/src/types.cpp b/src/types.cpp index 755f78f1c..2d5709b19 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -261,6 +261,7 @@ struct TypeProc { TYPE_KIND(SimdVector, struct { \ i64 count; \ Type *elem; \ + Type *generic_count; \ }) \ TYPE_KIND(RelativePointer, struct { \ Type *pointer_type; \ @@ -1085,10 +1086,11 @@ Type *alloc_type_bit_set() { -Type *alloc_type_simd_vector(i64 count, Type *elem) { +Type *alloc_type_simd_vector(i64 count, Type *elem, Type *generic_count=nullptr) { Type *t = alloc_type(Type_SimdVector); t->SimdVector.count = count; t->SimdVector.elem = elem; + t->SimdVector.generic_count = generic_count; return t; } @@ -2078,6 +2080,11 @@ bool is_type_polymorphic(Type *t, bool or_specialized=false) { return true; } return is_type_polymorphic(t->Array.elem, or_specialized); + case Type_SimdVector: + if (t->SimdVector.generic_count != nullptr) { + return true; + } + return is_type_polymorphic(t->SimdVector.elem, or_specialized); case Type_DynamicArray: return is_type_polymorphic(t->DynamicArray.elem, or_specialized); case Type_Slice: From 8ac12886ed90298f3de1e4685153b90bb67fd6db Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 21:30:10 +0100 Subject: [PATCH 164/254] Add `core:simd` --- core/simd/simd.odin | 58 ++++++++++++++++++++++++++++++++++++++ examples/all/all_main.odin | 1 + 2 files changed, 59 insertions(+) create mode 100644 core/simd/simd.odin diff --git a/core/simd/simd.odin b/core/simd/simd.odin new file mode 100644 index 000000000..87386f91f --- /dev/null +++ b/core/simd/simd.odin @@ -0,0 +1,58 @@ +package simd + +import "core:intrinsics" + +add :: intrinsics.simd_add +sub :: intrinsics.simd_sub +mul :: intrinsics.simd_mul +div :: intrinsics.simd_div +rem :: intrinsics.simd_rem + +// Keeps Odin's Behaviour +// (x << y) if y <= mask else 0 +shl :: intrinsics.simd_shl +shr :: intrinsics.simd_shr + +// Similar to C's Behaviour +// x << (y & mask) +shl_masked :: intrinsics.simd_shl_masked +shr_masked :: intrinsics.simd_shr_masked + +and :: intrinsics.simd_and +or :: intrinsics.simd_or +xor :: intrinsics.simd_xor +neg :: intrinsics.simd_neg +abs :: intrinsics.simd_abs +min :: intrinsics.simd_min +max :: intrinsics.simd_max +eq :: intrinsics.simd_eq +ne :: intrinsics.simd_ne +lt :: intrinsics.simd_lt +le :: intrinsics.simd_le +gt :: intrinsics.simd_gt +ge :: intrinsics.simd_ge +extract :: intrinsics.simd_extract +replace :: intrinsics.simd_replace + +splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { + return T{0.. ^[LANES]E { + return (^[LANES]E)(v) +} +to_array :: #force_inline proc "contextless" (v: #simd[$LANES]$E) -> [LANES]E { + return transmute([LANES]E)(v) +} +from_array :: #force_inline proc "contextless" (v: $A/[$LANES]$E) -> #simd[LANES]E where LANES & (LANES-1) == 0 { + return transmute(#simd[LANES]E)v +} + +from_slice :: proc($T: typeid/#simd[$LANES]$E, slice: []E) -> T where LANES & (LANES-1) == 0 { + assert(len(slice) >= LANES, "slice length must be a least the number of lanes") + array: [LANES]E + #no_bounds_check for i in 0.. Date: Wed, 25 May 2022 22:04:47 +0100 Subject: [PATCH 165/254] Add `intrinsics.simd_reduce_*` --- core/simd/simd.odin | 8 +++ src/check_builtin.cpp | 50 +++++++++++++++++ src/checker_builtin_procs.hpp | 16 ++++++ src/llvm_backend_proc.cpp | 102 +++++++++++++++++++++++++++++++--- 4 files changed, 167 insertions(+), 9 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 87386f91f..ad14855bd 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -34,6 +34,14 @@ ge :: intrinsics.simd_ge extract :: intrinsics.simd_extract replace :: intrinsics.simd_replace +reduce_add_ordered :: intrinsics.simd_reduce_add_ordered +reduce_mul_ordered :: intrinsics.simd_reduce_mul_ordered +reduce_min :: intrinsics.simd_reduce_min +reduce_max :: intrinsics.simd_reduce_max +reduce_and :: intrinsics.simd_reduce_and +reduce_or :: intrinsics.simd_reduce_or +reduce_xor :: intrinsics.simd_reduce_xor + splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { return T{0..args[0]); if (x.mode == Addressing_Invalid) { return false; } + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem) && !is_type_float(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + + operand->mode = Addressing_Value; + operand->type = base_array_type(x.type); + return true; + } + + case BuiltinProc_simd_reduce_and: + case BuiltinProc_simd_reduce_or: + case BuiltinProc_simd_reduce_xor: + { + Operand x = {}; + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + + operand->mode = Addressing_Value; + operand->type = base_array_type(x.type); + return true; + } + + // case BuiltinProc_simd_rotate_left: // { // Operand x = {}; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index f5d4111bc..98cc9f284 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -149,6 +149,14 @@ BuiltinProc__simd_begin, BuiltinProc_simd_extract, BuiltinProc_simd_replace, + + BuiltinProc_simd_reduce_add_ordered, + BuiltinProc_simd_reduce_mul_ordered, + BuiltinProc_simd_reduce_min, + BuiltinProc_simd_reduce_max, + BuiltinProc_simd_reduce_and, + BuiltinProc_simd_reduce_or, + BuiltinProc_simd_reduce_xor, BuiltinProc__simd_end, // Platform specific intrinsics @@ -401,6 +409,14 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_extract"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_replace"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_reduce_add_ordered"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_mul_ordered"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_min"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_max"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_and"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_or"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reduce_xor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index cfb69c654..c09265e7a 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -981,7 +981,7 @@ lbValue lb_emit_call(lbProcedure *p, lbValue value, Array const &args, return result; } -lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, BuiltinProcId id) { +lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, BuiltinProcId builtin_id) { ast_node(ce, CallExpr, expr); lbModule *m = p->module; @@ -1000,7 +1000,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const LLVMOpcode op_code = cast(LLVMOpcode)0; - switch (id) { + switch (builtin_id) { case BuiltinProc_simd_add: case BuiltinProc_simd_sub: case BuiltinProc_simd_mul: @@ -1008,14 +1008,14 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_rem: arg1 = lb_build_expr(p, ce->args[1]); if (is_float) { - switch (id) { + switch (builtin_id) { case BuiltinProc_simd_add: op_code = LLVMFAdd; break; case BuiltinProc_simd_sub: op_code = LLVMFSub; break; case BuiltinProc_simd_mul: op_code = LLVMFMul; break; case BuiltinProc_simd_div: op_code = LLVMFDiv; break; } } else { - switch (id) { + switch (builtin_id) { case BuiltinProc_simd_add: op_code = LLVMAdd; break; case BuiltinProc_simd_sub: op_code = LLVMSub; break; case BuiltinProc_simd_mul: op_code = LLVMMul; break; @@ -1053,7 +1053,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const Type *elem1 = base_array_type(arg1.type); bool is_masked = false; - switch (id) { + switch (builtin_id) { case BuiltinProc_simd_shl: op_code = LLVMShl; is_masked = false; break; case BuiltinProc_simd_shr: op_code = is_signed ? LLVMAShr : LLVMLShr; is_masked = false; break; case BuiltinProc_simd_shl_masked: op_code = LLVMShl; is_masked = true; break; @@ -1086,7 +1086,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_or: case BuiltinProc_simd_xor: arg1 = lb_build_expr(p, ce->args[1]); - switch (id) { + switch (builtin_id) { case BuiltinProc_simd_and: op_code = LLVMAnd; break; case BuiltinProc_simd_or: op_code = LLVMOr; break; case BuiltinProc_simd_xor: op_code = LLVMXor; break; @@ -1144,7 +1144,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const arg1 = lb_build_expr(p, ce->args[1]); if (is_float) { LLVMRealPredicate pred = cast(LLVMRealPredicate)0; - switch (id) { + switch (builtin_id) { case BuiltinProc_simd_eq: pred = LLVMRealOEQ; break; case BuiltinProc_simd_ne: pred = LLVMRealONE; break; case BuiltinProc_simd_lt: pred = LLVMRealOLT; break; @@ -1159,7 +1159,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const } } else { LLVMIntPredicate pred = cast(LLVMIntPredicate)0; - switch (id) { + switch (builtin_id) { case BuiltinProc_simd_eq: pred = LLVMIntEQ; break; case BuiltinProc_simd_ne: pred = LLVMIntNE; break; case BuiltinProc_simd_lt: pred = is_signed ? LLVMIntSLT :LLVMIntULT; break; @@ -1184,8 +1184,92 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const arg2 = lb_build_expr(p, ce->args[2]); res.value = LLVMBuildInsertElement(p->builder, arg0.value, arg2.value, arg1.value, ""); return res; + + case BuiltinProc_simd_reduce_add_ordered: + case BuiltinProc_simd_reduce_mul_ordered: + { + LLVMTypeRef llvm_elem = lb_type(m, elem); + LLVMValueRef args[2] = {}; + isize args_count = 0; + + char const *name = nullptr; + switch (builtin_id) { + case BuiltinProc_simd_reduce_add_ordered: + if (is_float) { + name = "llvm.vector.reduce.fadd"; + args[args_count++] = LLVMConstReal(llvm_elem, 0.0); + } else { + name = "llvm.vector.reduce.add"; + } + break; + case BuiltinProc_simd_reduce_mul_ordered: + if (is_float) { + name = "llvm.vector.reduce.fmul"; + args[args_count++] = LLVMConstReal(llvm_elem, 1.0); + } else { + name = "llvm.vector.reduce.mul"; + } + break; + } + args[args_count++] = arg0.value; + + + LLVMTypeRef types[1] = {lb_type(p->module, arg0.type)}; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + lbValue res = {}; + res.value = LLVMBuildCall(p->builder, ip, args, cast(unsigned)args_count, ""); + res.type = tv.type; + return res; + } + case BuiltinProc_simd_reduce_min: + case BuiltinProc_simd_reduce_max: + case BuiltinProc_simd_reduce_and: + case BuiltinProc_simd_reduce_or: + case BuiltinProc_simd_reduce_xor: + { + char const *name = nullptr; + switch (builtin_id) { + case BuiltinProc_simd_reduce_min: + if (is_float) { + name = "llvm.vector.reduce.fmin"; + } else if (is_signed) { + name = "llvm.vector.reduce.smin"; + } else { + name = "llvm.vector.reduce.umin"; + } + break; + case BuiltinProc_simd_reduce_max: + if (is_float) { + name = "llvm.vector.reduce.fmax"; + } else if (is_signed) { + name = "llvm.vector.reduce.smax"; + } else { + name = "llvm.vector.reduce.umax"; + } + break; + case BuiltinProc_simd_reduce_and: name = "llvm.vector.reduce.and"; break; + case BuiltinProc_simd_reduce_or: name = "llvm.vector.reduce.or"; break; + case BuiltinProc_simd_reduce_xor: name = "llvm.vector.reduce.xor"; break; + } + LLVMTypeRef types[1] = {lb_type(p->module, arg0.type)}; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[1] = {}; + args[0] = arg0.value; + + lbValue res = {}; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + res.type = tv.type; + return res; + } } - GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[id].name)); + GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); + return {}; } From 63d6c08d9035fb3b344dc17b7667b24928a1edf7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 22:09:38 +0100 Subject: [PATCH 166/254] Add `raw_simd_data` --- core/mem/raw.odin | 1 + core/runtime/core_builtin.odin | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/mem/raw.odin b/core/mem/raw.odin index 0a0780dfd..2bce2d7aa 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -21,6 +21,7 @@ make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { } raw_array_data :: runtime.raw_array_data +raw_simd_data :: runtime.raw_simd_data raw_string_data :: runtime.raw_string_data raw_slice_data :: runtime.raw_slice_data raw_dynamic_array_data :: runtime.raw_dynamic_array_data diff --git a/core/runtime/core_builtin.odin b/core/runtime/core_builtin.odin index 4ddc3928a..7cb5287c0 100644 --- a/core/runtime/core_builtin.odin +++ b/core/runtime/core_builtin.odin @@ -604,6 +604,10 @@ raw_array_data :: proc "contextless" (a: $P/^($T/[$N]$E)) -> [^]E { return ([^]E)(a) } @builtin +raw_simd_data :: proc "contextless" (a: $P/^($T/#simd[$N]$E)) -> [^]E { + return ([^]E)(a) +} +@builtin raw_slice_data :: proc "contextless" (s: $S/[]$E) -> [^]E { ptr := (transmute(Raw_Slice)s).data return ([^]E)(ptr) @@ -619,7 +623,7 @@ raw_string_data :: proc "contextless" (s: $S/string) -> [^]u8 { } @builtin -raw_data :: proc{raw_array_data, raw_slice_data, raw_dynamic_array_data, raw_string_data} +raw_data :: proc{raw_array_data, raw_slice_data, raw_dynamic_array_data, raw_string_data, raw_simd_data} From 808ea30b48b35d1556afbddcd49839ea9014d76e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 22:16:44 +0100 Subject: [PATCH 167/254] Allow booleans for #simd --- src/check_builtin.cpp | 79 +++++++++++++++++++++++++------------------ src/check_type.cpp | 2 +- src/types.cpp | 3 ++ 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index a499937b2..e4fd504b3 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -447,7 +447,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call Type *elem = base_array_type(x.type); if (!is_type_integer(elem) && !is_type_float(elem)) { gbString xs = type_to_string(x.type); - error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating point element, got '%s'", LIT(builtin_name), xs); gb_string_free(xs); return false; } @@ -485,11 +485,21 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return false; } Type *elem = base_array_type(x.type); - if (!is_type_integer(elem)) { - gbString xs = type_to_string(x.type); - error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); - gb_string_free(xs); - return false; + + if (id == BuiltinProc_simd_rem) { + if (!is_type_integer(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + } else { + if (!is_type_integer(elem) && !is_type_boolean(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or boolean element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } } operand->mode = Addressing_Value; @@ -497,10 +507,10 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } - case BuiltinProc_simd_shl: - case BuiltinProc_simd_shr: - case BuiltinProc_simd_shl_masked: - case BuiltinProc_simd_shr_masked: + case BuiltinProc_simd_shl: // Odin-like + case BuiltinProc_simd_shr: // Odin-like + case BuiltinProc_simd_shl_masked: // C-like + case BuiltinProc_simd_shr_masked: // C-like { Operand x = {}; Operand y = {}; @@ -561,7 +571,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call Type *elem = base_array_type(x.type); if (!is_type_integer(elem) && !is_type_float(elem)) { gbString xs = type_to_string(x.type); - error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating point element, got '%s'", LIT(builtin_name), xs); gb_string_free(xs); return false; } @@ -592,12 +602,27 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return false; } Type *elem = base_array_type(x.type); - if (!is_type_integer(elem) && !is_type_float(elem)) { - gbString xs = type_to_string(x.type); - error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); - gb_string_free(xs); - return false; + switch (id) { + case BuiltinProc_simd_eq: + case BuiltinProc_simd_ne: + if (!is_type_integer(elem) && !is_type_float(elem) && !is_type_boolean(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer, floating point, or boolean element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + break; + default: + if (!is_type_integer(elem) && !is_type_float(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating point element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + break; } + + Type *vt = base_type(x.type); GB_ASSERT(vt->kind == Type_SimdVector); i64 count = vt->SimdVector.count; @@ -630,12 +655,6 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return false; } Type *elem = base_array_type(x.type); - if (!is_type_integer(elem) && !is_type_float(elem)) { - gbString xs = type_to_string(x.type); - error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); - gb_string_free(xs); - return false; - } i64 max_count = x.type->SimdVector.count; i64 value = -1; if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) { @@ -661,12 +680,6 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return false; } Type *elem = base_array_type(x.type); - if (!is_type_integer(elem) && !is_type_float(elem)) { - gbString xs = type_to_string(x.type); - error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); - gb_string_free(xs); - return false; - } i64 max_count = x.type->SimdVector.count; i64 value = -1; if (!check_index_value(c, x.type, false, ce->args[1], max_count, &value)) { @@ -710,7 +723,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call Type *elem = base_array_type(x.type); if (!is_type_integer(elem) && !is_type_float(elem)) { gbString xs = type_to_string(x.type); - error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating point element, got '%s'", LIT(builtin_name), xs); gb_string_free(xs); return false; } @@ -732,9 +745,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return false; } Type *elem = base_array_type(x.type); - if (!is_type_integer(elem)) { + if (!is_type_integer(elem) && !is_type_boolean(elem)) { gbString xs = type_to_string(x.type); - error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); + error(x.expr, "'%.*s' expected a #simd type with an integer or boolean element, got '%s'", LIT(builtin_name), xs); gb_string_free(xs); return false; } @@ -757,7 +770,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call // Type *elem = base_array_type(x.type); // if (!is_type_integer(elem) && !is_type_float(elem)) { // gbString xs = type_to_string(x.type); - // error(x.expr, "'%.*s' expected a #simd type with an integer or floating-point element, got '%s'", LIT(builtin_name), xs); + // error(x.expr, "'%.*s' expected a #simd type with an integer or floating point element, got '%s'", LIT(builtin_name), xs); // gb_string_free(xs); // return false; // } @@ -3102,7 +3115,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 Type *elem = y.type; if (!is_type_valid_vector_elem(elem)) { gbString str = type_to_string(elem); - error(call, "Invalid element type for 'intrinsics.simd_vector', expected an integer or float with no specific endianness, got '%s'", str); + error(call, "Invalid element type for 'intrinsics.simd_vector', expected an integer, float, or boolean with no specific endianness, got '%s'", str); gb_string_free(str); operand->mode = Addressing_Type; operand->type = t_invalid; diff --git a/src/check_type.cpp b/src/check_type.cpp index 354ab6e94..540413e32 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2797,7 +2797,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t } else if (name == "simd") { if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) { gbString str = type_to_string(elem); - error(at->elem, "Invalid element type for 'intrinsics.simd_vector', expected an integer or float with no specific endianness, got '%s'", str); + error(at->elem, "Invalid element type for 'intrinsics.simd_vector', expected an integer, float, or boolean with no specific endianness, got '%s'", str); gb_string_free(str); *type = alloc_type_array(elem, count, generic_type); goto array_end; diff --git a/src/types.cpp b/src/types.cpp index 2d5709b19..4fca25e52 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1939,6 +1939,9 @@ bool is_type_valid_vector_elem(Type *t) { if (is_type_float(t)) { return true; } + if (is_type_boolean(t)) { + return true; + } } return false; } From 140c00aa0cdeac6d1149db3845cc9f3433140cf9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 23:01:33 +0100 Subject: [PATCH 168/254] `intrinsics.simd_shuffle` --- core/simd/simd.odin | 24 +++++++++++++ src/check_builtin.cpp | 64 +++++++++++++++++++++++++++++++++++ src/check_type.cpp | 10 ++++-- src/checker_builtin_procs.hpp | 4 +++ src/llvm_backend_proc.cpp | 45 +++++++++++++++++++----- 5 files changed, 137 insertions(+), 10 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index ad14855bd..08839fd23 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -1,7 +1,28 @@ package simd +import "core:builtin" import "core:intrinsics" +// boolx16 :: #simd[16]bool +// b8x16 :: #simd[16]b8 +// b16x8 :: #simd[8]b16 +// b32x4 :: #simd[4]b32 +// b64x2 :: #simd[2]b64 + +// u8x16 :: #simd[16]u8 +// i8x16 :: #simd[16]i8 +// u16x8 :: #simd[8]u16 +// i16x8 :: #simd[8]i16 +// u32x4 :: #simd[4]u32 +// i32x4 :: #simd[4]i32 +// u64x2 :: #simd[2]u64 +// i64x2 :: #simd[2]i64 + +// f16x8 :: #simd[8]f16 +// f32x4 :: #simd[4]f32 +// f64x2 :: #simd[2]f64 + + add :: intrinsics.simd_add sub :: intrinsics.simd_sub mul :: intrinsics.simd_mul @@ -42,6 +63,9 @@ reduce_and :: intrinsics.simd_reduce_and reduce_or :: intrinsics.simd_reduce_or reduce_xor :: intrinsics.simd_reduce_xor +swizzle :: builtin.swizzle +shuffle :: intrinsics.simd_shuffle + splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { return T{0..args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &y, x.type); + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!is_type_simd_vector(y.type)) { + error(y.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!are_types_identical(x.type, y.type)) { + gbString xs = type_to_string(x.type); + gbString ys = type_to_string(y.type); + error(x.expr, "'%.*s' expected 2 arguments of the same type, got '%s' vs '%s'", LIT(builtin_name), xs, ys); + gb_string_free(ys); + gb_string_free(xs); + return false; + } + Type *elem = base_array_type(x.type); + + check_expr(c, &z, ce->args[2]); if (z.mode == Addressing_Invalid) { return false; } + Type *z_elem = base_array_type(z.type); + if (!is_type_simd_vector(z.type) || !are_types_identical(z_elem, t_u32)) { + gbString zstr = type_to_string(z.type); + error(z.expr, "'%.*s' expected a simd vector type with an element of type 'u32', got '%s'", LIT(builtin_name), zstr); + gb_string_free(zstr); + return false; + } + + i64 x_count = x.type->SimdVector.count; + i64 z_count = z.type->SimdVector.count; + + if (!is_power_of_two(z_count)) { + gbString zstr = type_to_string(z.type); + error(z.expr, "'%.*s' expected a simd vector type with a power of two length, got '%s'", LIT(builtin_name), zstr); + gb_string_free(zstr); + return false; + } + if (z_count > x_count) { + gbString zstr = type_to_string(z.type); + error(z.expr, "'%.*s' expected a simd vector type excepts the sum of the two input vectors, got '%s'", LIT(builtin_name), zstr); + gb_string_free(zstr); + return false; + } + + + operand->mode = Addressing_Value; + operand->type = alloc_type_simd_vector(z_count, elem); + return true; + } + + // case BuiltinProc_simd_rotate_left: // { // Operand x = {}; @@ -3131,6 +3189,12 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 operand->mode = Addressing_Type; operand->type = alloc_type_simd_vector(count, elem); + if (is_arch_wasm()) { + if (type_size_of(operand->type) != 16) { + error(x.expr, "wasm based targets are limited to 128-bit types"); + } + } + break; } diff --git a/src/check_type.cpp b/src/check_type.cpp index 540413e32..74fa235d5 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2802,15 +2802,21 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t *type = alloc_type_array(elem, count, generic_type); goto array_end; } + if (is_type_polymorphic(elem)) { // Ignore } else if (count < 1 || !is_power_of_two(count)) { error(at->count, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); *type = alloc_type_array(elem, count, generic_type); goto array_end; - } - + } else *type = alloc_type_simd_vector(count, elem, generic_type); + + if (is_arch_wasm()) { + if (type_size_of(*type) != 16) { + error(at->count, "wasm based targets are limited to 128-bit types"); + } + } } else { error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name)); *type = alloc_type_array(elem, count, generic_type); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 98cc9f284..722bbec84 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -157,6 +157,8 @@ BuiltinProc__simd_begin, BuiltinProc_simd_reduce_and, BuiltinProc_simd_reduce_or, BuiltinProc_simd_reduce_xor, + + BuiltinProc_simd_shuffle, BuiltinProc__simd_end, // Platform specific intrinsics @@ -417,6 +419,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_reduce_and"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_or"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_xor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_shuffle"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index c09265e7a..1b5d15d9b 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -981,6 +981,24 @@ lbValue lb_emit_call(lbProcedure *p, lbValue value, Array const &args, return result; } +LLVMValueRef llvm_splat_float(i64 count, LLVMTypeRef type, f64 value) { + LLVMValueRef v = LLVMConstReal(type, value); + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); + for (i64 i = 0; i < count; i++) { + values[i] = v; + } + return LLVMConstVector(values, cast(unsigned)count); +} +LLVMValueRef llvm_splat_int(i64 count, LLVMTypeRef type, i64 value, bool is_signed=false) { + LLVMValueRef v = LLVMConstInt(type, value, is_signed); + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); + for (i64 i = 0; i < count; i++) { + values[i] = v; + } + return LLVMConstVector(values, cast(unsigned)count); +} + + lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, BuiltinProcId builtin_id) { ast_node(ce, CallExpr, expr); @@ -1060,12 +1078,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_shr_masked: op_code = is_signed ? LLVMAShr : LLVMLShr; is_masked = true; break; } if (op_code) { - LLVMValueRef bit_value = lb_const_int(m, elem1, sz*8 - 1).value; - LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); - for (i64 i = 0; i < count; i++) { - values[i] = bit_value; - } - LLVMValueRef bits = LLVMConstVector(values, cast(unsigned)count); + LLVMValueRef bits = llvm_splat_int(count, lb_type(m, elem1), sz*8 - 1); if (is_masked) { // C logic LLVMValueRef shift = LLVMBuildAnd(p->builder, arg1.value, bits, ""); @@ -1077,7 +1090,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const LLVMValueRef shift = LLVMBuildBinOp(p->builder, op_code, arg0.value, arg1.value, ""); res.value = LLVMBuildSelect(p->builder, mask, shift, zero, ""); } - return res; } } @@ -1264,7 +1276,24 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const lbValue res = {}; res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); - res.type = tv.type; + return res; + } + + case BuiltinProc_simd_shuffle: + { + arg1 = lb_build_expr(p, ce->args[1]); + arg2 = lb_build_expr(p, ce->args[2]); + + Type *vt = arg0.type; + GB_ASSERT(vt->kind == Type_SimdVector); + + LLVMValueRef mask = arg2.value; + + i64 max_count = vt->SimdVector.count*2; + LLVMValueRef max_mask = llvm_splat_int(max_count, lb_type(m, arg2.type->SimdVector.elem), max_count-1); + mask = LLVMBuildAnd(p->builder, mask, max_mask, ""); + + res.value = LLVMBuildShuffleVector(p->builder, arg0.value, arg1.value, mask, ""); return res; } } From 09f936b04db2be7d30f695fe050ba57ac6d6da3d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 23:24:32 +0100 Subject: [PATCH 169/254] Correct casting between integer and boolean #simd --- src/llvm_backend_expr.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 1b10cd776..426becc1c 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1842,10 +1842,13 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } else { res.value = LLVMBuildSIToFP(p->builder, value.value, lb_type(m, t), ""); } - } else if (is_type_integer(src_elem) && is_type_integer(dst_elem)) { + } else if ((is_type_integer(src_elem) || is_type_boolean(src_elem)) && is_type_integer(dst_elem)) { res.value = LLVMBuildIntCast2(p->builder, value.value, lb_type(m, t), !is_type_unsigned(src_elem), ""); } else if (is_type_float(src_elem) && is_type_float(dst_elem)) { res.value = LLVMBuildFPCast(p->builder, value.value, lb_type(m, t), ""); + } else if (is_type_integer(src_elem) && is_type_boolean(dst_elem)) { + LLVMValueRef i1vector = LLVMBuildICmp(p->builder, LLVMIntNE, value.value, LLVMConstNull(LLVMTypeOf(value.value)), ""); + res.value = LLVMBuildIntCast2(p->builder, i1vector, lb_type(m, t), !is_type_unsigned(src_elem), ""); } else { GB_PANIC("Unhandled simd vector conversion: %s -> %s", type_to_string(src), type_to_string(dst)); } From 57e69ea3922ce56b3c959e60aeeaa032ab81ff8d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 23:24:42 +0100 Subject: [PATCH 170/254] Add comments --- core/simd/simd.odin | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 08839fd23..b5207e154 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -42,16 +42,25 @@ shr_masked :: intrinsics.simd_shr_masked and :: intrinsics.simd_and or :: intrinsics.simd_or xor :: intrinsics.simd_xor + neg :: intrinsics.simd_neg + abs :: intrinsics.simd_abs min :: intrinsics.simd_min max :: intrinsics.simd_max -eq :: intrinsics.simd_eq -ne :: intrinsics.simd_ne -lt :: intrinsics.simd_lt -le :: intrinsics.simd_le -gt :: intrinsics.simd_gt -ge :: intrinsics.simd_ge + +// Return an unsigned integer of the same size as the input type +// NOT A BOOLEAN +// element-wise: +// false => 0x00...00 +// true => 0xff...ff +eq :: intrinsics.simd_eq +ne :: intrinsics.simd_ne +lt :: intrinsics.simd_lt +le :: intrinsics.simd_le +gt :: intrinsics.simd_gt +ge :: intrinsics.simd_ge + extract :: intrinsics.simd_extract replace :: intrinsics.simd_replace From 7002c94a63c58aa0ac5a5d74b1fffd1511aeb699 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 23:34:41 +0100 Subject: [PATCH 171/254] Add `intrinsics.simd_select` --- core/simd/simd.odin | 1 + src/check_builtin.cpp | 51 +++++++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 2 ++ src/llvm_backend_proc.cpp | 12 +++++++++ 4 files changed, 66 insertions(+) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index b5207e154..1819f3951 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -74,6 +74,7 @@ reduce_xor :: intrinsics.simd_reduce_xor swizzle :: builtin.swizzle shuffle :: intrinsics.simd_shuffle +select :: intrinsics.simd_select splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { return T{0..args[0]); if (cond.mode == Addressing_Invalid) { return false; } + + if (!is_type_simd_vector(cond.type)) { + error(cond.expr, "'%.*s' expected a simd vector boolean type", LIT(builtin_name)); + return false; + } + if (!is_type_boolean(base_array_type(cond.type))) { + error(cond.expr, "'%.*s' expected a simd vector boolean type", LIT(builtin_name)); + return false; + } + + Operand x = {}; + Operand y = {}; + check_expr(c, &x, ce->args[1]); if (x.mode == Addressing_Invalid) { return false; } + check_expr_with_type_hint(c, &y, ce->args[2], x.type); if (y.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &y, x.type); + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!is_type_simd_vector(y.type)) { + error(y.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!are_types_identical(x.type, y.type)) { + gbString xs = type_to_string(x.type); + gbString ys = type_to_string(y.type); + error(x.expr, "'%.*s' expected 2 results of the same type, got '%s' vs '%s'", LIT(builtin_name), xs, ys); + gb_string_free(ys); + gb_string_free(xs); + return false; + } + + if (cond.type->SimdVector.count != x.type->SimdVector.count) { + error(x.expr, "'%.*s' expected condition vector to match the length of the result lengths, got '%lld' vs '%lld'", + LIT(builtin_name), + cast(long long)cond.type->SimdVector.count, + cast(long long)x.type->SimdVector.count); + return false; + } + + + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + + // case BuiltinProc_simd_rotate_left: // { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 722bbec84..2fb355e91 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -159,6 +159,7 @@ BuiltinProc__simd_begin, BuiltinProc_simd_reduce_xor, BuiltinProc_simd_shuffle, + BuiltinProc_simd_select, BuiltinProc__simd_end, // Platform specific intrinsics @@ -421,6 +422,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_reduce_xor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shuffle"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_select"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 1b5d15d9b..7a86427d4 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1296,6 +1296,18 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const res.value = LLVMBuildShuffleVector(p->builder, arg0.value, arg1.value, mask, ""); return res; } + + case BuiltinProc_simd_select: + { + LLVMValueRef cond = arg0.value; + LLVMValueRef x = lb_build_expr(p, ce->args[1]).value; + LLVMValueRef y = lb_build_expr(p, ce->args[2]).value; + + cond = LLVMBuildICmp(p->builder, LLVMIntNE, cond, LLVMConstNull(LLVMTypeOf(cond)), ""); + res.value = LLVMBuildSelect(p->builder, cond, x, y, ""); + return res; + } + } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); From 12d19d21c4a77bce5ff4acd8f0184e72709fb364 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 23:40:59 +0100 Subject: [PATCH 172/254] Document simd stuff in intrinsics.odin --- core/intrinsics/intrinsics.odin | 54 +++++++++++++++++++++++++++++++++ core/simd/simd.odin | 7 +++++ 2 files changed, 61 insertions(+) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index d71522936..a24f1d868 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -186,6 +186,60 @@ type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawpt constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- +// SIMD related +simd_add :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_sub :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_mul :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_div :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_rem :: proc(a, b: #simd[N]T) -> #simd[N]T --- + +// Keeps Odin's Behaviour +// (x << y) if y <= mask else 0 +simd_shl :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- +simd_shr :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- + +// Similar to C's Behaviour +// x << (y & mask) +simd_shl_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- +simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- + +simd_and :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_or :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_xor :: proc(a, b: #simd[N]T) -> #simd[N]T --- + +simd_neg :: proc(a: #simd[N]T) -> #simd[N]T --- + +simd_abs :: proc(a: #simd[N]T) -> #simd[N]T --- +simd_min :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_max :: proc(a, b: #simd[N]T) -> #simd[N]T --- + +// Return an unsigned integer of the same size as the input type +// NOT A BOOLEAN +// element-wise: +// false => 0x00...00 +// true => 0xff...ff +simd_eq :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_ne :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_lt :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_le :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_gt :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- + +simd_extract :: proc(a: #simd[N]T, idx: uint) -> T --- +simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T --- + +simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T --- +simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T --- +simd_reduce_min :: proc(a: #simd[N]T) -> T --- +simd_reduce_max :: proc(a: #simd[N]T) -> T --- +simd_reduce_and :: proc(a: #simd[N]T) -> T --- +simd_reduce_or :: proc(a: #simd[N]T) -> T --- +simd_reduce_xor :: proc(a: #simd[N]T) -> T --- + +simd_shuffle :: proc(a, b: #simd[N]T, indices: #simd[max 2*N]u32) -> #simd[len(indices)]T --- +simd_select :: proc(cond: #simd[N]any_boolean, true, false: #simd[N]T) -> #simd[N]T --- + + // WASM targets only wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_size :: proc(index: uintptr) -> int --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 1819f3951..c5473a92c 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -61,7 +61,9 @@ le :: intrinsics.simd_le gt :: intrinsics.simd_gt ge :: intrinsics.simd_ge +// extract :: proc(a: #simd[N]T, idx: uint) -> T extract :: intrinsics.simd_extract +// replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T replace :: intrinsics.simd_replace reduce_add_ordered :: intrinsics.simd_reduce_add_ordered @@ -72,8 +74,13 @@ reduce_and :: intrinsics.simd_reduce_and reduce_or :: intrinsics.simd_reduce_or reduce_xor :: intrinsics.simd_reduce_xor +// swizzle :: proc(a: #simd[N]T, indices: ..int) -> #simd[len(indices)]T swizzle :: builtin.swizzle + +// shuffle :: proc(a, b: #simd[N]T, indices: #simd[max 2*N]u32) -> #simd[len(indices)]T shuffle :: intrinsics.simd_shuffle + +// select :: proc(cond: #simd[N]any_boolean, true, false: #simd[N]T) -> #simd[N]T select :: intrinsics.simd_select splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { From 8e57511ffa58e9198ceab9e0e458ce6f132d37d1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 23:42:25 +0100 Subject: [PATCH 173/254] Minor clean up --- core/simd/simd.odin | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index c5473a92c..ab0d9937b 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -23,31 +23,31 @@ import "core:intrinsics" // f64x2 :: #simd[2]f64 -add :: intrinsics.simd_add -sub :: intrinsics.simd_sub -mul :: intrinsics.simd_mul -div :: intrinsics.simd_div -rem :: intrinsics.simd_rem +add :: intrinsics.simd_add +sub :: intrinsics.simd_sub +mul :: intrinsics.simd_mul +div :: intrinsics.simd_div +rem :: intrinsics.simd_rem // Keeps Odin's Behaviour // (x << y) if y <= mask else 0 -shl :: intrinsics.simd_shl -shr :: intrinsics.simd_shr +shl :: intrinsics.simd_shl +shr :: intrinsics.simd_shr // Similar to C's Behaviour // x << (y & mask) shl_masked :: intrinsics.simd_shl_masked shr_masked :: intrinsics.simd_shr_masked -and :: intrinsics.simd_and -or :: intrinsics.simd_or -xor :: intrinsics.simd_xor +and :: intrinsics.simd_and +or :: intrinsics.simd_or +xor :: intrinsics.simd_xor -neg :: intrinsics.simd_neg +neg :: intrinsics.simd_neg -abs :: intrinsics.simd_abs -min :: intrinsics.simd_min -max :: intrinsics.simd_max +abs :: intrinsics.simd_abs +min :: intrinsics.simd_min +max :: intrinsics.simd_max // Return an unsigned integer of the same size as the input type // NOT A BOOLEAN From c2f5cbdeb48e49d25dc75c1fcc02ce688dc85e26 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 25 May 2022 23:49:23 +0100 Subject: [PATCH 174/254] Allow integer vectors in select --- core/intrinsics/intrinsics.odin | 2 +- core/simd/simd.odin | 2 +- src/check_builtin.cpp | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index a24f1d868..8f2ebce13 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -237,7 +237,7 @@ simd_reduce_or :: proc(a: #simd[N]T) -> T --- simd_reduce_xor :: proc(a: #simd[N]T) -> T --- simd_shuffle :: proc(a, b: #simd[N]T, indices: #simd[max 2*N]u32) -> #simd[len(indices)]T --- -simd_select :: proc(cond: #simd[N]any_boolean, true, false: #simd[N]T) -> #simd[N]T --- +simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- // WASM targets only diff --git a/core/simd/simd.odin b/core/simd/simd.odin index ab0d9937b..b26719d56 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -80,7 +80,7 @@ swizzle :: builtin.swizzle // shuffle :: proc(a, b: #simd[N]T, indices: #simd[max 2*N]u32) -> #simd[len(indices)]T shuffle :: intrinsics.simd_shuffle -// select :: proc(cond: #simd[N]any_boolean, true, false: #simd[N]T) -> #simd[N]T +// select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T select :: intrinsics.simd_select splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index eaf71fdab..add719280 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -824,8 +824,11 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call error(cond.expr, "'%.*s' expected a simd vector boolean type", LIT(builtin_name)); return false; } - if (!is_type_boolean(base_array_type(cond.type))) { - error(cond.expr, "'%.*s' expected a simd vector boolean type", LIT(builtin_name)); + Type *cond_elem = base_array_type(cond.type); + if (!is_type_boolean(cond_elem) && !is_type_integer(cond_elem)) { + gbString cond_str = type_to_string(cond.type); + error(cond.expr, "'%.*s' expected a simd vector boolean or integer type, got '%s'", LIT(builtin_name), cond_str); + gb_string_free(cond_str); return false; } From cde6a2f7a5e5ea0676f9732342f10169baa64c52 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 00:36:24 +0100 Subject: [PATCH 175/254] Make `simd_shuffle` act closer to `swizzle` --- core/intrinsics/intrinsics.odin | 2 +- core/simd/simd.odin | 4 +- src/check_builtin.cpp | 95 +++++++++++++++------------------ src/checker_builtin_procs.hpp | 2 +- src/llvm_backend_proc.cpp | 14 +++-- 5 files changed, 59 insertions(+), 58 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 8f2ebce13..097496d47 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -236,7 +236,7 @@ simd_reduce_and :: proc(a: #simd[N]T) -> T --- simd_reduce_or :: proc(a: #simd[N]T) -> T --- simd_reduce_xor :: proc(a: #simd[N]T) -> T --- -simd_shuffle :: proc(a, b: #simd[N]T, indices: #simd[max 2*N]u32) -> #simd[len(indices)]T --- +simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T --- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index b26719d56..e81f82341 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -93,11 +93,11 @@ to_array_ptr :: #force_inline proc "contextless" (v: ^#simd[$LANES]$E) -> ^[LANE to_array :: #force_inline proc "contextless" (v: #simd[$LANES]$E) -> [LANES]E { return transmute([LANES]E)(v) } -from_array :: #force_inline proc "contextless" (v: $A/[$LANES]$E) -> #simd[LANES]E where LANES & (LANES-1) == 0 { +from_array :: #force_inline proc "contextless" (v: $A/[$LANES]$E) -> #simd[LANES]E { return transmute(#simd[LANES]E)v } -from_slice :: proc($T: typeid/#simd[$LANES]$E, slice: []E) -> T where LANES & (LANES-1) == 0 { +from_slice :: proc($T: typeid/#simd[$LANES]$E, slice: []E) -> T { assert(len(slice) >= LANES, "slice length must be a least the number of lanes") array: [LANES]E #no_bounds_check for i in 0..args[0]); if (x.mode == Addressing_Invalid) { return false; } check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } convert_to_typed(c, &y, x.type); @@ -784,34 +783,53 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call } Type *elem = base_array_type(x.type); - check_expr(c, &z, ce->args[2]); if (z.mode == Addressing_Invalid) { return false; } - Type *z_elem = base_array_type(z.type); - if (!is_type_simd_vector(z.type) || !are_types_identical(z_elem, t_u32)) { - gbString zstr = type_to_string(z.type); - error(z.expr, "'%.*s' expected a simd vector type with an element of type 'u32', got '%s'", LIT(builtin_name), zstr); - gb_string_free(zstr); + i64 max_count = x.type->SimdVector.count + y.type->SimdVector.count; + + i64 arg_count = 0; + for_array(i, ce->args) { + if (i < 2) { + continue; + } + Ast *arg = ce->args[i]; + Operand op = {}; + check_expr(c, &op, arg); + if (op.mode == Addressing_Invalid) { + return false; + } + Type *arg_type = base_type(op.type); + if (!is_type_integer(arg_type) || op.mode != Addressing_Constant) { + error(op.expr, "Indices to '%.*s' must be constant integers", LIT(builtin_name)); + return false; + } + + if (big_int_is_neg(&op.value.value_integer)) { + error(op.expr, "Negative '%.*s' index", LIT(builtin_name)); + return false; + } + + BigInt mc = {}; + big_int_from_i64(&mc, max_count); + if (big_int_cmp(&mc, &op.value.value_integer) <= 0) { + error(op.expr, "'%.*s' index exceeds length", LIT(builtin_name)); + return false; + } + + arg_count++; + } + + if (arg_count > max_count) { + error(call, "Too many '%.*s' indices, %td > %td", LIT(builtin_name), arg_count, max_count); return false; } - i64 x_count = x.type->SimdVector.count; - i64 z_count = z.type->SimdVector.count; - if (!is_power_of_two(z_count)) { - gbString zstr = type_to_string(z.type); - error(z.expr, "'%.*s' expected a simd vector type with a power of two length, got '%s'", LIT(builtin_name), zstr); - gb_string_free(zstr); + if (!is_power_of_two(arg_count)) { + error(call, "'%.*s' must have a power of two index arguments, got %lld", LIT(builtin_name), cast(long long)arg_count); return false; } - if (z_count > x_count) { - gbString zstr = type_to_string(z.type); - error(z.expr, "'%.*s' expected a simd vector type excepts the sum of the two input vectors, got '%s'", LIT(builtin_name), zstr); - gb_string_free(zstr); - return false; - } - operand->mode = Addressing_Value; - operand->type = alloc_type_simd_vector(z_count, elem); + operand->type = alloc_type_simd_vector(arg_count, elem); return true; } @@ -869,36 +887,6 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call } - - // case BuiltinProc_simd_rotate_left: - // { - // Operand x = {}; - // check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } - - // if (!is_type_simd_vector(x.type)) { - // error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); - // return false; - // } - // Type *elem = base_array_type(x.type); - // if (!is_type_integer(elem) && !is_type_float(elem)) { - // gbString xs = type_to_string(x.type); - // error(x.expr, "'%.*s' expected a #simd type with an integer or floating point element, got '%s'", LIT(builtin_name), xs); - // gb_string_free(xs); - // return false; - // } - - // Operand offset = {}; - // check_expr_with_type_hint(c, &offset, ce->args[1]); if (x.mode == Addressing_Invalid) { return false; } - // convert_to_typed(c, &offset, t_int); - // if (offset.mode != Addressing_Constant) { - // error(offset.expr, "'%.*s' expected a constant integer for the offset", LIT(builtin_name)); - // return false; - // } - - // operand->mode = Addressing_Value; - // operand->type = x.type; - // return true - // } default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); } @@ -1540,6 +1528,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 bt->Struct.soa_kind == StructSoa_Dynamic) { mode = Addressing_Value; } + } else if (is_type_simd_vector(op_type)) { + Type *bt = base_type(op_type); + mode = Addressing_Constant; + value = exact_value_i64(bt->SimdVector.count); + type = t_untyped_integer; } if (operand->mode == Addressing_Type && mode != Addressing_Constant) { mode = Addressing_Invalid; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 2fb355e91..660208eb0 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -421,7 +421,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_reduce_or"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reduce_xor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_shuffle"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_shuffle"), 2, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_select"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 7a86427d4..2bffa111c 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1282,15 +1282,23 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_shuffle: { arg1 = lb_build_expr(p, ce->args[1]); - arg2 = lb_build_expr(p, ce->args[2]); Type *vt = arg0.type; GB_ASSERT(vt->kind == Type_SimdVector); - LLVMValueRef mask = arg2.value; + i64 mask_count = ce->args.count-2; i64 max_count = vt->SimdVector.count*2; - LLVMValueRef max_mask = llvm_splat_int(max_count, lb_type(m, arg2.type->SimdVector.elem), max_count-1); + + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, mask_count); + for (isize i = 0; i < max_count; i++) { + lbValue idx = lb_build_expr(p, ce->args[i+2]); + GB_ASSERT(LLVMIsConstant(idx.value)); + values[i] = idx.value; + } + LLVMValueRef mask = LLVMConstVector(values, cast(unsigned)mask_count); + + LLVMValueRef max_mask = llvm_splat_int(mask_count, lb_type(m, t_u32), max_count-1); mask = LLVMBuildAnd(p->builder, mask, max_mask, ""); res.value = LLVMBuildShuffleVector(p->builder, arg0.value, arg1.value, mask, ""); From 83d880a94a783e2c78c4ab0c1c2f1740c8ceac72 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 00:37:48 +0100 Subject: [PATCH 176/254] Remove unneeded mask --- src/llvm_backend_proc.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 2bffa111c..1ec71bc21 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1287,21 +1287,18 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const GB_ASSERT(vt->kind == Type_SimdVector); - i64 mask_count = ce->args.count-2; + i64 indices_count = ce->args.count-2; i64 max_count = vt->SimdVector.count*2; - LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, mask_count); + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, indices_count); for (isize i = 0; i < max_count; i++) { lbValue idx = lb_build_expr(p, ce->args[i+2]); GB_ASSERT(LLVMIsConstant(idx.value)); values[i] = idx.value; } - LLVMValueRef mask = LLVMConstVector(values, cast(unsigned)mask_count); + LLVMValueRef indices = LLVMConstVector(values, cast(unsigned)indices_count); - LLVMValueRef max_mask = llvm_splat_int(mask_count, lb_type(m, t_u32), max_count-1); - mask = LLVMBuildAnd(p->builder, mask, max_mask, ""); - - res.value = LLVMBuildShuffleVector(p->builder, arg0.value, arg1.value, mask, ""); + res.value = LLVMBuildShuffleVector(p->builder, arg0.value, arg1.value, indices, ""); return res; } From b95ca80f85efc3f7be292d9c751a742e43e41b2e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 00:39:34 +0100 Subject: [PATCH 177/254] Fix `simd_shuffle` --- src/llvm_backend_proc.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 1ec71bc21..05477d84b 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1289,9 +1289,10 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const i64 indices_count = ce->args.count-2; i64 max_count = vt->SimdVector.count*2; + GB_ASSERT(indices_count <= max_count); LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, indices_count); - for (isize i = 0; i < max_count; i++) { + for (isize i = 0; i < indices_count; i++) { lbValue idx = lb_build_expr(p, ce->args[i+2]); GB_ASSERT(LLVMIsConstant(idx.value)); values[i] = idx.value; From 10deb2e88b007504423f962942872e76c305553a Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 25 May 2022 21:51:36 -0400 Subject: [PATCH 178/254] fix mmap call in virtual_linux.odin --- core/mem/virtual/virtual_linux.odin | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/mem/virtual/virtual_linux.odin b/core/mem/virtual/virtual_linux.odin index 6ae926e47..2f6fbdd01 100644 --- a/core/mem/virtual/virtual_linux.odin +++ b/core/mem/virtual/virtual_linux.odin @@ -37,9 +37,9 @@ MADV_WIPEONFORK :: 18 MADV_KEEPONFORK :: 19 MADV_HWPOISON :: 100 -mmap :: proc "contextless" (addr: rawptr, length: uint, prot: c.int, flags: c.int, fd: c.int, offset: uintptr) -> rawptr { +mmap :: proc "contextless" (addr: rawptr, length: uint, prot: c.int, flags: c.int, fd: c.int, offset: uintptr) -> int { res := intrinsics.syscall(unix.SYS_mmap, uintptr(addr), uintptr(length), uintptr(prot), uintptr(flags), uintptr(fd), offset) - return rawptr(res) + return int(res) } munmap :: proc "contextless" (addr: rawptr, length: uint) -> c.int { @@ -59,12 +59,11 @@ madvise :: proc "contextless" (addr: rawptr, length: uint, advice: c.int) -> c.i _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { - MAP_FAILED := rawptr(~uintptr(0)) result := mmap(nil, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) - if result == MAP_FAILED { + if result < 0 && result > -4096 { return nil, .Out_Of_Memory } - return ([^]byte)(result)[:size], nil + return ([^]byte)(uintptr(result))[:size], nil } _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { From 06337129d8de636ad00e8ea64218d48c67514611 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 10:38:51 +0100 Subject: [PATCH 179/254] Remove `intrinsics.odin.simd_vector` in favour of `#simd[N]T` --- core/intrinsics/intrinsics.odin | 1 - src/check_builtin.cpp | 53 --------------------------------- src/checker_builtin_procs.hpp | 2 -- 3 files changed, 56 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 097496d47..6a1ffe5a0 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -6,7 +6,6 @@ package intrinsics is_package_imported :: proc(package_name: string) -> bool --- // Types -simd_vector :: proc($N: int, $T: typeid) -> type/#simd[N]T soa_struct :: proc($N: int, $T: typeid) -> type/#soa[N]T // Volatile diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 334202230..40933fcaa 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3192,59 +3192,6 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; } - case BuiltinProc_simd_vector: { - Operand x = {}; - Operand y = {}; - x = *operand; - if (!is_type_integer(x.type) || x.mode != Addressing_Constant) { - error(call, "Expected a constant integer for 'intrinsics.simd_vector'"); - operand->mode = Addressing_Type; - operand->type = t_invalid; - return false; - } - if (big_int_is_neg(&x.value.value_integer)) { - error(call, "Negative vector element length"); - operand->mode = Addressing_Type; - operand->type = t_invalid; - return false; - } - i64 count = big_int_to_i64(&x.value.value_integer); - - check_expr_or_type(c, &y, ce->args[1]); - if (y.mode != Addressing_Type) { - error(call, "Expected a type 'intrinsics.simd_vector'"); - operand->mode = Addressing_Type; - operand->type = t_invalid; - return false; - } - Type *elem = y.type; - if (!is_type_valid_vector_elem(elem)) { - gbString str = type_to_string(elem); - error(call, "Invalid element type for 'intrinsics.simd_vector', expected an integer, float, or boolean with no specific endianness, got '%s'", str); - gb_string_free(str); - operand->mode = Addressing_Type; - operand->type = t_invalid; - return false; - } - - if (count < 1 || !is_power_of_two(count)) { - error(call, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); - operand->mode = Addressing_Type; - operand->type = t_invalid; - return false; - } - - operand->mode = Addressing_Type; - operand->type = alloc_type_simd_vector(count, elem); - if (is_arch_wasm()) { - if (type_size_of(operand->type) != 16) { - error(x.expr, "wasm based targets are limited to 128-bit types"); - } - } - - break; - } - case BuiltinProc_is_package_imported: { bool value = false; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 660208eb0..0fff70f01 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -45,7 +45,6 @@ enum BuiltinProcId { // "Intrinsics" BuiltinProc_is_package_imported, - BuiltinProc_simd_vector, BuiltinProc_soa_struct, BuiltinProc_alloca, @@ -311,7 +310,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { // "Intrinsics" {STR_LIT("is_package_imported"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_vector"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, // Type {STR_LIT("soa_struct"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, // Type {STR_LIT("alloca"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, From 0fd43c1a0b697ea919efdeef42427694f32692bf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 11:02:02 +0100 Subject: [PATCH 180/254] Add simd.{sqrt, ceil, floor, trunc, nearest} --- core/intrinsics/intrinsics.odin | 6 ++++++ core/simd/simd.odin | 7 +++++++ src/check_builtin.cpp | 26 ++++++++++++++++++++++++++ src/check_type.cpp | 7 +++++-- src/checker_builtin_procs.hpp | 12 ++++++++++++ src/llvm_backend_proc.cpp | 30 +++++++++++++++++++++++++++--- src/types.cpp | 3 +++ 7 files changed, 86 insertions(+), 5 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 6a1ffe5a0..13a185da0 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -239,6 +239,12 @@ simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T -- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- +simd_sqrt :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +simd_ceil :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +simd_floor :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +simd_trunc :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- + // WASM targets only wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_size :: proc(index: uintptr) -> int --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index e81f82341..9c37c380c 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -83,6 +83,13 @@ shuffle :: intrinsics.simd_shuffle // select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T select :: intrinsics.simd_select + +sqrt :: intrinsics.simd_sqrt +ceil :: intrinsics.simd_ceil +floor :: intrinsics.simd_floor +trunc :: intrinsics.simd_trunc +nearest :: intrinsics.simd_nearest + splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { return T{0..args[0]); if (x.mode == Addressing_Invalid) { return false; } + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector boolean type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_float(elem)) { + gbString x_str = type_to_string(x.type); + error(x.expr, "'%.*s' expected a simd vector floating point type, got '%s'", LIT(builtin_name), x_str); + gb_string_free(x_str); + return false; + } + + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); diff --git a/src/check_type.cpp b/src/check_type.cpp index 74fa235d5..de58db054 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2797,7 +2797,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t } else if (name == "simd") { if (!is_type_valid_vector_elem(elem) && !is_type_polymorphic(elem)) { gbString str = type_to_string(elem); - error(at->elem, "Invalid element type for 'intrinsics.simd_vector', expected an integer, float, or boolean with no specific endianness, got '%s'", str); + error(at->elem, "Invalid element type for #simd, expected an integer, float, or boolean with no specific endianness, got '%s'", str); gb_string_free(str); *type = alloc_type_array(elem, count, generic_type); goto array_end; @@ -2806,7 +2806,7 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t if (is_type_polymorphic(elem)) { // Ignore } else if (count < 1 || !is_power_of_two(count)) { - error(at->count, "Invalid length for 'intrinsics.simd_vector', expected a power of two length, got '%lld'", cast(long long)count); + error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count); *type = alloc_type_array(elem, count, generic_type); goto array_end; } else @@ -2817,6 +2817,9 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t error(at->count, "wasm based targets are limited to 128-bit types"); } } + if (count > SIMD_ELEMENT_COUNT_MAX) { + error(at->count, "#simd support a maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)count); + } } else { error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name)); *type = alloc_type_array(elem, count, generic_type); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 0fff70f01..adb4e4624 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -159,6 +159,12 @@ BuiltinProc__simd_begin, BuiltinProc_simd_shuffle, BuiltinProc_simd_select, + + BuiltinProc_simd_sqrt, + BuiltinProc_simd_ceil, + BuiltinProc_simd_floor, + BuiltinProc_simd_trunc, + BuiltinProc_simd_nearest, BuiltinProc__simd_end, // Platform specific intrinsics @@ -421,6 +427,12 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_shuffle"), 2, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_select"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_sqrt") , 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_ceil") , 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_floor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_trunc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_nearest"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 05477d84b..88129ba5d 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1231,9 +1231,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); - lbValue res = {}; res.value = LLVMBuildCall(p->builder, ip, args, cast(unsigned)args_count, ""); - res.type = tv.type; return res; } case BuiltinProc_simd_reduce_min: @@ -1274,7 +1272,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const LLVMValueRef args[1] = {}; args[0] = arg0.value; - lbValue res = {}; res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); return res; } @@ -1314,6 +1311,33 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } + case BuiltinProc_simd_sqrt: + case BuiltinProc_simd_ceil: + case BuiltinProc_simd_floor: + case BuiltinProc_simd_trunc: + case BuiltinProc_simd_nearest: + { + char const *name = nullptr; + switch (builtin_id) { + case BuiltinProc_simd_sqrt: name = "llvm.sqrt"; break; + case BuiltinProc_simd_ceil: name = "llvm.ceil"; break; + case BuiltinProc_simd_floor: name = "llvm.floor"; break; + case BuiltinProc_simd_trunc: name = "llvm.trunc"; break; + case BuiltinProc_simd_nearest: name = "llvm.nearbyint"; break; + } + + LLVMTypeRef types[1] = {lb_type(p->module, arg0.type)}; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[1] = {}; + args[0] = arg0.value; + + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); diff --git a/src/types.cpp b/src/types.cpp index 4fca25e52..fccea2937 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -363,6 +363,9 @@ enum : int { MATRIX_ELEMENT_COUNT_MIN = 1, MATRIX_ELEMENT_COUNT_MAX = 16, MATRIX_ELEMENT_MAX_SIZE = MATRIX_ELEMENT_COUNT_MAX * (2 * 8), // complex128 + + SIMD_ELEMENT_COUNT_MIN = 1, + SIMD_ELEMENT_COUNT_MAX = 64, }; From 7ec0236fbf55939ef46662a732b00908730f826b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 11:14:22 +0100 Subject: [PATCH 181/254] Add `simd_reverse` --- core/intrinsics/intrinsics.odin | 2 ++ core/simd/simd.odin | 2 ++ src/check_builtin.cpp | 13 +++++++++++++ src/checker_builtin_procs.hpp | 4 ++++ src/llvm_backend_proc.cpp | 16 ++++++++++++++++ src/types.cpp | 2 ++ 6 files changed, 39 insertions(+) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 13a185da0..a14488210 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -245,6 +245,8 @@ simd_floor :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- simd_trunc :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T --- + // WASM targets only wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_size :: proc(index: uintptr) -> int --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 9c37c380c..060e32323 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -90,6 +90,8 @@ floor :: intrinsics.simd_floor trunc :: intrinsics.simd_trunc nearest :: intrinsics.simd_nearest +reverse :: intrinsics.simd_reverse + splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { return T{0..args[0]); if (x.mode == Addressing_Invalid) { return false; } + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + operand->type = x.type; + operand->mode = Addressing_Value; + return true; + } default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index adb4e4624..22ee3d141 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -165,6 +165,8 @@ BuiltinProc__simd_begin, BuiltinProc_simd_floor, BuiltinProc_simd_trunc, BuiltinProc_simd_nearest, + + BuiltinProc_simd_reverse, BuiltinProc__simd_end, // Platform specific intrinsics @@ -433,6 +435,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_floor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_trunc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_nearest"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_reverse"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 88129ba5d..42f5a60fa 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1338,6 +1338,22 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } + case BuiltinProc_simd_reverse: + { + i64 count = get_array_type_count(arg0.type); + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); + LLVMTypeRef llvm_u32 = lb_type(m, t_u32); + for (i64 i = 0; i < count; i++) { + values[i] = LLVMConstInt(llvm_u32, count-1-i, false); + } + LLVMValueRef mask = LLVMConstVector(values, cast(unsigned)count); + + LLVMValueRef v = arg0.value; + res.value = LLVMBuildShuffleVector(p->builder, v, v, mask, ""); + return res; + } + + } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); diff --git a/src/types.cpp b/src/types.cpp index fccea2937..6f61015d3 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1598,6 +1598,8 @@ i64 get_array_type_count(Type *t) { return bt->Array.count; } else if (bt->kind == Type_EnumeratedArray) { return bt->EnumeratedArray.count; + } else if (bt->kind == Type_SimdVector) { + return bt->SimdVector.count; } GB_ASSERT(is_type_array_like(t)); return -1; From 35502816c7d53b0b5fd422537d32efb5c34b1811 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 11:24:10 +0100 Subject: [PATCH 182/254] Add `simd_add_sat` `simd_sub_sat` --- core/intrinsics/intrinsics.odin | 9 +++++++-- core/simd/simd.odin | 4 ++++ src/check_builtin.cpp | 11 +++++++++-- src/checker_builtin_procs.hpp | 7 +++++++ src/llvm_backend_proc.cpp | 24 ++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index a14488210..7bbbc1efd 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -202,6 +202,9 @@ simd_shr :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- simd_shl_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T --- +simd_add_sat :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_sub_sat :: proc(a, b: #simd[N]T) -> #simd[N]T --- + simd_and :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_or :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_xor :: proc(a, b: #simd[N]T) -> #simd[N]T --- @@ -238,13 +241,15 @@ simd_reduce_xor :: proc(a: #simd[N]T) -> T --- simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T --- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- - -simd_sqrt :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +// Lane-wise operations +simd_sqrt :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- // IEEE sqrt simd_ceil :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- simd_floor :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- simd_trunc :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +// rounding to the nearest integral value; if two values are equally near, rounds to the even one simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +// equivalent a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0) simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T --- // WASM targets only diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 060e32323..2ce487d13 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -39,6 +39,10 @@ shr :: intrinsics.simd_shr shl_masked :: intrinsics.simd_shl_masked shr_masked :: intrinsics.simd_shr_masked +// Saturation Arithmetic +add_sat :: intrinsics.simd_add_sat +sub_sat :: intrinsics.simd_sub_sat + and :: intrinsics.simd_and or :: intrinsics.simd_or xor :: intrinsics.simd_xor diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index edf84b152..682667972 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -458,6 +458,8 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call } // Integer only + case BuiltinProc_simd_add_sat: + case BuiltinProc_simd_sub_sat: case BuiltinProc_simd_rem: case BuiltinProc_simd_and: case BuiltinProc_simd_or: @@ -486,20 +488,25 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call } Type *elem = base_array_type(x.type); - if (id == BuiltinProc_simd_rem) { + switch (id) { + case BuiltinProc_simd_add_sat: + case BuiltinProc_simd_sub_sat: + case BuiltinProc_simd_rem: if (!is_type_integer(elem)) { gbString xs = type_to_string(x.type); error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); gb_string_free(xs); return false; } - } else { + break; + default: if (!is_type_integer(elem) && !is_type_boolean(elem)) { gbString xs = type_to_string(x.type); error(x.expr, "'%.*s' expected a #simd type with an integer or boolean element, got '%s'", LIT(builtin_name), xs); gb_string_free(xs); return false; } + break; } operand->mode = Addressing_Value; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 22ee3d141..07c425cfa 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -129,6 +129,9 @@ BuiltinProc__simd_begin, BuiltinProc_simd_shl_masked, // C logic BuiltinProc_simd_shr_masked, // C logic + BuiltinProc_simd_add_sat, // saturation arithmetic + BuiltinProc_simd_sub_sat, // saturation arithmetic + BuiltinProc_simd_and, BuiltinProc_simd_or, BuiltinProc_simd_xor, @@ -402,6 +405,10 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_shr"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shl_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_shr_masked"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_add_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_sub_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 42f5a60fa..dbdc51078 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1353,6 +1353,30 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } + case BuiltinProc_simd_add_sat: + case BuiltinProc_simd_sub_sat: + { + arg1 = lb_build_expr(p, ce->args[1]); + + char const *name = nullptr; + switch (builtin_id) { + case BuiltinProc_simd_add_sat: name = is_signed ? "llvm.sadd.sat" : "llvm.uadd.sat"; break; + case BuiltinProc_simd_sub_sat: name = is_signed ? "llvm.ssub.sat" : "llvm.usub.sat"; break; + } + + LLVMTypeRef types[1] = {lb_type(p->module, arg0.type)}; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[2] = {}; + args[0] = arg0.value; + args[1] = arg1.value; + + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } + } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); From e331b0647e99a07b5d0f70cbac948ab30b30b5c7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 11:48:04 +0100 Subject: [PATCH 183/254] Add `simd_rotate_left` simd_rotate_right` --- core/intrinsics/intrinsics.odin | 4 ++++ core/simd/simd.odin | 3 +++ src/check_builtin.cpp | 23 ++++++++++++++++++++++ src/checker_builtin_procs.hpp | 4 ++++ src/llvm_backend_proc.cpp | 35 +++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 7bbbc1efd..f8e138ef6 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -252,6 +252,10 @@ simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- // equivalent a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0) simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T --- +simd_rotate_left :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- +simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- + + // WASM targets only wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_size :: proc(index: uintptr) -> int --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 2ce487d13..5d2b5edab 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -96,6 +96,9 @@ nearest :: intrinsics.simd_nearest reverse :: intrinsics.simd_reverse +rotate_left :: intrinsics.simd_rotate_left +rotate_right :: intrinsics.simd_rotate_right + splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { return T{0..args[0]); if (x.mode == Addressing_Invalid) { return false; } + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Operand offset = {}; + check_expr(c, &offset, ce->args[1]); if (offset.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &offset, t_i64); + if (!is_type_integer(offset.type) || offset.mode != Addressing_Constant) { + error(offset.expr, "'%.*s' expected a constant integer offset"); + return false; + } + check_assignment(c, &offset, t_i64, builtin_name); + + operand->type = x.type; + operand->mode = Addressing_Value; + return true; + } + default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); } diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 07c425cfa..13fe2822c 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -170,6 +170,8 @@ BuiltinProc__simd_begin, BuiltinProc_simd_nearest, BuiltinProc_simd_reverse, + BuiltinProc_simd_rotate_left, + BuiltinProc_simd_rotate_right, BuiltinProc__simd_end, // Platform specific intrinsics @@ -444,6 +446,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_nearest"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_reverse"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_rotate_left"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index dbdc51078..c433334d1 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1353,6 +1353,41 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } + case BuiltinProc_simd_rotate_left: + case BuiltinProc_simd_rotate_right: + { + + i64 count = get_array_type_count(arg0.type); + GB_ASSERT(is_power_of_two(count)); + BigInt bi_count = {}; + big_int_from_i64(&bi_count, count); + + TypeAndValue const &tv = ce->args[1]->tav; + ExactValue val = exact_value_to_integer(tv.value); + GB_ASSERT(val.kind == ExactValue_Integer); + BigInt *bi = &val.value_integer; + if (builtin_id == BuiltinProc_simd_rotate_right) { + big_int_neg(bi, bi); + } + big_int_rem(bi, bi, &bi_count); + big_int_dealloc(&bi_count); + + i64 left = big_int_to_i64(bi); + + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); + LLVMTypeRef llvm_u32 = lb_type(m, t_u32); + for (i64 i = 0; i < count; i++) { + u64 idx = cast(u64)(i+left) & cast(u64)(count-1); + values[i] = LLVMConstInt(llvm_u32, idx, false); + } + LLVMValueRef mask = LLVMConstVector(values, cast(unsigned)count); + + LLVMValueRef v = arg0.value; + res.value = LLVMBuildShuffleVector(p->builder, v, v, mask, ""); + return res; + } + + case BuiltinProc_simd_add_sat: case BuiltinProc_simd_sub_sat: { From f3f6c12a7cc6dca745ae40744a5220878ff9261e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 11:58:55 +0100 Subject: [PATCH 184/254] Add `simd_clamp` --- core/intrinsics/intrinsics.odin | 6 ++-- core/simd/simd.odin | 8 ++++-- src/check_builtin.cpp | 51 +++++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 5 ++++ src/llvm_backend_proc.cpp | 22 ++++++++++++++ 5 files changed, 87 insertions(+), 5 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index f8e138ef6..484cd945c 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -212,8 +212,10 @@ simd_xor :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_neg :: proc(a: #simd[N]T) -> #simd[N]T --- simd_abs :: proc(a: #simd[N]T) -> #simd[N]T --- -simd_min :: proc(a, b: #simd[N]T) -> #simd[N]T --- -simd_max :: proc(a, b: #simd[N]T) -> #simd[N]T --- + +simd_min :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_max :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_clamp :: proc(v, min, max: #simd[N]T) -> #simd[N]T --- // Return an unsigned integer of the same size as the input type // NOT A BOOLEAN diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 5d2b5edab..1da0bd3e5 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -49,9 +49,11 @@ xor :: intrinsics.simd_xor neg :: intrinsics.simd_neg -abs :: intrinsics.simd_abs -min :: intrinsics.simd_min -max :: intrinsics.simd_max +abs :: intrinsics.simd_abs + +min :: intrinsics.simd_min +max :: intrinsics.simd_max +clamp :: intrinsics.simd_clamp // Return an unsigned integer of the same size as the input type // NOT A BOOLEAN diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 54bede3a8..45c9c93c5 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -956,6 +956,57 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } + case BuiltinProc_simd_clamp: + { + Operand x = {}; + Operand y = {}; + Operand z = {}; + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } + check_expr_with_type_hint(c, &z, ce->args[2], x.type); if (z.mode == Addressing_Invalid) { return false; } + convert_to_typed(c, &y, x.type); + convert_to_typed(c, &z, x.type); + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!is_type_simd_vector(y.type)) { + error(y.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!is_type_simd_vector(z.type)) { + error(z.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + if (!are_types_identical(x.type, y.type)) { + gbString xs = type_to_string(x.type); + gbString ys = type_to_string(y.type); + error(x.expr, "'%.*s' expected 2 arguments of the same type, got '%s' vs '%s'", LIT(builtin_name), xs, ys); + gb_string_free(ys); + gb_string_free(xs); + return false; + } + if (!are_types_identical(x.type, z.type)) { + gbString xs = type_to_string(x.type); + gbString zs = type_to_string(z.type); + error(x.expr, "'%.*s' expected 2 arguments of the same type, got '%s' vs '%s'", LIT(builtin_name), xs, zs); + gb_string_free(zs); + gb_string_free(xs); + return false; + } + Type *elem = base_array_type(x.type); + if (!is_type_integer(elem) && !is_type_float(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' expected a #simd type with an integer or floating point element, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + + operand->mode = Addressing_Value; + operand->type = x.type; + return true; + } + default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); } diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 13fe2822c..eea4d0a8b 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -141,6 +141,7 @@ BuiltinProc__simd_begin, BuiltinProc_simd_min, BuiltinProc_simd_max, + BuiltinProc_simd_clamp, BuiltinProc_simd_eq, BuiltinProc_simd_ne, @@ -415,9 +416,13 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_neg"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_abs"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_min"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_max"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_clamp"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_eq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_ne"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_lt"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index c433334d1..97bb02ba3 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1412,6 +1412,28 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } + case BuiltinProc_simd_clamp: + { + arg1 = lb_build_expr(p, ce->args[1]); + arg2 = lb_build_expr(p, ce->args[2]); + + LLVMValueRef v = arg0.value; + LLVMValueRef min = arg1.value; + LLVMValueRef max = arg2.value; + + if (is_float) { + v = LLVMBuildSelect(p->builder, LLVMBuildFCmp(p->builder, LLVMRealOLT, v, min, ""), min, v, ""); + res.value = LLVMBuildSelect(p->builder, LLVMBuildFCmp(p->builder, LLVMRealOGT, v, max, ""), max, v, ""); + } else if (is_signed) { + v = LLVMBuildSelect(p->builder, LLVMBuildICmp(p->builder, LLVMIntSLT, v, min, ""), min, v, ""); + res.value = LLVMBuildSelect(p->builder, LLVMBuildICmp(p->builder, LLVMIntSGT, v, max, ""), max, v, ""); + } else { + v = LLVMBuildSelect(p->builder, LLVMBuildICmp(p->builder, LLVMIntULT, v, min, ""), min, v, ""); + res.value = LLVMBuildSelect(p->builder, LLVMBuildICmp(p->builder, LLVMIntUGT, v, max, ""), max, v, ""); + } + return res; + } + } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); From 66b5a35ec352b74e66ad866640669d920e9d0849 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 13:45:47 +0100 Subject: [PATCH 185/254] Add `simd_to_bits`; correct fix typo causing issue with parapoly --- src/check_builtin.cpp | 27 +++++++++++++++++++++++++++ src/check_decl.cpp | 8 ++++---- src/check_type.cpp | 7 ++++--- src/checker_builtin_procs.hpp | 4 ++++ src/llvm_backend_proc.cpp | 5 +++++ src/parser.cpp | 14 +++++++++++++- 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 45c9c93c5..c432d6080 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1007,6 +1007,33 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } + case BuiltinProc_simd_to_bits: + { + Operand x = {}; + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + + if (!is_type_simd_vector(x.type)) { + error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); + return false; + } + Type *elem = base_array_type(x.type); + i64 count = get_array_type_count(x.type); + i64 sz = type_size_of(elem); + Type *bit_elem = nullptr; + switch (sz) { + case 1: bit_elem = t_u8; break; + case 2: bit_elem = t_u16; break; + case 4: bit_elem = t_u32; break; + case 8: bit_elem = t_u64; break; + } + GB_ASSERT(bit_elem != nullptr); + + operand->type = alloc_type_simd_vector(count, bit_elem); + operand->mode = Addressing_Value; + return true; + } + + default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); } diff --git a/src/check_decl.cpp b/src/check_decl.cpp index d8cad2ce1..62a1e2555 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1315,20 +1315,20 @@ void check_proc_group_decl(CheckerContext *ctx, Entity *&pg_entity, DeclInfo *d) if (!both_have_where_clauses) switch (kind) { case ProcOverload_Identical: - error(p->token, "Overloaded procedure '%.*s' as the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); + error(p->token, "Overloaded procedure '%.*s' has the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); is_invalid = true; break; // case ProcOverload_CallingConvention: - // error(p->token, "Overloaded procedure '%.*s' as the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); + // error(p->token, "Overloaded procedure '%.*s' has the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); // is_invalid = true; // break; case ProcOverload_ParamVariadic: - error(p->token, "Overloaded procedure '%.*s' as the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); + error(p->token, "Overloaded procedure '%.*s' has the same type as another procedure in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); is_invalid = true; break; case ProcOverload_ResultCount: case ProcOverload_ResultTypes: - error(p->token, "Overloaded procedure '%.*s' as the same parameters but different results in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); + error(p->token, "Overloaded procedure '%.*s' has the same parameters but different results in the procedure group '%.*s'", LIT(name), LIT(proc_group_name)); is_invalid = true; break; case ProcOverload_Polymorphic: diff --git a/src/check_type.cpp b/src/check_type.cpp index de58db054..f84c15a19 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1234,7 +1234,7 @@ bool check_type_specialization_to(CheckerContext *ctx, Type *specialization, Typ } -Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *poly_type, Operand operand) { +Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *poly_type, Operand const &operand) { bool modify_type = !ctx->no_polymorphic_errors; bool show_error = modify_type && !ctx->hide_polymorphic_errors; if (!is_operand_value(operand)) { @@ -2803,13 +2803,14 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t goto array_end; } - if (is_type_polymorphic(elem)) { + if (generic_type != nullptr) { // Ignore } else if (count < 1 || !is_power_of_two(count)) { error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count); *type = alloc_type_array(elem, count, generic_type); goto array_end; - } else + } + *type = alloc_type_simd_vector(count, elem, generic_type); if (is_arch_wasm()) { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index eea4d0a8b..350213de2 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -170,6 +170,8 @@ BuiltinProc__simd_begin, BuiltinProc_simd_trunc, BuiltinProc_simd_nearest, + BuiltinProc_simd_to_bits, + BuiltinProc_simd_reverse, BuiltinProc_simd_rotate_left, BuiltinProc_simd_rotate_right, @@ -450,6 +452,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_trunc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_nearest"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_to_bits"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_reverse"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_rotate_left"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 97bb02ba3..a56aa862a 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1434,6 +1434,11 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } + case BuiltinProc_simd_to_bits: + { + res.value = LLVMBuildBitCast(p->builder, arg0.value, lb_type(m, tv.type), ""); + return res; + } } GB_PANIC("Unhandled simd intrinsic: '%.*s'", LIT(builtin_procs[builtin_id].name)); diff --git a/src/parser.cpp b/src/parser.cpp index d19e249e5..5280fd4b0 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -360,6 +360,7 @@ Ast *clone_ast(Ast *node) { case Ast_ArrayType: n->ArrayType.count = clone_ast(n->ArrayType.count); n->ArrayType.elem = clone_ast(n->ArrayType.elem); + n->ArrayType.tag = clone_ast(n->ArrayType.tag); break; case Ast_DynamicArrayType: n->DynamicArrayType.elem = clone_ast(n->DynamicArrayType.elem); @@ -2127,7 +2128,18 @@ Ast *parse_operand(AstFile *f, bool lhs) { Token name = expect_token(f, Token_Ident); if (name.string == "type") { return ast_helper_type(f, token, parse_type(f)); - } else if (name.string == "soa" || name.string == "simd") { + } else if ( name.string == "simd") { + Ast *tag = ast_basic_directive(f, token, name); + Ast *original_type = parse_type(f); + Ast *type = unparen_expr(original_type); + switch (type->kind) { + case Ast_ArrayType: type->ArrayType.tag = tag; break; + default: + syntax_error(type, "Expected a fixed array type after #%.*s, got %.*s", LIT(name.string), LIT(ast_strings[type->kind])); + break; + } + return original_type; + } else if (name.string == "soa") { Ast *tag = ast_basic_directive(f, token, name); Ast *original_type = parse_type(f); Ast *type = unparen_expr(original_type); From 59e9df2609ef3e22b4ea0eca158b736c3eba4a15 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 13:49:27 +0100 Subject: [PATCH 186/254] `simd.bit_not`; `simd.copysign` --- core/simd/simd.odin | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 1da0bd3e5..6df30fda7 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -27,7 +27,7 @@ add :: intrinsics.simd_add sub :: intrinsics.simd_sub mul :: intrinsics.simd_mul div :: intrinsics.simd_div -rem :: intrinsics.simd_rem +rem :: intrinsics.simd_rem // integers only // Keeps Odin's Behaviour // (x << y) if y <= mask else 0 @@ -96,6 +96,8 @@ floor :: intrinsics.simd_floor trunc :: intrinsics.simd_trunc nearest :: intrinsics.simd_nearest +to_bits :: intrinsics.simd_to_bits + reverse :: intrinsics.simd_reverse rotate_left :: intrinsics.simd_rotate_left @@ -123,3 +125,15 @@ from_slice :: proc($T: typeid/#simd[$LANES]$E, slice: []E) -> T { } return transmute(T)array } + +bit_not :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_integer(E) { + ones := splat(type_of(v), ~E(0)) + return xor(v, ones) +} + +copysign :: #force_inline proc "contextless" (v, sign: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { + neg_zero := to_bits(splat(T, E(-0.0))) + sign_bit := and(to_bits(sign), neg_zero) + magnitude := and(to_bits(v), bit_not(neg_zero)) + return transmute(T)or(sign_bit, magnitude) +} \ No newline at end of file From c2610cb75ed81d687a4e6962283f498e94773706 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 13:56:35 +0100 Subject: [PATCH 187/254] Keep -vet happy --- core/intrinsics/intrinsics.odin | 2 ++ core/simd/simd.odin | 2 +- examples/all/all_main.odin | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 484cd945c..a30bb109f 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -251,6 +251,8 @@ simd_trunc :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- // rounding to the nearest integral value; if two values are equally near, rounds to the even one simd_nearest :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- +simd_to_bits :: proc(v: #simd[N]T) -> #simd[N]Integer where size_of(T) == size_of(Integer), type_is_unsigned(Integer) --- + // equivalent a swizzle with descending indices, e.g. reserve(a, 3, 2, 1, 0) simd_reverse :: proc(a: #simd[N]T) -> #simd[N]T --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 6df30fda7..79d26c845 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -136,4 +136,4 @@ copysign :: #force_inline proc "contextless" (v, sign: $T/#simd[$LANES]$E) -> T sign_bit := and(to_bits(sign), neg_zero) magnitude := and(to_bits(v), bit_not(neg_zero)) return transmute(T)or(sign_bit, magnitude) -} \ No newline at end of file +} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 94a841a26..1ab242305 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -193,6 +193,7 @@ _ :: slashpath _ :: filepath _ :: reflect _ :: runtime +_ :: simd _ :: slice _ :: sort _ :: strconv From f308f37ba112ca361715e470d513749236da026d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 14:51:50 +0100 Subject: [PATCH 188/254] Remove need for `simd.splat` --- core/simd/simd.odin | 9 ++--- src/check_expr.cpp | 8 +++++ src/llvm_backend_const.cpp | 26 +++++++++++--- src/llvm_backend_expr.cpp | 72 ++++++++++++++++++++++++-------------- 4 files changed, 76 insertions(+), 39 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 79d26c845..ef5fdc70b 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -103,10 +103,6 @@ reverse :: intrinsics.simd_reverse rotate_left :: intrinsics.simd_rotate_left rotate_right :: intrinsics.simd_rotate_right -splat :: #force_inline proc "contextless" ($T: typeid/#simd[$LANES]$E, value: E) -> T { - return T{0.. ^[LANES]E { return (^[LANES]E)(v) } @@ -127,12 +123,11 @@ from_slice :: proc($T: typeid/#simd[$LANES]$E, slice: []E) -> T { } bit_not :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_integer(E) { - ones := splat(type_of(v), ~E(0)) - return xor(v, ones) + return xor(v, T(~E(0))) } copysign :: #force_inline proc "contextless" (v, sign: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { - neg_zero := to_bits(splat(T, E(-0.0))) + neg_zero := to_bits(T(-0.0)) sign_bit := and(to_bits(sign), neg_zero) magnitude := and(to_bits(v), bit_not(neg_zero)) return transmute(T)or(sign_bit, magnitude) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fcd7818bc..2a3b5bf02 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2713,6 +2713,14 @@ bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y) { return check_is_castable_to(c, &x, elem_dst); } + if (is_type_simd_vector(dst)) { + Type *elem = base_array_type(dst); + if (check_is_castable_to(c, operand, elem)) { + return true; + } + } + + return false; } diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 3a3067dbc..bd76400de 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -495,9 +495,9 @@ lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_loc res.value = data; return res; } else if (is_type_array(type) && - value.kind != ExactValue_Invalid && - value.kind != ExactValue_String && - value.kind != ExactValue_Compound) { + value.kind != ExactValue_Invalid && + value.kind != ExactValue_String && + value.kind != ExactValue_Compound) { i64 count = type->Array.count; Type *elem = type->Array.elem; @@ -513,8 +513,8 @@ lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_loc res.value = llvm_const_array(lb_type(m, elem), elems, cast(unsigned)count); return res; } else if (is_type_matrix(type) && - value.kind != ExactValue_Invalid && - value.kind != ExactValue_Compound) { + value.kind != ExactValue_Invalid && + value.kind != ExactValue_Compound) { i64 row = type->Matrix.row_count; i64 column = type->Matrix.column_count; GB_ASSERT(row == column); @@ -537,6 +537,22 @@ lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_loc res.value = LLVMConstArray(lb_type(m, elem), elems, cast(unsigned)total_elem_count); return res; + } else if (is_type_simd_vector(type) && + value.kind != ExactValue_Invalid && + value.kind != ExactValue_Compound) { + i64 count = type->SimdVector.count; + Type *elem = type->SimdVector.elem; + + lbValue single_elem = lb_const_value(m, elem, value, allow_local); + single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); + + LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, count); + for (i64 i = 0; i < count; i++) { + elems[i] = single_elem.value; + } + + res.value = LLVMConstVector(elems, cast(unsigned)count); + return res; } switch (value.kind) { diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 426becc1c..10c337650 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1820,41 +1820,59 @@ lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { return res; } - if (is_type_simd_vector(src) && is_type_simd_vector(dst)) { - Type *src_elem = core_array_type(src); - Type *dst_elem = core_array_type(dst); + if (is_type_simd_vector(dst)) { + Type *et = base_array_type(dst); + if (is_type_simd_vector(src)) { + Type *src_elem = core_array_type(src); + Type *dst_elem = core_array_type(dst); - GB_ASSERT(src->SimdVector.count == dst->SimdVector.count); + GB_ASSERT(src->SimdVector.count == dst->SimdVector.count); - lbValue res = {}; - res.type = t; - if (are_types_identical(src_elem, dst_elem)) { - res.value = value.value; - } else if (is_type_float(src_elem) && is_type_integer(dst_elem)) { - if (is_type_unsigned(dst_elem)) { - res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t), ""); + lbValue res = {}; + res.type = t; + if (are_types_identical(src_elem, dst_elem)) { + res.value = value.value; + } else if (is_type_float(src_elem) && is_type_integer(dst_elem)) { + if (is_type_unsigned(dst_elem)) { + res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t), ""); + } else { + res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t), ""); + } + } else if (is_type_integer(src_elem) && is_type_float(dst_elem)) { + if (is_type_unsigned(src_elem)) { + res.value = LLVMBuildUIToFP(p->builder, value.value, lb_type(m, t), ""); + } else { + res.value = LLVMBuildSIToFP(p->builder, value.value, lb_type(m, t), ""); + } + } else if ((is_type_integer(src_elem) || is_type_boolean(src_elem)) && is_type_integer(dst_elem)) { + res.value = LLVMBuildIntCast2(p->builder, value.value, lb_type(m, t), !is_type_unsigned(src_elem), ""); + } else if (is_type_float(src_elem) && is_type_float(dst_elem)) { + res.value = LLVMBuildFPCast(p->builder, value.value, lb_type(m, t), ""); + } else if (is_type_integer(src_elem) && is_type_boolean(dst_elem)) { + LLVMValueRef i1vector = LLVMBuildICmp(p->builder, LLVMIntNE, value.value, LLVMConstNull(LLVMTypeOf(value.value)), ""); + res.value = LLVMBuildIntCast2(p->builder, i1vector, lb_type(m, t), !is_type_unsigned(src_elem), ""); } else { - res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t), ""); + GB_PANIC("Unhandled simd vector conversion: %s -> %s", type_to_string(src), type_to_string(dst)); } - } else if (is_type_integer(src_elem) && is_type_float(dst_elem)) { - if (is_type_unsigned(src_elem)) { - res.value = LLVMBuildUIToFP(p->builder, value.value, lb_type(m, t), ""); - } else { - res.value = LLVMBuildSIToFP(p->builder, value.value, lb_type(m, t), ""); - } - } else if ((is_type_integer(src_elem) || is_type_boolean(src_elem)) && is_type_integer(dst_elem)) { - res.value = LLVMBuildIntCast2(p->builder, value.value, lb_type(m, t), !is_type_unsigned(src_elem), ""); - } else if (is_type_float(src_elem) && is_type_float(dst_elem)) { - res.value = LLVMBuildFPCast(p->builder, value.value, lb_type(m, t), ""); - } else if (is_type_integer(src_elem) && is_type_boolean(dst_elem)) { - LLVMValueRef i1vector = LLVMBuildICmp(p->builder, LLVMIntNE, value.value, LLVMConstNull(LLVMTypeOf(value.value)), ""); - res.value = LLVMBuildIntCast2(p->builder, i1vector, lb_type(m, t), !is_type_unsigned(src_elem), ""); + return res; } else { - GB_PANIC("Unhandled simd vector conversion: %s -> %s", type_to_string(src), type_to_string(dst)); + i64 count = get_array_type_count(dst); + LLVMTypeRef vt = lb_type(m, t); + LLVMTypeRef llvm_u32 = lb_type(m, t_u32); + LLVMValueRef elem = lb_emit_conv(p, value, et).value; + LLVMValueRef vector = LLVMConstNull(vt); + for (i64 i = 0; i < count; i++) { + LLVMValueRef idx = LLVMConstInt(llvm_u32, i, false); + vector = LLVMBuildInsertElement(p->builder, vector, elem, idx, ""); + } + lbValue res = {}; + res.type = t; + res.value = vector; + return res; } - return res; } + // Pointer <-> uintptr if (is_type_pointer(src) && is_type_uintptr(dst)) { lbValue res = {}; From 208226dba29d46514c8c2b7a8fcd023f1ebf7bd8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 14:55:10 +0100 Subject: [PATCH 189/254] Improve `#simd` literal support --- src/check_builtin.cpp | 76 +++++++++++++++++++++---------------------- src/check_expr.cpp | 9 +++++ 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index c432d6080..34b7d14e9 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -425,9 +425,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call { Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } - check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -467,9 +467,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call { Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } - check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -521,9 +521,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call { Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } - check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -601,9 +601,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } - check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -655,7 +655,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_extract: { Operand x = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); @@ -680,7 +680,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_replace: { Operand x = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); @@ -698,8 +698,8 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call } Operand y = {}; - check_expr_with_type_hint(c, &y, ce->args[2], elem); if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, elem); + check_expr_with_type_hint(c, &y, ce->args[2], elem); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &y, elem); if (y.mode == Addressing_Invalid) return false; if (!are_types_identical(y.type, elem)) { gbString et = type_to_string(elem); gbString yt = type_to_string(y.type); @@ -721,7 +721,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_reduce_max: { Operand x = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); @@ -745,7 +745,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_reduce_xor: { Operand x = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); @@ -769,9 +769,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call { Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } - check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -843,7 +843,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_select: { Operand cond = {}; - check_expr(c, &cond, ce->args[0]); if (cond.mode == Addressing_Invalid) { return false; } + check_expr(c, &cond, ce->args[0]); if (cond.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(cond.type)) { error(cond.expr, "'%.*s' expected a simd vector boolean type", LIT(builtin_name)); @@ -859,9 +859,9 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call Operand x = {}; Operand y = {}; - check_expr(c, &x, ce->args[1]); if (x.mode == Addressing_Invalid) { return false; } - check_expr_with_type_hint(c, &y, ce->args[2], x.type); if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + check_expr(c, &x, ce->args[1]); if (x.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &y, ce->args[2], x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; @@ -900,7 +900,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_nearest: { Operand x = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector boolean type", LIT(builtin_name)); @@ -922,7 +922,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_reverse: { Operand x = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); @@ -937,13 +937,13 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_rotate_right: { Operand x = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); return false; } Operand offset = {}; - check_expr(c, &offset, ce->args[1]); if (offset.mode == Addressing_Invalid) { return false; } + check_expr(c, &offset, ce->args[1]); if (offset.mode == Addressing_Invalid) return false; convert_to_typed(c, &offset, t_i64); if (!is_type_integer(offset.type) || offset.mode != Addressing_Constant) { error(offset.expr, "'%.*s' expected a constant integer offset"); @@ -961,10 +961,10 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call Operand x = {}; Operand y = {}; Operand z = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } - check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) { return false; } - check_expr_with_type_hint(c, &z, ce->args[2], x.type); if (z.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &y, ce->args[1], x.type); if (y.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &z, ce->args[2], x.type); if (z.mode == Addressing_Invalid) return false; + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; convert_to_typed(c, &z, x.type); if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); @@ -1010,7 +1010,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_to_bits: { Operand x = {}; - check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) { return false; } + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; if (!is_type_simd_vector(x.type)) { error(x.expr, "'%.*s' expected a simd vector type", LIT(builtin_name)); @@ -2933,7 +2933,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (i == j) continue; Operand *b = ops[j]; convert_to_typed(c, a, b->type); - if (a->mode == Addressing_Invalid) { return false; } + if (a->mode == Addressing_Invalid) return false; } } @@ -3616,7 +3616,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; convert_to_typed(c, &x, y.type); if (is_type_untyped(x.type)) { gbString xts = type_to_string(x.type); @@ -4232,7 +4232,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (x.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; if (x.mode == Addressing_Invalid) { return false; } @@ -4289,7 +4289,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (y.mode == Addressing_Invalid) { return false; } - convert_to_typed(c, &y, x.type); + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; convert_to_typed(c, &x, y.type); if (!are_types_identical(x.type, y.type)) { gbString xts = type_to_string(x.type); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2a3b5bf02..b7568aa70 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -777,6 +777,14 @@ i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type return distance + 6; } } + + if (is_type_simd_vector(dst)) { + Type *dst_elem = base_array_type(dst); + i64 distance = check_distance_between_types(c, operand, dst_elem); + if (distance >= 0) { + return distance + 6; + } + } if (is_type_matrix(dst)) { Type *dst_elem = base_array_type(dst); @@ -786,6 +794,7 @@ i64 check_distance_between_types(CheckerContext *c, Operand *operand, Type *type } } + if (is_type_any(dst)) { if (!is_type_polymorphic(src)) { if (operand->mode == Addressing_Context && operand->type == t_context) { From d0e8a735bae52eaa7d1d852953da721071571020 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 17:09:46 +0100 Subject: [PATCH 190/254] Add arithmetic operator support for simd vectors; Add `intrinsics.simd_and_not` --- core/simd/simd.odin | 16 +++++++++++--- src/check_builtin.cpp | 1 + src/check_expr.cpp | 7 ++---- src/checker_builtin_procs.hpp | 3 +++ src/llvm_backend_expr.cpp | 40 ++++++++++++++++++++++++++++++++++- src/llvm_backend_proc.cpp | 4 ++++ src/types.cpp | 3 +++ 7 files changed, 65 insertions(+), 9 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index ef5fdc70b..9673b5cc9 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -43,9 +43,10 @@ shr_masked :: intrinsics.simd_shr_masked add_sat :: intrinsics.simd_add_sat sub_sat :: intrinsics.simd_sub_sat -and :: intrinsics.simd_and -or :: intrinsics.simd_or -xor :: intrinsics.simd_xor +and :: intrinsics.simd_and +or :: intrinsics.simd_or +xor :: intrinsics.simd_xor +and_not :: intrinsics.simd_and_not neg :: intrinsics.simd_neg @@ -132,3 +133,12 @@ copysign :: #force_inline proc "contextless" (v, sign: $T/#simd[$LANES]$E) -> T magnitude := and(to_bits(v), bit_not(neg_zero)) return transmute(T)or(sign_bit, magnitude) } + +signum :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { + is_nan := ne(v, v) + return select(is_nan, v, copysign(T(1), v)) +} + +recip :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { + return div(T(1), v) +} diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 34b7d14e9..bfaa91cf8 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -464,6 +464,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call case BuiltinProc_simd_and: case BuiltinProc_simd_or: case BuiltinProc_simd_xor: + case BuiltinProc_simd_and_not: { Operand x = {}; Operand y = {}; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index b7568aa70..7fe9b8acf 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1590,11 +1590,6 @@ bool check_unary_op(CheckerContext *c, Operand *o, Token op) { bool check_binary_op(CheckerContext *c, Operand *o, Token op) { Type *main_type = o->type; - if (is_type_simd_vector(main_type)) { - error(op, "Operator '%.*s' is not supported on #simd vector types, please use the intrinsics.simd_*", LIT(op.string)); - return false; - } - // TODO(bill): Handle errors correctly Type *type = base_type(core_array_type(main_type)); Type *ct = core_type(type); @@ -2500,6 +2495,8 @@ void check_shift(CheckerContext *c, Operand *x, Operand *y, Ast *node, Type *typ gb_string_free(err_str); } + // TODO(bill): Should we support shifts for fixed arrays and #simd vectors? + if (!is_type_integer(x->type)) { gbString err_str = expr_to_string(y->expr); error(node, "Shift operand '%s' must be an integer", err_str); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 350213de2..eb4bc1498 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -135,6 +135,7 @@ BuiltinProc__simd_begin, BuiltinProc_simd_and, BuiltinProc_simd_or, BuiltinProc_simd_xor, + BuiltinProc_simd_and_not, BuiltinProc_simd_neg, BuiltinProc_simd_abs, @@ -417,6 +418,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_and_not"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_neg"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_abs"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 10c337650..55b76b93a 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -258,7 +258,13 @@ lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, Type *type) LLVMBuildStore(p->builder, v2, LLVMBuildStructGEP(p->builder, addr.addr.value, 2, "")); LLVMBuildStore(p->builder, v3, LLVMBuildStructGEP(p->builder, addr.addr.value, 3, "")); return lb_addr_load(p, addr); - + } else if (is_type_simd_vector(x.type)) { + Type *elem = base_array_type(x.type); + if (is_type_float(elem)) { + res.value = LLVMBuildFNeg(p->builder, x.value, ""); + } else { + res.value = LLVMBuildNeg(p->builder, x.value, ""); + } } else { GB_PANIC("Unhandled type %s", type_to_string(x.type)); } @@ -2559,6 +2565,38 @@ lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue ri case Token_NotEq: pred = LLVMIntNE; break; } res.value = LLVMBuildICmp(p->builder, pred, left.value, right.value, ""); + } else if (is_type_simd_vector(a)) { + LLVMValueRef mask = nullptr; + Type *elem = base_array_type(a); + if (is_type_float(elem)) { + LLVMRealPredicate pred = {}; + switch (op_kind) { + case Token_CmpEq: pred = LLVMRealOEQ; break; + case Token_NotEq: pred = LLVMRealONE; break; + } + mask = LLVMBuildFCmp(p->builder, pred, left.value, right.value, ""); + } else { + LLVMIntPredicate pred = {}; + switch (op_kind) { + case Token_CmpEq: pred = LLVMIntEQ; break; + case Token_NotEq: pred = LLVMIntNE; break; + } + mask = LLVMBuildICmp(p->builder, pred, left.value, right.value, ""); + } + GB_ASSERT_MSG(mask != nullptr, "Unhandled comparison kind %s (%s) %.*s %s (%s)", type_to_string(left.type), type_to_string(base_type(left.type)), LIT(token_strings[op_kind]), type_to_string(right.type), type_to_string(base_type(right.type))); + + // TODO(bill): is this a good approach to dealing with comparisons of vectors? + char const *name = "llvm.vector.reduce.umax"; + LLVMTypeRef types[1] = {LLVMTypeOf(mask)}; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[1] = {}; + args[0] = mask; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + return res; + } else { GB_PANIC("Unhandled comparison kind %s (%s) %.*s %s (%s)", type_to_string(left.type), type_to_string(base_type(left.type)), LIT(token_strings[op_kind]), type_to_string(right.type), type_to_string(base_type(right.type))); } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a56aa862a..99c023311 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1097,11 +1097,15 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_and: case BuiltinProc_simd_or: case BuiltinProc_simd_xor: + case BuiltinProc_simd_and_not: arg1 = lb_build_expr(p, ce->args[1]); switch (builtin_id) { case BuiltinProc_simd_and: op_code = LLVMAnd; break; case BuiltinProc_simd_or: op_code = LLVMOr; break; case BuiltinProc_simd_xor: op_code = LLVMXor; break; + case BuiltinProc_simd_and_not: + res.value = LLVMBuildAnd(p->builder, arg0.value, LLVMBuildNot(p->builder, arg1.value, ""), ""); + return res; } if (op_code) { res.value = LLVMBuildBinOp(p->builder, op_code, arg0.value, arg1.value, ""); diff --git a/src/types.cpp b/src/types.cpp index 6f61015d3..ad83e0568 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2306,6 +2306,9 @@ bool is_type_comparable(Type *t) { } } return true; + + case Type_SimdVector: + return true; } return false; } From 7092273a8f5a2ae8c60ece297e7114a29e0f3652 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 17:36:13 +0100 Subject: [PATCH 191/254] Rename `simd_eq` etc to `simd_lanes_eq` --- core/intrinsics/intrinsics.odin | 19 ++++++++--------- core/simd/simd.odin | 12 +++++------ src/check_builtin.cpp | 16 +++++++-------- src/checker_builtin_procs.hpp | 24 +++++++++++----------- src/llvm_backend_proc.cpp | 36 ++++++++++++++++----------------- 5 files changed, 54 insertions(+), 53 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index a30bb109f..7c211d8fc 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -205,9 +205,10 @@ simd_shr_masked :: proc(a: #simd[N]T, b: #simd[N]Unsigned_Integer) -> #simd[N]T simd_add_sat :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_sub_sat :: proc(a, b: #simd[N]T) -> #simd[N]T --- -simd_and :: proc(a, b: #simd[N]T) -> #simd[N]T --- -simd_or :: proc(a, b: #simd[N]T) -> #simd[N]T --- -simd_xor :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_and :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_or :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_xor :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_and_not :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_neg :: proc(a: #simd[N]T) -> #simd[N]T --- @@ -222,12 +223,12 @@ simd_clamp :: proc(v, min, max: #simd[N]T) -> #simd[N]T --- // element-wise: // false => 0x00...00 // true => 0xff...ff -simd_eq :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- -simd_ne :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- -simd_lt :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- -simd_le :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- -simd_gt :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- -simd_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_lanes_eq :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_lanes_ne :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_lanes_lt :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_lanes_le :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_lanes_gt :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- +simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer --- simd_extract :: proc(a: #simd[N]T, idx: uint) -> T --- simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 9673b5cc9..c54d2a480 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -61,12 +61,12 @@ clamp :: intrinsics.simd_clamp // element-wise: // false => 0x00...00 // true => 0xff...ff -eq :: intrinsics.simd_eq -ne :: intrinsics.simd_ne -lt :: intrinsics.simd_lt -le :: intrinsics.simd_le -gt :: intrinsics.simd_gt -ge :: intrinsics.simd_ge +lanes_eq :: intrinsics.simd_lanes_eq +lanes_ne :: intrinsics.simd_lanes_ne +lanes_lt :: intrinsics.simd_lanes_lt +lanes_le :: intrinsics.simd_lanes_le +lanes_gt :: intrinsics.simd_lanes_gt +lanes_ge :: intrinsics.simd_lanes_ge // extract :: proc(a: #simd[N]T, idx: uint) -> T extract :: intrinsics.simd_extract diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index bfaa91cf8..c63c67d90 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -589,12 +589,12 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call } // Return integer masks - case BuiltinProc_simd_eq: - case BuiltinProc_simd_ne: - case BuiltinProc_simd_lt: - case BuiltinProc_simd_le: - case BuiltinProc_simd_gt: - case BuiltinProc_simd_ge: + case BuiltinProc_simd_lanes_eq: + case BuiltinProc_simd_lanes_ne: + case BuiltinProc_simd_lanes_lt: + case BuiltinProc_simd_lanes_le: + case BuiltinProc_simd_lanes_gt: + case BuiltinProc_simd_lanes_ge: { // op(#simd[N]T, #simd[N]T) -> #simd[N]V // where `V` is an integer, `size_of(T) == size_of(V)` @@ -611,8 +611,8 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call } Type *elem = base_array_type(x.type); switch (id) { - case BuiltinProc_simd_eq: - case BuiltinProc_simd_ne: + case BuiltinProc_simd_lanes_eq: + case BuiltinProc_simd_lanes_ne: if (!is_type_integer(elem) && !is_type_float(elem) && !is_type_boolean(elem)) { gbString xs = type_to_string(x.type); error(x.expr, "'%.*s' expected a #simd type with an integer, floating point, or boolean element, got '%s'", LIT(builtin_name), xs); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index eb4bc1498..1b2c105f1 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -144,12 +144,12 @@ BuiltinProc__simd_begin, BuiltinProc_simd_max, BuiltinProc_simd_clamp, - BuiltinProc_simd_eq, - BuiltinProc_simd_ne, - BuiltinProc_simd_lt, - BuiltinProc_simd_le, - BuiltinProc_simd_gt, - BuiltinProc_simd_ge, + BuiltinProc_simd_lanes_eq, + BuiltinProc_simd_lanes_ne, + BuiltinProc_simd_lanes_lt, + BuiltinProc_simd_lanes_le, + BuiltinProc_simd_lanes_gt, + BuiltinProc_simd_lanes_ge, BuiltinProc_simd_extract, BuiltinProc_simd_replace, @@ -428,12 +428,12 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_max"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_clamp"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_eq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_ne"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_lt"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_le"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_gt"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_ge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_eq"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_ne"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_lt"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_le"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_gt"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_ge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_extract"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_replace"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 99c023311..4c4000fec 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1151,22 +1151,22 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); } return res; - case BuiltinProc_simd_eq: - case BuiltinProc_simd_ne: - case BuiltinProc_simd_lt: - case BuiltinProc_simd_le: - case BuiltinProc_simd_gt: - case BuiltinProc_simd_ge: + case BuiltinProc_simd_lanes_eq: + case BuiltinProc_simd_lanes_ne: + case BuiltinProc_simd_lanes_lt: + case BuiltinProc_simd_lanes_le: + case BuiltinProc_simd_lanes_gt: + case BuiltinProc_simd_lanes_ge: arg1 = lb_build_expr(p, ce->args[1]); if (is_float) { LLVMRealPredicate pred = cast(LLVMRealPredicate)0; switch (builtin_id) { - case BuiltinProc_simd_eq: pred = LLVMRealOEQ; break; - case BuiltinProc_simd_ne: pred = LLVMRealONE; break; - case BuiltinProc_simd_lt: pred = LLVMRealOLT; break; - case BuiltinProc_simd_le: pred = LLVMRealOLE; break; - case BuiltinProc_simd_gt: pred = LLVMRealOGT; break; - case BuiltinProc_simd_ge: pred = LLVMRealOGE; break; + case BuiltinProc_simd_lanes_eq: pred = LLVMRealOEQ; break; + case BuiltinProc_simd_lanes_ne: pred = LLVMRealONE; break; + case BuiltinProc_simd_lanes_lt: pred = LLVMRealOLT; break; + case BuiltinProc_simd_lanes_le: pred = LLVMRealOLE; break; + case BuiltinProc_simd_lanes_gt: pred = LLVMRealOGT; break; + case BuiltinProc_simd_lanes_ge: pred = LLVMRealOGE; break; } if (pred) { res.value = LLVMBuildFCmp(p->builder, pred, arg0.value, arg1.value, ""); @@ -1176,12 +1176,12 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const } else { LLVMIntPredicate pred = cast(LLVMIntPredicate)0; switch (builtin_id) { - case BuiltinProc_simd_eq: pred = LLVMIntEQ; break; - case BuiltinProc_simd_ne: pred = LLVMIntNE; break; - case BuiltinProc_simd_lt: pred = is_signed ? LLVMIntSLT :LLVMIntULT; break; - case BuiltinProc_simd_le: pred = is_signed ? LLVMIntSLE :LLVMIntULE; break; - case BuiltinProc_simd_gt: pred = is_signed ? LLVMIntSGT :LLVMIntUGT; break; - case BuiltinProc_simd_ge: pred = is_signed ? LLVMIntSGE :LLVMIntUGE; break; + case BuiltinProc_simd_lanes_eq: pred = LLVMIntEQ; break; + case BuiltinProc_simd_lanes_ne: pred = LLVMIntNE; break; + case BuiltinProc_simd_lanes_lt: pred = is_signed ? LLVMIntSLT :LLVMIntULT; break; + case BuiltinProc_simd_lanes_le: pred = is_signed ? LLVMIntSLE :LLVMIntULE; break; + case BuiltinProc_simd_lanes_gt: pred = is_signed ? LLVMIntSGT :LLVMIntUGT; break; + case BuiltinProc_simd_lanes_ge: pred = is_signed ? LLVMIntSGE :LLVMIntUGE; break; } if (pred) { res.value = LLVMBuildICmp(p->builder, pred, arg0.value, arg1.value, ""); From 20e7b5c88acb2e0cee64ccdec7247227306a345f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 17:48:51 +0100 Subject: [PATCH 192/254] Support `count_ones` etc with #simd --- core/intrinsics/intrinsics.odin | 8 ++++---- core/simd/simd.odin | 15 ++++++++++----- src/check_builtin.cpp | 9 ++++++++- src/llvm_backend_utility.cpp | 6 ++++-- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 7c211d8fc..bf8f56e63 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -22,10 +22,10 @@ alloca :: proc(size, align: int) -> [^]u8 --- cpu_relax :: proc() --- read_cycle_counter :: proc() -> i64 --- -count_ones :: proc(x: $T) -> T where type_is_integer(T) --- -count_zeros :: proc(x: $T) -> T where type_is_integer(T) --- -count_trailing_zeros :: proc(x: $T) -> T where type_is_integer(T) --- -count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) --- +count_ones :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- +count_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- +count_trailing_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- +count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- reverse_bits :: proc(x: $T) -> T where type_is_integer(T) --- byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index c54d2a480..17d97f918 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -104,6 +104,11 @@ reverse :: intrinsics.simd_reverse rotate_left :: intrinsics.simd_rotate_left rotate_right :: intrinsics.simd_rotate_right +count_ones :: intrinsics.count_ones +count_zeros :: intrinsics.count_zeros +count_trailing_zeros :: intrinsics.count_trailing_zeros +count_leading_zeros :: intrinsics.count_leading_zeros + to_array_ptr :: #force_inline proc "contextless" (v: ^#simd[$LANES]$E) -> ^[LANES]E { return (^[LANES]E)(v) } @@ -129,16 +134,16 @@ bit_not :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where i copysign :: #force_inline proc "contextless" (v, sign: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { neg_zero := to_bits(T(-0.0)) - sign_bit := and(to_bits(sign), neg_zero) - magnitude := and(to_bits(v), bit_not(neg_zero)) - return transmute(T)or(sign_bit, magnitude) + sign_bit := to_bits(sign) & neg_zero + magnitude := to_bits(v) &~ neg_zero + return transmute(T)(sign_bit|magnitude) } signum :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { - is_nan := ne(v, v) + is_nan := lanes_ne(v, v) return select(is_nan, v, copysign(T(1), v)) } recip :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { - return div(T(1), v) + return T(1) / v } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index c63c67d90..ee805702d 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3559,7 +3559,14 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } - if (!is_type_integer_like(x.type)) { + if (is_type_simd_vector(x.type) && id != BuiltinProc_reverse_bits) { + Type *elem = base_array_type(x.type); + if (!is_type_integer_like(elem)) { + gbString xts = type_to_string(x.type); + error(x.expr, "#simd values passed to '%.*s' must have an element of an integer-like type (integer, boolean, enum, bit_set), got %s", LIT(builtin_name), xts); + gb_string_free(xts); + } + } else if (!is_type_integer_like(x.type)) { gbString xts = type_to_string(x.type); error(x.expr, "Values passed to '%.*s' must be an integer-like type (integer, boolean, enum, bit_set), got %s", LIT(builtin_name), xts); gb_string_free(xts); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index bfd21bedb..52d3a17cf 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -485,8 +485,10 @@ lbValue lb_emit_count_ones(lbProcedure *p, lbValue x, Type *type) { } lbValue lb_emit_count_zeros(lbProcedure *p, lbValue x, Type *type) { - i64 sz = 8*type_size_of(type); - lbValue size = lb_const_int(p->module, type, cast(u64)sz); + Type *elem = base_array_type(type); + i64 sz = 8*type_size_of(elem); + lbValue size = lb_const_int(p->module, elem, cast(u64)sz); + size = lb_emit_conv(p, size, type); lbValue count = lb_emit_count_ones(p, x, type); return lb_emit_arith(p, Token_Sub, size, count, type); } From 421d45a7a76e53946e7441212af9d08a0d93ff68 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 18:06:26 +0100 Subject: [PATCH 193/254] Add `intrinsics.fused_mul_add` --- core/intrinsics/intrinsics.odin | 2 ++ core/simd/simd.odin | 3 ++ src/check_builtin.cpp | 53 +++++++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 2 ++ src/llvm_backend_proc.cpp | 25 ++++++++++++++++ 5 files changed, 85 insertions(+) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index bf8f56e63..c13e099c5 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -35,6 +35,8 @@ overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- sqrt :: proc(x: $T) -> T where type_is_float(T) --- +fused_mul_add :: proc(a, b, c: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- + mem_copy :: proc(dst, src: rawptr, len: int) --- mem_copy_non_overlapping :: proc(dst, src: rawptr, len: int) --- mem_zero :: proc(ptr: rawptr, len: int) --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 17d97f918..ce278bce7 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -109,6 +109,9 @@ count_zeros :: intrinsics.count_zeros count_trailing_zeros :: intrinsics.count_trailing_zeros count_leading_zeros :: intrinsics.count_leading_zeros +fused_mul_add :: intrinsics.fused_mul_add +fma :: intrinsics.fused_mul_add + to_array_ptr :: #force_inline proc "contextless" (v: ^#simd[$LANES]$E) -> ^[LANES]E { return (^[LANES]E)(v) } diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ee805702d..19b78b46e 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3681,6 +3681,59 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } break; + case BuiltinProc_fused_mul_add: + { + Operand x = {}; + Operand y = {}; + Operand z = {}; + check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; + check_expr(c, &y, ce->args[1]); if (y.mode == Addressing_Invalid) return false; + check_expr(c, &z, ce->args[2]); if (z.mode == Addressing_Invalid) return false; + + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &x, y.type); if (x.mode == Addressing_Invalid) return false; + convert_to_typed(c, &z, x.type); if (z.mode == Addressing_Invalid) return false; + convert_to_typed(c, &x, z.type); if (x.mode == Addressing_Invalid) return false; + if (is_type_untyped(x.type)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a typed floating point value or #simd vector for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + + Type *elem = core_array_type(x.type); + if (!is_type_float(x.type) && !(is_type_simd_vector(x.type) && is_type_float(elem))) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a floating point or #simd vector value for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + if (is_type_different_to_arch_endianness(elem)) { + GB_ASSERT(elem->kind == Type_Basic); + if (elem->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a float which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + } + + if (!are_types_identical(x.type, y.type) || !are_types_identical(y.type, z.type)) { + gbString xts = type_to_string(x.type); + gbString yts = type_to_string(y.type); + gbString zts = type_to_string(z.type); + error(x.expr, "Mismatched types for '%.*s', got %s vs %s vs %s", LIT(builtin_name), xts, yts, zts); + gb_string_free(zts); + gb_string_free(yts); + gb_string_free(xts); + return false; + } + + operand->mode = Addressing_Value; + operand->type = default_type(x.type); + } + break; + case BuiltinProc_mem_copy: case BuiltinProc_mem_copy_non_overlapping: { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 1b2c105f1..5859ce3ab 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -65,6 +65,7 @@ enum BuiltinProcId { BuiltinProc_overflow_mul, BuiltinProc_sqrt, + BuiltinProc_fused_mul_add, BuiltinProc_mem_copy, BuiltinProc_mem_copy_non_overlapping, @@ -348,6 +349,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("overflow_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("fused_mul_add"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("mem_copy"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("mem_copy_non_overlapping"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 4c4000fec..a5dda7815 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2005,6 +2005,31 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } + case BuiltinProc_fused_mul_add: + { + Type *type = tv.type; + lbValue x = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), type); + lbValue y = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), type); + lbValue z = lb_emit_conv(p, lb_build_expr(p, ce->args[2]), type); + + + char const *name = "llvm.fma"; + LLVMTypeRef types[1] = {lb_type(p->module, type)}; + unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); + GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); + LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + + LLVMValueRef args[3] = {}; + args[0] = x.value; + args[1] = y.value; + args[2] = z.value; + + lbValue res = {}; + res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + res.type = type; + return res; + } + case BuiltinProc_mem_copy: { lbValue dst = lb_build_expr(p, ce->args[0]); From 1f438d4e6c0a979b249683cb2da048a0ff36dcce Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 18:09:59 +0100 Subject: [PATCH 194/254] Merge `intrinsics.simd_sqrt` with `intrinsics.sqrt` --- core/intrinsics/intrinsics.odin | 3 +-- core/simd/simd.odin | 2 +- src/check_builtin.cpp | 21 +++++++++++++++++---- src/checker_builtin_procs.hpp | 2 -- src/llvm_backend_proc.cpp | 2 -- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index c13e099c5..4f10c5a32 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -33,7 +33,7 @@ overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- -sqrt :: proc(x: $T) -> T where type_is_float(T) --- +sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- fused_mul_add :: proc(a, b, c: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- @@ -247,7 +247,6 @@ simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T -- simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T --- // Lane-wise operations -simd_sqrt :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- // IEEE sqrt simd_ceil :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- simd_floor :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- simd_trunc :: proc(a: #simd[N]any_float) -> #simd[N]any_float --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index ce278bce7..263402c43 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -91,7 +91,7 @@ shuffle :: intrinsics.simd_shuffle select :: intrinsics.simd_select -sqrt :: intrinsics.simd_sqrt +sqrt :: intrinsics.sqrt ceil :: intrinsics.simd_ceil floor :: intrinsics.simd_floor trunc :: intrinsics.simd_trunc diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 19b78b46e..87f7358f0 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -894,7 +894,6 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } - case BuiltinProc_simd_sqrt: case BuiltinProc_simd_ceil: case BuiltinProc_simd_floor: case BuiltinProc_simd_trunc: @@ -3661,14 +3660,28 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 if (x.mode == Addressing_Invalid) { return false; } + + Type *elem = core_array_type(x.type); + if (!is_type_float(x.type) && !(is_type_simd_vector(x.type) && is_type_float(elem))) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a floating point or #simd vector value for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } else if (is_type_different_to_arch_endianness(elem)) { + GB_ASSERT(elem->kind == Type_Basic); + if (elem->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a float which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + } if (!is_type_float(x.type)) { gbString xts = type_to_string(x.type); error(x.expr, "Expected a floating point value for '%.*s', got %s", LIT(builtin_name), xts); gb_string_free(xts); return false; - } - - if (x.mode == Addressing_Constant) { + } else if (x.mode == Addressing_Constant) { f64 v = exact_value_to_f64(x.value); operand->mode = Addressing_Constant; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 5859ce3ab..28e62e5d6 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -166,7 +166,6 @@ BuiltinProc__simd_begin, BuiltinProc_simd_shuffle, BuiltinProc_simd_select, - BuiltinProc_simd_sqrt, BuiltinProc_simd_ceil, BuiltinProc_simd_floor, BuiltinProc_simd_trunc, @@ -451,7 +450,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_shuffle"), 2, true, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_select"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_sqrt") , 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_ceil") , 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_floor"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_trunc"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index a5dda7815..13643ccfe 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1315,7 +1315,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } - case BuiltinProc_simd_sqrt: case BuiltinProc_simd_ceil: case BuiltinProc_simd_floor: case BuiltinProc_simd_trunc: @@ -1323,7 +1322,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const { char const *name = nullptr; switch (builtin_id) { - case BuiltinProc_simd_sqrt: name = "llvm.sqrt"; break; case BuiltinProc_simd_ceil: name = "llvm.ceil"; break; case BuiltinProc_simd_floor: name = "llvm.floor"; break; case BuiltinProc_simd_trunc: name = "llvm.trunc"; break; From 70451f9335b819a056a06536016cdad7784cb8ea Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 20:40:48 +0100 Subject: [PATCH 195/254] Support reverse_bits for #simd --- core/intrinsics/intrinsics.odin | 2 +- core/simd/simd.odin | 3 ++- src/check_builtin.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 4f10c5a32..fa9b0ecec 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -26,7 +26,7 @@ count_ones :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim count_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- count_trailing_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- -reverse_bits :: proc(x: $T) -> T where type_is_integer(T) --- +reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- overflow_add :: proc(lhs, rhs: $T) -> (T, bool) #optional_ok --- diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 263402c43..fc3c95e82 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -99,7 +99,7 @@ nearest :: intrinsics.simd_nearest to_bits :: intrinsics.simd_to_bits -reverse :: intrinsics.simd_reverse +lanes_reverse :: intrinsics.simd_reverse rotate_left :: intrinsics.simd_rotate_left rotate_right :: intrinsics.simd_rotate_right @@ -108,6 +108,7 @@ count_ones :: intrinsics.count_ones count_zeros :: intrinsics.count_zeros count_trailing_zeros :: intrinsics.count_trailing_zeros count_leading_zeros :: intrinsics.count_leading_zeros +reverse_bits :: intrinsics.reverse_bits fused_mul_add :: intrinsics.fused_mul_add fma :: intrinsics.fused_mul_add diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 87f7358f0..ecaba8b49 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3558,7 +3558,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } - if (is_type_simd_vector(x.type) && id != BuiltinProc_reverse_bits) { + if (is_type_simd_vector(x.type)) { Type *elem = base_array_type(x.type); if (!is_type_integer_like(elem)) { gbString xts = type_to_string(x.type); From 1ff8b97dae79be3f5985de66dc4665640dd12c81 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 26 May 2022 20:44:37 +0100 Subject: [PATCH 196/254] Add prefix of `lanes_` --- core/simd/simd.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index fc3c95e82..43b3b3ee2 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -101,8 +101,8 @@ to_bits :: intrinsics.simd_to_bits lanes_reverse :: intrinsics.simd_reverse -rotate_left :: intrinsics.simd_rotate_left -rotate_right :: intrinsics.simd_rotate_right +lanes_rotate_left :: intrinsics.simd_rotate_left +lanes_rotate_right :: intrinsics.simd_rotate_right count_ones :: intrinsics.count_ones count_zeros :: intrinsics.count_zeros From 833f9dd037d2638a9d2a5bd2a1346057b097aed5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 11:55:03 +0100 Subject: [PATCH 197/254] Minor change --- src/llvm_backend_proc.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 13643ccfe..93481352b 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1104,8 +1104,9 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_or: op_code = LLVMOr; break; case BuiltinProc_simd_xor: op_code = LLVMXor; break; case BuiltinProc_simd_and_not: - res.value = LLVMBuildAnd(p->builder, arg0.value, LLVMBuildNot(p->builder, arg1.value, ""), ""); - return res; + op_code = LLVMAnd; + arg1.value = LLVMBuildNot(p->builder, arg1.value, ""); + break; } if (op_code) { res.value = LLVMBuildBinOp(p->builder, op_code, arg0.value, arg1.value, ""); From c23274adb040ef1ffbdfd8dc1689bb320f274005 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 12:11:58 +0100 Subject: [PATCH 198/254] Remove useless check --- src/check_type.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index f84c15a19..fc5b7aed7 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2813,11 +2813,6 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t *type = alloc_type_simd_vector(count, elem, generic_type); - if (is_arch_wasm()) { - if (type_size_of(*type) != 16) { - error(at->count, "wasm based targets are limited to 128-bit types"); - } - } if (count > SIMD_ELEMENT_COUNT_MAX) { error(at->count, "#simd support a maximum element count of %d, got %lld", SIMD_ELEMENT_COUNT_MAX, cast(long long)count); } From 952f294bcef26e47d4a9e0f3ab2a1bc6f159c8b4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 12:20:48 +0100 Subject: [PATCH 199/254] Add loads of aliases of vector types --- core/simd/simd.odin | 67 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 43b3b3ee2..ed7e418f3 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -3,24 +3,59 @@ package simd import "core:builtin" import "core:intrinsics" -// boolx16 :: #simd[16]bool -// b8x16 :: #simd[16]b8 -// b16x8 :: #simd[8]b16 -// b32x4 :: #simd[4]b32 -// b64x2 :: #simd[2]b64 +// 128-bit vector aliases +u8x16 :: #simd[16]u8 +i8x16 :: #simd[16]i8 +u16x8 :: #simd[8]u16 +i16x8 :: #simd[8]i16 +u32x4 :: #simd[4]u32 +i32x4 :: #simd[4]i32 +u64x2 :: #simd[2]u64 +i64x2 :: #simd[2]i64 +f32x4 :: #simd[4]f32 +f64x2 :: #simd[2]f64 -// u8x16 :: #simd[16]u8 -// i8x16 :: #simd[16]i8 -// u16x8 :: #simd[8]u16 -// i16x8 :: #simd[8]i16 -// u32x4 :: #simd[4]u32 -// i32x4 :: #simd[4]i32 -// u64x2 :: #simd[2]u64 -// i64x2 :: #simd[2]i64 +boolx16 :: #simd[16]bool +b8x16 :: #simd[16]b8 +b16x8 :: #simd[8]b16 +b32x4 :: #simd[4]b32 +b64x2 :: #simd[2]b64 -// f16x8 :: #simd[8]f16 -// f32x4 :: #simd[4]f32 -// f64x2 :: #simd[2]f64 +// 256-bit vector aliases +u8x32 :: #simd[32]u8 +i8x32 :: #simd[32]i8 +u16x16 :: #simd[16]u16 +i16x16 :: #simd[16]i16 +u32x8 :: #simd[8]u32 +i32x8 :: #simd[8]i32 +u64x4 :: #simd[4]u64 +i64x4 :: #simd[4]i64 +f32x8 :: #simd[8]f32 +f64x4 :: #simd[4]f64 + +boolx32 :: #simd[32]bool +b8x32 :: #simd[32]b8 +b16x16 :: #simd[16]b16 +b32x8 :: #simd[8]b32 +b64x4 :: #simd[4]b64 + +// 512-bit vector aliases +u8x64 :: #simd[64]u8 +i8x64 :: #simd[64]i8 +u16x32 :: #simd[32]u16 +i16x32 :: #simd[32]i16 +u32x16 :: #simd[16]u32 +i32x16 :: #simd[16]i32 +u64x8 :: #simd[8]u64 +i64x8 :: #simd[8]i64 +f32x16 :: #simd[16]f32 +f64x8 :: #simd[8]f64 + +boolx64 :: #simd[64]bool +b8x64 :: #simd[64]b8 +b16x32 :: #simd[32]b16 +b32x16 :: #simd[16]b32 +b64x8 :: #simd[8]b64 add :: intrinsics.simd_add From 432b2b19e9b3a79e7ee5fecbf981d794e9d022cc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 12:54:28 +0100 Subject: [PATCH 200/254] Add `intrinsics.simd_x86__MM_SHUFFLE` --- src/check_builtin.cpp | 28 ++++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ecaba8b49..e93e63d4d 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1033,6 +1033,34 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } + case BuiltinProc_simd_x86__MM_SHUFFLE: + { + Operand x[4] = {}; + for (unsigned i = 0; i < 4; i++) { + check_expr(c, x+i, ce->args[i]); if (x[i].mode == Addressing_Invalid) return false; + } + + u32 offsets[4] = {6, 4, 2, 0}; + u32 result = 0; + for (unsigned i = 0; i < 4; i++) { + if (!is_type_integer(x[i].type) || x[i].mode != Addressing_Constant) { + gbString xs = type_to_string(x[i].type); + error(x[i].expr, "'%.*s' expected a constant integer", LIT(builtin_name), xs); + gb_string_free(xs); + return false; + } + i64 val = exact_value_to_i64(x[i].value); + if (val < 0 || val > 3) { + error(x[i].expr, "'%.*s' expected a constant integer in the range 0..<4, got %lld", LIT(builtin_name), cast(long long)val); + return false; + } + result |= cast(u32)(val) << offsets[i]; + } + + operand->type = t_untyped_integer; + operand->mode = Addressing_Constant; + operand->value = exact_value_i64(result); + } default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 28e62e5d6..3ef97b361 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -176,6 +176,10 @@ BuiltinProc__simd_begin, BuiltinProc_simd_reverse, BuiltinProc_simd_rotate_left, BuiltinProc_simd_rotate_right, + + + // Platform specific SIMD intrinsics + BuiltinProc_simd_x86__MM_SHUFFLE, BuiltinProc__simd_end, // Platform specific intrinsics @@ -460,6 +464,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_reverse"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_rotate_left"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + + {STR_LIT("simd_x86__MM_SHUFFLE"), 4, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, From 0b08080119f500e65a381d7e76a010789b4dcef0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 14:23:31 +0100 Subject: [PATCH 201/254] Add `core:simd/x86` SSE Proof of Concept to show intrinsics specific to a certain target platform --- core/simd/x86/sse.odin | 500 +++++++++++++++++++++++++++++++++++++++ core/simd/x86/types.odin | 44 ++++ 2 files changed, 544 insertions(+) create mode 100644 core/simd/x86/sse.odin create mode 100644 core/simd/x86/types.odin diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin new file mode 100644 index 000000000..79b13f667 --- /dev/null +++ b/core/simd/x86/sse.odin @@ -0,0 +1,500 @@ +//+build amd64 +package simd_amd64 + +import "core:intrinsics" +import "core:simd" + +// _MM_SHUFFLE(z, y, x, w) -> (z<<6 | y<<4 | x<<2 | w) +_MM_SHUFFLE :: intrinsics.simd_x86__MM_SHUFFLE + +_MM_HINT_T0 :: 3 +_MM_HINT_T1 :: 2 +_MM_HINT_T2 :: 1 +_MM_HINT_NTA :: 0 +_MM_HINT_ET0 :: 7 +_MM_HINT_ET1 :: 6 + + +_MM_EXCEPT_INVALID :: 0x0001 +_MM_EXCEPT_DENORM :: 0x0002 +_MM_EXCEPT_DIV_ZERO :: 0x0004 +_MM_EXCEPT_OVERFLOW :: 0x0008 +_MM_EXCEPT_UNDERFLOW :: 0x0010 +_MM_EXCEPT_INEXACT :: 0x0020 +_MM_EXCEPT_MASK :: 0x003f + +_MM_MASK_INVALID :: 0x0080 +_MM_MASK_DENORM :: 0x0100 +_MM_MASK_DIV_ZERO :: 0x0200 +_MM_MASK_OVERFLOW :: 0x0400 +_MM_MASK_UNDERFLOW :: 0x0800 +_MM_MASK_INEXACT :: 0x1000 +_MM_MASK_MASK :: 0x1f80 + +_MM_ROUND_NEAREST :: 0x0000 +_MM_ROUND_DOWN :: 0x2000 +_MM_ROUND_UP :: 0x4000 +_MM_ROUND_TOWARD_ZERO :: 0x6000 + +_MM_ROUND_MASK :: 0x6000 + +_MM_FLUSH_ZERO_MASK :: 0x8000 +_MM_FLUSH_ZERO_ON :: 0x8000 +_MM_FLUSH_ZERO_OFF :: 0x0000 + + +_mm_add_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return addss(a, b) +} +_mm_add_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.add(a, b) +} + +_mm_sub_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return subss(a, b) +} +_mm_sub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.sub(a, b) +} + +_mm_mul_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return mulss(a, b) +} +_mm_mul_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.mul(a, b) +} + +_mm_div_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return divss(a, b) +} +_mm_div_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.div(a, b) +} + +_mm_sqrt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return sqrtss(a) +} +_mm_sqrt_ps :: #force_inline proc "c" (a: __m128) -> __m128 { + return sqrtps(a) +} + +_mm_rcp_ss :: #force_inline proc "c" (a: __m128) -> __m128 { + return rcpss(a) +} +_mm_rcp_ps :: #force_inline proc "c" (a: __m128) -> __m128 { + return rcpps(a) +} + +_mm_rsqrt_ss :: #force_inline proc "c" (a: __m128) -> __m128 { + return rsqrtss(a) +} +_mm_rsqrt_ps :: #force_inline proc "c" (a: __m128) -> __m128 { + return rsqrtps(a) +} + +_mm_min_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return minss(a, b) +} +_mm_min_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return minps(a, b) +} + +_mm_max_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return maxss(a, b) +} +_mm_max_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return maxps(a, b) +} + +_mm_and_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + x := transmute(__m128i)a + y := transmute(__m128i)b + return transmute(__m128)simd.and(x, y) +} +_mm_andnot_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + x := transmute(__m128i)a + y := transmute(__m128i)b + return transmute(__m128)simd.and_not(x, y) +} +_mm_or_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + x := transmute(__m128i)a + y := transmute(__m128i)b + return transmute(__m128)simd.or(x, y) +} +_mm_xor_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + x := transmute(__m128i)a + y := transmute(__m128i)b + return transmute(__m128)simd.xor(x, y) +} + + +_mm_cmpeq_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpss(a, b, 0) +} +_mm_cmplt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpss(a, b, 1) +} +_mm_cmple_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpss(a, b, 2) +} +_mm_cmpgt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, cmpss(b, a, 1), 4, 1, 2, 3) +} +_mm_cmpge_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, cmpss(b, a, 2), 4, 1, 2, 3) +} +_mm_cmpneq_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpss(a, b, 4) +} +_mm_cmpnlt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpss(a, b, 5) +} +_mm_cmpnle_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpss(a, b, 6) +} +_mm_cmpngt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, cmpss(b, a, 5), 4, 1, 2, 3) +} +_mm_cmpnge_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, cmpss(b, a, 6), 4, 1, 2, 3) +} +_mm_cmpord_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpss(a, b, 7) +} +_mm_cmpunord_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpss(a, b, 3) +} + + +_mm_cmpeq_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(a, b, 0) +} +_mm_cmplt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(a, b, 1) +} +_mm_cmple_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(a, b, 2) +} +_mm_cmpgt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(b, a, 1) +} +_mm_cmpge_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(b, a, 2) +} +_mm_cmpneq_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(a, b, 4) +} +_mm_cmpnlt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(a, b, 5) +} +_mm_cmpnle_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(a, b, 6) +} +_mm_cmpngt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(b, a, 5) +} +_mm_cmpnge_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(b, a, 6) +} +_mm_cmpord_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(b, a, 7) +} +_mm_cmpunord_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return cmpps(b, a, 3) +} + + +_mm_comieq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return comieq_ss(a, b) +} +_mm_comilt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return comilt_ss(a, b) +} +_mm_comile_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return comile_ss(a, b) +} +_mm_comigt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return comigt_ss(a, b) +} +_mm_comige_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return comige_ss(a, b) +} +_mm_comineq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return comineq_ss(a, b) +} + +_mm_ucomieq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return ucomieq_ss(a, b) +} +_mm_ucomilt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return ucomilt_ss(a, b) +} +_mm_ucomile_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return ucomile_ss(a, b) +} +_mm_ucomigt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return ucomigt_ss(a, b) +} +_mm_ucomige_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return ucomige_ss(a, b) +} +_mm_ucomineq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { + return ucomineq_ss(a, b) +} + +_mm_cvtss_si32 :: #force_inline proc "c" (a: __m128) -> i32 { + return cvtss2si(a) +} +_mm_cvt_ss2si :: _mm_cvtss_si32 +_mm_cvttss_si32 :: _mm_cvtss_si32 + +_mm_cvtss_f32 :: #force_inline proc "c" (a: __m128) -> f32 { + return simd.extract(a, 0) +} + +_mm_cvtsi32_ss :: #force_inline proc "c" (a: __m128, b: i32) -> __m128 { + return cvtsi2ss(a, b) +} +_mm_cvt_si2ss :: _mm_cvtsi32_ss + + +_mm_set_ss :: #force_inline proc "c" (a: f32) -> __m128 { + return __m128{a, 0, 0, 0} +} +_mm_set1_ps :: #force_inline proc "c" (a: f32) -> __m128 { + return __m128(a) +} +_mm_set_ps1 :: _mm_set1_ps + +_mm_set_ps :: #force_inline proc "c" (a, b, c, d: f32) -> __m128 { + return __m128{d, c, b, a} +} +_mm_setr_ps :: #force_inline proc "c" (a, b, c, d: f32) -> __m128 { + return __m128{a, b, c, d} +} + +_mm_setzero_ps :: #force_inline proc "c" () -> __m128 { + return __m128{0, 0, 0, 0} +} + +_mm_shuffle_ps :: #force_inline proc "c" (a, b: __m128, $MASK: u32) -> __m128 { + return simd.shuffle( + a, b, + u32(MASK) & 0b11, + (u32(MASK)>>2) & 0b11, + ((u32(MASK)>>4) & 0b11)+4, + ((u32(MASK)>>6) & 0b11)+4) +} + + +_mm_unpackhi_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, b, 2, 6, 3, 7) +} +_mm_unpacklo_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, b, 0, 4, 1, 5) +} + +_mm_movehl_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, b, 6, 7, 2, 3) +} +_mm_movelh_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, b, 0, 1, 4, 5) +} + +_mm_movemask_ps :: proc(a: __m128) -> u32 { + return movmskps(a) +} + +_mm_load_ss :: #force_inline proc "c" (p: ^f32) -> __m128 { + return __m128{p^, 0, 0, 0} +} +_mm_load1_ps :: #force_inline proc "c" (p: ^f32) -> __m128 { + a := p^ + return __m128(a) +} +_mm_load_ps1 :: _mm_load1_ps + +_mm_load_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { + return (^__m128)(p)^ +} + +_mm_loadu_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { + dst := _mm_undefined_ps() + intrinsics.mem_copy_non_overlapping(&dst, p, size_of(__m128)) + return dst +} + +_mm_loadr_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { + return simd.lanes_reverse(_mm_load_ps(p)) +} + +_mm_loadu_si64 :: #force_inline proc "c" (mem_addr: rawptr) -> __m128i { + a := intrinsics.unaligned_load((^i64)(mem_addr)) + return __m128i{a, 0} +} + +_mm_store_ss :: #force_inline proc "c" (p: ^f32, a: __m128) { + p^ = simd.extract(a, 0) +} + +_mm_store1_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { + b := simd.swizzle(a, 0, 0, 0, 0) + (^__m128)(p)^ = b +} +_mm_store_ps1 :: _mm_store1_ps + + +_mm_store_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { + (^__m128)(p)^ = a +} +_mm_storeu_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { + b := a + intrinsics.mem_copy_non_overlapping(p, &b, size_of(__m128)) +} +_mm_storer_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { + (^__m128)(p)^ = simd.lanes_reverse(a) +} + + +_mm_move_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return simd.shuffle(a, b, 4, 1, 2, 3) +} + +_mm_sfence :: #force_inline proc "c" () { + sfence() +} + +_mm_getcsr :: #force_inline proc "c" () -> (result: u32) { + stmxcsr(&result) + return result +} + +_mm_setcsr :: #force_inline proc "c" (val: u32) { + val := val + ldmxcsr(&val) +} + +_MM_GET_EXCEPTION_MASK :: #force_inline proc "c" () -> u32 { + return _mm_getcsr() & _MM_MASK_MASK +} +_MM_GET_EXCEPTION_STATE :: #force_inline proc "c" () -> u32 { + return _mm_getcsr() & _MM_EXCEPT_MASK +} +_MM_GET_FLUSH_ZERO_MODE :: #force_inline proc "c" () -> u32 { + return _mm_getcsr() & _MM_FLUSH_ZERO_MASK +} +_MM_GET_ROUNDING_MODE :: #force_inline proc "c" () -> u32 { + return _mm_getcsr() & _MM_ROUND_MASK +} + +_MM_SET_EXCEPTION_MASK :: #force_inline proc "c" (x: u32) { + _mm_setcsr((_mm_getcsr() &~ _MM_MASK_MASK) | x) +} +_MM_SET_EXCEPTION_STATE :: #force_inline proc "c" (x: u32) { + _mm_setcsr((_mm_getcsr() &~ _MM_EXCEPT_MASK) | x) +} +_MM_SET_FLUSH_ZERO_MODE :: #force_inline proc "c" (x: u32) { + _mm_setcsr((_mm_getcsr() &~ _MM_FLUSH_ZERO_MASK) | x) +} +_MM_SET_ROUNDING_MODE :: #force_inline proc "c" (x: u32) { + _mm_setcsr((_mm_getcsr() &~ _MM_ROUND_MASK) | x) +} + +_mm_prefetch :: #force_inline proc "c" (p: rawptr, $STRATEGY: u32) { + prefetch(p, (STRATEGY>>2)&1, STRATEGY&3, 1) +} + + +_mm_undefined_ps :: #force_inline proc "c" () -> __m128 { + return _mm_set1_ps(0) +} + +_MM_TRANSPOSE4_PS :: #force_inline proc "c" (row0, row1, row2, row3: ^__m128) { + tmp0 := _mm_unpacklo_ps(row0^, row1^) + tmp1 := _mm_unpacklo_ps(row2^, row3^) + tmp2 := _mm_unpackhi_ps(row0^, row1^) + tmp3 := _mm_unpackhi_ps(row2^, row3^) + + row0^ = _mm_movelh_ps(tmp0, tmp2) + row1^ = _mm_movelh_ps(tmp2, tmp0) + row2^ = _mm_movelh_ps(tmp1, tmp3) + row3^ = _mm_movelh_ps(tmp3, tmp1) +} + + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name="llvm.x86.sse.add.ss") + addss :: proc(a, b: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.sub.ss") + subss :: proc(a, b: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.mul.ss") + mulss :: proc(a, b: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.div.ss") + divss :: proc(a, b: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.sqrt.ss") + sqrtss :: proc(a: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.sqrt.ps") + sqrtps :: proc(a: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.rcp.ss") + rcpss :: proc(a: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.rcp.ps") + rcpps :: proc(a: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.rsqrt.ss") + rsqrtss :: proc(a: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.rsqrt.ps") + rsqrtps :: proc(a: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.min.ss") + minss :: proc(a, b: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.min.ps") + minps :: proc(a, b: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.max.ss") + maxss :: proc(a, b: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.max.ps") + maxps :: proc(a, b: __m128) -> __m128 --- + @(link_name="llvm.x86.sse.movmsk.ps") + movmskps :: proc(a: __m128) -> u32 --- + @(link_name="llvm.x86.sse.cmp.ps") + cmpps :: proc(a, b: __m128, #const imm8: u8) -> __m128 --- + @(link_name="llvm.x86.sse.comieq.ss") + comieq_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.comilt.ss") + comilt_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.comile.ss") + comile_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.comigt.ss") + comigt_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.comige.ss") + comige_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.comineq.ss") + comineq_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.ucomieq.ss") + ucomieq_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.ucomilt.ss") + ucomilt_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.ucomile.ss") + ucomile_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.ucomigt.ss") + ucomigt_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.ucomige.ss") + ucomige_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.ucomineq.ss") + ucomineq_ss :: proc(a, b: __m128) -> b32 --- + @(link_name="llvm.x86.sse.cvtss2si") + cvtss2si :: proc(a: __m128) -> i32 --- + @(link_name="llvm.x86.sse.cvttss2si") + cvttss2si :: proc(a: __m128) -> i32 --- + @(link_name="llvm.x86.sse.cvtsi2ss") + cvtsi2ss :: proc(a: __m128, b: i32) -> __m128 --- + @(link_name="llvm.x86.sse.sfence") + sfence :: proc() --- + @(link_name="llvm.x86.sse.stmxcsr") + stmxcsr :: proc(p: rawptr) --- + @(link_name="llvm.x86.sse.ldmxcsr") + ldmxcsr :: proc(p: rawptr) --- + @(link_name="llvm.prefetch") + prefetch :: proc(p: rawptr, #const rw, loc, ty: u32) --- + @(link_name="llvm.x86.sse.cmp.ss") + cmpss :: proc(a, b: __m128, #const imm8: u8) -> __m128 --- +} diff --git a/core/simd/x86/types.odin b/core/simd/x86/types.odin new file mode 100644 index 000000000..ef6342542 --- /dev/null +++ b/core/simd/x86/types.odin @@ -0,0 +1,44 @@ +//+build amd64 +package simd_amd64 + +bf16 :: u16 + +__m128i :: #simd[2]i64 +__m128 :: #simd[4]f32 +__m128d :: #simd[2]f64 + +__m256i :: #simd[4]i64 +__m256 :: #simd[8]f32 +__m256d :: #simd[4]f64 + +__m512i :: #simd[8]i64 +__m512 :: #simd[16]f32 +__m512d :: #simd[8]f64 + +__m128bh :: #simd[8]bf16 +__m256bh :: #simd[16]bf16 +__m512bh :: #simd[32]bf16 + + +/// The `__mmask64` type used in AVX-512 intrinsics, a 64-bit integer +__mmask64 :: u64 + +/// The `__mmask32` type used in AVX-512 intrinsics, a 32-bit integer +__mmask32 :: u32 + +/// The `__mmask16` type used in AVX-512 intrinsics, a 16-bit integer +__mmask16 :: u16 + +/// The `__mmask8` type used in AVX-512 intrinsics, a 8-bit integer +__mmask8 :: u8 + +/// The `_MM_CMPINT_ENUM` type used to specify comparison operations in AVX-512 intrinsics. +_MM_CMPINT_ENUM :: i32 + +/// The `MM_MANTISSA_NORM_ENUM` type used to specify mantissa normalized operations in AVX-512 intrinsics. +_MM_MANTISSA_NORM_ENUM :: i32 + +/// The `MM_MANTISSA_SIGN_ENUM` type used to specify mantissa signed operations in AVX-512 intrinsics. +_MM_MANTISSA_SIGN_ENUM :: i32 + +_MM_PERM_ENUM :: i32 \ No newline at end of file From 2185dada56a7c649d11c4e2edea63b0595f69061 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 14:26:02 +0100 Subject: [PATCH 202/254] Change package name --- core/simd/x86/sse.odin | 4 ++-- core/simd/x86/types.odin | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index 79b13f667..1af5a65f1 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -1,5 +1,5 @@ -//+build amd64 -package simd_amd64 +//+build i386, amd64 +package simd_x86 import "core:intrinsics" import "core:simd" diff --git a/core/simd/x86/types.odin b/core/simd/x86/types.odin index ef6342542..3c94d74a0 100644 --- a/core/simd/x86/types.odin +++ b/core/simd/x86/types.odin @@ -1,5 +1,5 @@ -//+build amd64 -package simd_amd64 +//+build i386, amd64 +package simd_x86 bf16 :: u16 From f137b927b6b2199539e6e9004de4c5593078b9b6 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 27 May 2022 15:47:29 +0200 Subject: [PATCH 203/254] Refactor ms_craziness.h --- src/build_settings.cpp | 16 +- src/microsoft_craziness.h | 776 +++++++++++++++++++------------------- 2 files changed, 402 insertions(+), 390 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index b458d8308..9c996aef3 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1221,12 +1221,13 @@ bool init_build_paths(String init_filename) { } if (bc->pdb_filepath.len > 0) { - bc->build_paths[BuildPath_PDB] = path_from_string(ha, bc->pdb_filepath); + bc->build_paths[BuildPath_PDB] = path_from_string(ha, bc->pdb_filepath); } if ((bc->command_kind & Command__does_build) && (!bc->ignore_microsoft_magic)) { // NOTE(ic): It would be nice to extend this so that we could specify the Visual Studio version that we want instead of defaulting to the latest. Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); + defer (mc_free_all()); if (find_result.windows_sdk_version == 0) { gb_printf_err("Windows SDK not found.\n"); @@ -1256,13 +1257,6 @@ bool init_build_paths(String init_filename) { bc->build_paths[BuildPath_VS_LIB] = path_from_string(ha, find_result.vs_library_path); } } - - gb_free(ha, find_result.windows_sdk_root.text); - gb_free(ha, find_result.windows_sdk_um_library_path.text); - gb_free(ha, find_result.windows_sdk_ucrt_library_path.text); - gb_free(ha, find_result.vs_exe_path.text); - gb_free(ha, find_result.vs_library_path.text); - } #endif @@ -1344,9 +1338,9 @@ bool init_build_paths(String init_filename) { output_name.len -= 1; } output_name = remove_directory_from_path(output_name); - output_name = remove_extension_from_path(output_name); - output_name = copy_string(ha, string_trim_whitespace(output_name)); - output_path = path_from_string(ha, output_name); + output_name = remove_extension_from_path(output_name); + output_name = copy_string(ha, string_trim_whitespace(output_name)); + output_path = path_from_string(ha, output_name); // Replace extension. if (output_path.ext.len > 0) { diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index b4f815284..c2efb6b84 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -45,53 +45,92 @@ // // Here is the API you need to know about: // - - gb_global gbAllocator mc_allocator = heap_allocator(); struct Find_Result { - int windows_sdk_version; // Zero if no Windows SDK found. + int windows_sdk_version; // Zero if no Windows SDK found. - wchar_t const *windows_sdk_root; - wchar_t const *windows_sdk_um_library_path; - wchar_t const *windows_sdk_ucrt_library_path; + wchar_t const *windows_sdk_root; + wchar_t const *windows_sdk_um_library_path; + wchar_t const *windows_sdk_ucrt_library_path; - wchar_t const *vs_exe_path; - wchar_t const *vs_library_path; + wchar_t const *vs_exe_path; + wchar_t const *vs_library_path; }; struct Find_Result_Utf8 { - int windows_sdk_version; // Zero if no Windows SDK found. + int windows_sdk_version; // Zero if no Windows SDK found. - String windows_sdk_root; - String windows_sdk_um_library_path; - String windows_sdk_ucrt_library_path; + String windows_sdk_root; + String windows_sdk_um_library_path; + String windows_sdk_ucrt_library_path; - String vs_exe_path; - String vs_library_path; + String vs_exe_path; + String vs_library_path; }; - -Find_Result find_visual_studio_and_windows_sdk(); Find_Result_Utf8 find_visual_studio_and_windows_sdk_utf8(); -void free_resources(Find_Result *result) { - // free(result->windows_sdk_root); - // free(result->windows_sdk_um_library_path); - // free(result->windows_sdk_ucrt_library_path); - // free(result->vs_exe_path); - // free(result->vs_library_path); +String mc_wstring_to_string(wchar_t const *str) { + return string16_to_string(mc_allocator, make_string16_c(str)); } -void free_resources(Find_Result_Utf8 *result) { - // gbAllocator a = heap_allocator(); - // gb_free(a, result->windows_sdk_root.text); - // gb_free(a, result->windows_sdk_um_library_path.text); - // gb_free(a, result->windows_sdk_ucrt_library_path.text); - // gb_free(a, result->vs_exe_path.text); - // gb_free(a, result->vs_library_path.text); +String16 mc_string_to_wstring(String str) { + return string_to_string16(mc_allocator, str); } +String mc_concat(String a, String b) { + return concatenate_strings(mc_allocator, a, b); +} + +String mc_concat(String a, String b, String c) { + return concatenate3_strings(mc_allocator, a, b, c); +} + +void mc_free(String str) { + gb_free(mc_allocator, str.text); +} + +void mc_free(String16 str) { + gb_free(mc_allocator, str.text); +} + +void mc_free_all() { + gb_free_all(mc_allocator); +} + +typedef struct _MC_Find_Data { + DWORD file_attributes; + String filename; +} MC_Find_Data; + + +HANDLE mc_find_first(String wildcard, MC_Find_Data *find_data) { + WIN32_FIND_DATAW _find_data; + + String16 wildcard_wide = mc_string_to_wstring(wildcard); + defer (mc_free(wildcard_wide)); + + HANDLE handle = FindFirstFileW(wildcard_wide.text, &_find_data); + if (handle == INVALID_HANDLE_VALUE) return false; + + find_data->file_attributes = _find_data.dwFileAttributes; + find_data->filename = mc_wstring_to_string(_find_data.cFileName); + return handle; +} + +bool mc_find_next(HANDLE handle, MC_Find_Data *find_data) { + WIN32_FIND_DATAW _find_data; + bool success = FindNextFileW(handle, &_find_data); + + find_data->file_attributes = _find_data.dwFileAttributes; + find_data->filename = mc_wstring_to_string(_find_data.cFileName); + return success; +} + +void mc_find_close(HANDLE handle) { + FindClose(handle); +} // // Call find_visual_studio_and_windows_sdk, look at the resulting @@ -149,474 +188,453 @@ typedef const WCHAR* LPCOLESTR; struct DECLSPEC_UUID("B41463C3-8866-43B5-BC33-2B0676F7F42E") DECLSPEC_NOVTABLE ISetupInstance : public IUnknown { - virtual HRESULT STDMETHODCALLTYPE GetInstanceId(BSTR* pbstrInstanceId) = 0; - virtual HRESULT STDMETHODCALLTYPE GetInstallDate(LPFILETIME pInstallDate) = 0; - virtual HRESULT STDMETHODCALLTYPE GetInstallationName(BSTR* pbstrInstallationName) = 0; - virtual HRESULT STDMETHODCALLTYPE GetInstallationPath(BSTR* pbstrInstallationPath) = 0; - virtual HRESULT STDMETHODCALLTYPE GetInstallationVersion(BSTR* pbstrInstallationVersion) = 0; - virtual HRESULT STDMETHODCALLTYPE GetDisplayName(LCID lcid, BSTR* pbstrDisplayName) = 0; - virtual HRESULT STDMETHODCALLTYPE GetDescription(LCID lcid, BSTR* pbstrDescription) = 0; - virtual HRESULT STDMETHODCALLTYPE ResolvePath(LPCOLESTR pwszRelativePath, BSTR* pbstrAbsolutePath) = 0; + virtual HRESULT STDMETHODCALLTYPE GetInstanceId(BSTR* pbstrInstanceId) = 0; + virtual HRESULT STDMETHODCALLTYPE GetInstallDate(LPFILETIME pInstallDate) = 0; + virtual HRESULT STDMETHODCALLTYPE GetInstallationName(BSTR* pbstrInstallationName) = 0; + virtual HRESULT STDMETHODCALLTYPE GetInstallationPath(BSTR* pbstrInstallationPath) = 0; + virtual HRESULT STDMETHODCALLTYPE GetInstallationVersion(BSTR* pbstrInstallationVersion) = 0; + virtual HRESULT STDMETHODCALLTYPE GetDisplayName(LCID lcid, BSTR* pbstrDisplayName) = 0; + virtual HRESULT STDMETHODCALLTYPE GetDescription(LCID lcid, BSTR* pbstrDescription) = 0; + virtual HRESULT STDMETHODCALLTYPE ResolvePath(LPCOLESTR pwszRelativePath, BSTR* pbstrAbsolutePath) = 0; }; struct DECLSPEC_UUID("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848") DECLSPEC_NOVTABLE IEnumSetupInstances : public IUnknown { - virtual HRESULT STDMETHODCALLTYPE Next(ULONG celt, ISetupInstance** rgelt, ULONG* pceltFetched) = 0; - virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt) = 0; - virtual HRESULT STDMETHODCALLTYPE Reset(void) = 0; - virtual HRESULT STDMETHODCALLTYPE Clone(IEnumSetupInstances** ppenum) = 0; + virtual HRESULT STDMETHODCALLTYPE Next(ULONG celt, ISetupInstance** rgelt, ULONG* pceltFetched) = 0; + virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt) = 0; + virtual HRESULT STDMETHODCALLTYPE Reset(void) = 0; + virtual HRESULT STDMETHODCALLTYPE Clone(IEnumSetupInstances** ppenum) = 0; }; struct DECLSPEC_UUID("42843719-DB4C-46C2-8E7C-64F1816EFD5B") DECLSPEC_NOVTABLE ISetupConfiguration : public IUnknown { - virtual HRESULT STDMETHODCALLTYPE EnumInstances(IEnumSetupInstances** ppEnumInstances) = 0; - virtual HRESULT STDMETHODCALLTYPE GetInstanceForCurrentProcess(ISetupInstance** ppInstance) = 0; - virtual HRESULT STDMETHODCALLTYPE GetInstanceForPath(LPCWSTR wzPath, ISetupInstance** ppInstance) = 0; + virtual HRESULT STDMETHODCALLTYPE EnumInstances(IEnumSetupInstances** ppEnumInstances) = 0; + virtual HRESULT STDMETHODCALLTYPE GetInstanceForCurrentProcess(ISetupInstance** ppInstance) = 0; + virtual HRESULT STDMETHODCALLTYPE GetInstanceForPath(LPCWSTR wzPath, ISetupInstance** ppInstance) = 0; }; // The beginning of the actual code that does things. struct Version_Data { - i32 best_version[4]; // For Windows 8 versions, only two of these numbers are used. - wchar_t const *best_name; + i32 best_version[4]; // For Windows 8 versions, only two of these numbers are used. + wchar_t const *best_name; }; bool os_file_exists(wchar_t const *name) { - // @Robustness: What flags do we really want to check here? + // @Robustness: What flags do we really want to check here? - auto attrib = GetFileAttributesW(name); - if (attrib == INVALID_FILE_ATTRIBUTES) return false; - if (attrib & FILE_ATTRIBUTE_DIRECTORY) return false; + auto attrib = GetFileAttributesW(name); + if (attrib == INVALID_FILE_ATTRIBUTES) return false; + if (attrib & FILE_ATTRIBUTE_DIRECTORY) return false; - return true; + return true; } wchar_t *concat(wchar_t const *a, wchar_t const *b, wchar_t const *c = nullptr, wchar_t const *d = nullptr) { - // Concatenate up to 4 wide strings together. Allocated with malloc. - // If you don't like that, use a programming language that actually - // helps you with using custom allocators. Or just edit the code. + // Concatenate up to 4 wide strings together. Allocated with malloc. + // If you don't like that, use a programming language that actually + // helps you with using custom allocators. Or just edit the code. - isize len_a = string16_len(a); - isize len_b = string16_len(b); - isize len_c = string16_len(c); - isize len_d = string16_len(d); + isize len_a = string16_len(a); + isize len_b = string16_len(b); + isize len_c = string16_len(c); + isize len_d = string16_len(d); - wchar_t *result = (wchar_t *)calloc(2, (len_a + len_b + len_c + len_d + 1)); - gb_memmove(result, a, len_a*2); - gb_memmove(result + len_a, b, len_b*2); + wchar_t *result = (wchar_t *)calloc(2, (len_a + len_b + len_c + len_d + 1)); + gb_memmove(result, a, len_a*2); + gb_memmove(result + len_a, b, len_b*2); - if (c) gb_memmove(result + len_a + len_b, c, len_c * 2); - if (d) gb_memmove(result + len_a + len_b + len_c, d, len_d * 2); + if (c) gb_memmove(result + len_a + len_b, c, len_c * 2); + if (d) gb_memmove(result + len_a + len_b + len_c, d, len_d * 2); - result[len_a + len_b + len_c + len_d] = 0; + result[len_a + len_b + len_c + len_d] = 0; - return result; + return result; } -typedef void (*Visit_Proc_W)(wchar_t const *short_name, wchar_t const *full_name, Version_Data *data); -bool visit_files_w(wchar_t const *dir_name, Version_Data *data, Visit_Proc_W proc) { +struct Version_Data_Utf8 { + i32 best_version[4]; // For Windows 8 versions, only two of these numbers are used. + String best_name; +}; - // Visit everything in one folder (non-recursively). If it's a directory - // that doesn't start with ".", call the visit proc on it. The visit proc - // will see if the filename conforms to the expected versioning pattern. +typedef void (*MC_Visit_Proc)(String short_name, String full_name, Version_Data_Utf8 *data); +bool mc_visit_files(String dir_name, Version_Data_Utf8 *data, MC_Visit_Proc proc) { - auto wildcard_name = concat(dir_name, L"\\*"); - defer (free(wildcard_name)); + // Visit everything in one folder (non-recursively). If it's a directory + // that doesn't start with ".", call the visit proc on it. The visit proc + // will see if the filename conforms to the expected versioning pattern. - WIN32_FIND_DATAW find_data; - auto handle = FindFirstFileW(wildcard_name, &find_data); - if (handle == INVALID_HANDLE_VALUE) return false; + String wildcard_name = mc_concat(dir_name, str_lit("\\*")); + defer (mc_free(wildcard_name)); - while (true) { - if ((find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && (find_data.cFileName[0] != '.')) { - auto full_name = concat(dir_name, L"\\", find_data.cFileName); - defer (free(full_name)); + MC_Find_Data find_data; - proc(find_data.cFileName, full_name, data); - } + HANDLE handle = mc_find_first(wildcard_name, &find_data); + if (handle == INVALID_HANDLE_VALUE) return false; - auto success = FindNextFileW(handle, &find_data); - if (!success) break; - } + bool success = true; + while (success) { + if ((find_data.file_attributes & FILE_ATTRIBUTE_DIRECTORY) && (find_data.filename[0] != '.')) { + String full_name = mc_concat(dir_name, str_lit("\\"), find_data.filename); + defer (mc_free(full_name)); - FindClose(handle); + proc(find_data.filename, full_name, data); + } - return true; + success = mc_find_next(handle, &find_data); + if (!success) break; + } + mc_find_close(handle); + return true; } +String find_windows_kit_root(HKEY key, String const version) { + // Given a key to an already opened registry entry, + // get the value stored under the 'version' subkey. + // If that's not the right terminology, hey, I never do registry stuff. -wchar_t *find_windows_kit_root(HKEY key, wchar_t const *version) { - // Given a key to an already opened registry entry, - // get the value stored under the 'version' subkey. - // If that's not the right terminology, hey, I never do registry stuff. + char *version_str = (char*)version.text; - DWORD required_length; - auto rc = RegQueryValueExW(key, version, NULL, NULL, NULL, &required_length); - if (rc != 0) return NULL; + DWORD required_length; + auto rc = RegQueryValueExA(key, version_str, NULL, NULL, NULL, &required_length); + if (rc != 0) return {}; - DWORD length = required_length + 2; // The +2 is for the maybe optional zero later on. Probably we are over-allocating. - wchar_t *value = (wchar_t *)calloc(1, length); - if (!value) return NULL; + DWORD length = required_length + 2; // The +2 is for the maybe optional zero later on. Probably we are over-allocating. + char *c_str = gb_alloc_array(mc_allocator, char, length); - rc = RegQueryValueExW(key, version, NULL, NULL, (LPBYTE)value, &length); // We know that version is zero-terminated... - if (rc != 0) return NULL; + rc = RegQueryValueExA(key, version_str, NULL, NULL, (LPBYTE)c_str, &length); // We know that version is zero-terminated... + if (rc != 0) return {}; - // The documentation says that if the string for some reason was not stored - // with zero-termination, we need to manually terminate it. Sigh!! + // The documentation says that if the string for some reason was not stored + // with zero-termination, we need to manually terminate it. Sigh!! - if (value[length]) { - value[length+1] = 0; - } + if (c_str[required_length]) { + c_str[required_length+1] = 0; + } - return value; + String value = make_string_c(c_str); + + return value; } -void win10_best(wchar_t const *short_name, wchar_t const *full_name, Version_Data *data) { - // Find the Windows 10 subdirectory with the highest version number. +void win10_best(String short_name, String full_name, Version_Data_Utf8 *data) { + // Find the Windows 10 subdirectory with the highest version number. - int i0, i1, i2, i3; - auto success = swscanf_s(short_name, L"%d.%d.%d.%d", &i0, &i1, &i2, &i3); - if (success < 4) return; + int i0, i1, i2, i3; + auto success = sscanf_s((const char *const)short_name.text, "%d.%d.%d.%d", &i0, &i1, &i2, &i3); + if (success < 4) return; - if (i0 < data->best_version[0]) return; - else if (i0 == data->best_version[0]) { - if (i1 < data->best_version[1]) return; - else if (i1 == data->best_version[1]) { - if (i2 < data->best_version[2]) return; - else if (i2 == data->best_version[2]) { - if (i3 < data->best_version[3]) return; - } - } - } + if (i0 < data->best_version[0]) return; + else if (i0 == data->best_version[0]) { + if (i1 < data->best_version[1]) return; + else if (i1 == data->best_version[1]) { + if (i2 < data->best_version[2]) return; + else if (i2 == data->best_version[2]) { + if (i3 < data->best_version[3]) return; + } + } + } - // we have to copy_string and free here because visit_files free's the full_name string - // after we execute this function, so Win*_Data would contain an invalid pointer. - if (data->best_name) free((void *)data->best_name); - data->best_name = _wcsdup(full_name); + // we have to copy_string and free here because visit_files free's the full_name string + // after we execute this function, so Win*_Data would contain an invalid pointer. + if (data->best_name.len > 0) mc_free(data->best_name); - if (data->best_name) { - data->best_version[0] = i0; - data->best_version[1] = i1; - data->best_version[2] = i2; - data->best_version[3] = i3; - } + data->best_name = copy_string(mc_allocator, full_name); + + if (data->best_name.len > 0) { + data->best_version[0] = i0; + data->best_version[1] = i1; + data->best_version[2] = i2; + data->best_version[3] = i3; + } } -void win8_best(wchar_t const *short_name, wchar_t const *full_name, Version_Data *data) { - // Find the Windows 8 subdirectory with the highest version number. +void win8_best(String short_name, String full_name, Version_Data_Utf8 *data) { + // Find the Windows 8 subdirectory with the highest version number. - int i0, i1; - auto success = swscanf_s(short_name, L"winv%d.%d", &i0, &i1); - if (success < 2) return; + int i0, i1; + auto success = sscanf_s((const char *const)short_name.text, "winv%d.%d", &i0, &i1); + if (success < 2) return; - if (i0 < data->best_version[0]) return; - else if (i0 == data->best_version[0]) { - if (i1 < data->best_version[1]) return; - } + if (i0 < data->best_version[0]) return; + else if (i0 == data->best_version[0]) { + if (i1 < data->best_version[1]) return; + } - // we have to copy_string and free here because visit_files free's the full_name string - // after we execute this function, so Win*_Data would contain an invalid pointer. - if (data->best_name) free((void *)data->best_name); - data->best_name = _wcsdup(full_name); + // we have to copy_string and free here because visit_files free's the full_name string + // after we execute this function, so Win*_Data would contain an invalid pointer. + if (data->best_name.len > 0) mc_free(data->best_name); + data->best_name = copy_string(mc_allocator, full_name); - if (data->best_name) { - data->best_version[0] = i0; - data->best_version[1] = i1; - } + if (data->best_name.len > 0) { + data->best_version[0] = i0; + data->best_version[1] = i1; + } } -void find_windows_kit_root(Find_Result *result) { - // Information about the Windows 10 and Windows 8 development kits - // is stored in the same place in the registry. We open a key - // to that place, first checking preferntially for a Windows 10 kit, - // then, if that's not found, a Windows 8 kit. +void find_windows_kit_root(Find_Result_Utf8 *result) { + // Information about the Windows 10 and Windows 8 development kits + // is stored in the same place in the registry. We open a key + // to that place, first checking preferntially for a Windows 10 kit, + // then, if that's not found, a Windows 8 kit. - HKEY main_key; + HKEY main_key; - auto rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", - 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &main_key); - if (rc != S_OK) return; - defer (RegCloseKey(main_key)); + auto rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", + 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &main_key); + if (rc != S_OK) return; + defer (RegCloseKey(main_key)); - // Look for a Windows 10 entry. - auto windows10_root = find_windows_kit_root(main_key, L"KitsRoot10"); + // Look for a Windows 10 entry. + String windows10_root = find_windows_kit_root(main_key, str_lit("KitsRoot10")); + if (windows10_root.len > 0) { + defer (mc_free(windows10_root)); - if (windows10_root) { - defer (free(windows10_root)); + String windows10_lib = mc_concat(windows10_root, str_lit("Lib")); + defer (mc_free(windows10_lib)); + Version_Data_Utf8 data = {0}; + mc_visit_files(windows10_lib, &data, win10_best); + if (data.best_name.len > 0) { + result->windows_sdk_version = 10; + result->windows_sdk_root = mc_concat(data.best_name, str_lit("\\")); + return; + } + mc_free(data.best_name); + } - Version_Data data = {0}; - auto windows10_lib = concat(windows10_root, L"Lib"); - defer (free(windows10_lib)); + // Look for a Windows 8 entry. + String windows8_root = find_windows_kit_root(main_key, str_lit("KitsRoot81")); + if (windows8_root.len > 0) { + defer (mc_free(windows8_root)); - visit_files_w(windows10_lib, &data, win10_best); - if (data.best_name) { - result->windows_sdk_version = 10; - result->windows_sdk_root = concat(data.best_name, L"\\"); - return; - } - } + String windows8_lib = mc_concat(windows8_root, str_lit("Lib")); + defer (mc_free(windows8_lib)); - // Look for a Windows 8 entry. - auto windows8_root = find_windows_kit_root(main_key, L"KitsRoot81"); - - if (windows8_root) { - defer (free(windows8_root)); - - auto windows8_lib = concat(windows8_root, L"Lib"); - defer (free(windows8_lib)); - - Version_Data data = {0}; - visit_files_w(windows8_lib, &data, win8_best); - if (data.best_name) { - result->windows_sdk_version = 8; - result->windows_sdk_root = concat(data.best_name, L"\\"); - return; - } - } - - // If we get here, we failed to find anything. + Version_Data_Utf8 data = {0}; + mc_visit_files(windows8_lib, &data, win8_best); + if (data.best_name.len > 0) { + result->windows_sdk_version = 8; + result->windows_sdk_root = mc_concat(data.best_name, str_lit("\\")); + return; + } + mc_free(data.best_name); + } + // If we get here, we failed to find anything. } - bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *result) { - // The name of this procedure is kind of cryptic. Its purpose is - // to fight through Microsoft craziness. The things that the fine - // Visual Studio team want you to do, JUST TO FIND A SINGLE FOLDER - // THAT EVERYONE NEEDS TO FIND, are ridiculous garbage. + // The name of this procedure is kind of cryptic. Its purpose is + // to fight through Microsoft craziness. The things that the fine + // Visual Studio team want you to do, JUST TO FIND A SINGLE FOLDER + // THAT EVERYONE NEEDS TO FIND, are ridiculous garbage. - // For earlier versions of Visual Studio, you'd find this information in the registry, - // similarly to the Windows Kits above. But no, now it's the future, so to ask the - // question "Where is the Visual Studio folder?" you have to do a bunch of COM object - // instantiation, enumeration, and querying. (For extra bonus points, try doing this in - // a new, underdeveloped programming language where you don't have COM routines up - // and running yet. So fun.) - // - // If all this COM object instantiation, enumeration, and querying doesn't give us - // a useful result, we drop back to the registry-checking method. + // For earlier versions of Visual Studio, you'd find this information in the registry, + // similarly to the Windows Kits above. But no, now it's the future, so to ask the + // question "Where is the Visual Studio folder?" you have to do a bunch of COM object + // instantiation, enumeration, and querying. (For extra bonus points, try doing this in + // a new, underdeveloped programming language where you don't have COM routines up + // and running yet. So fun.) + // + // If all this COM object instantiation, enumeration, and querying doesn't give us + // a useful result, we drop back to the registry-checking method. - auto rc = CoInitialize(NULL); - // "Subsequent valid calls return false." So ignore false. - if (rc != S_OK) return false; + auto rc = CoInitialize(NULL); + // "Subsequent valid calls return false." So ignore false. + if (rc != S_OK) return false; - GUID my_uid = {0x42843719, 0xDB4C, 0x46C2, {0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B}}; - GUID CLSID_SetupConfiguration = {0x177F0C4A, 0x1CD3, 0x4DE7, {0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D}}; + GUID my_uid = {0x42843719, 0xDB4C, 0x46C2, {0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B}}; + GUID CLSID_SetupConfiguration = {0x177F0C4A, 0x1CD3, 0x4DE7, {0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D}}; - ISetupConfiguration *config = NULL; - HRESULT hr = 0; - hr = CoCreateInstance(CLSID_SetupConfiguration, NULL, CLSCTX_INPROC_SERVER, my_uid, (void **)&config); - if (hr == 0) { - defer (config->Release()); + ISetupConfiguration *config = NULL; + HRESULT hr = 0; + hr = CoCreateInstance(CLSID_SetupConfiguration, NULL, CLSCTX_INPROC_SERVER, my_uid, (void **)&config); + if (hr == 0) { + defer (config->Release()); - IEnumSetupInstances *instances = NULL; - hr = config->EnumInstances(&instances); - if (hr != 0) return false; - if (!instances) return false; - defer (instances->Release()); + IEnumSetupInstances *instances = NULL; + hr = config->EnumInstances(&instances); + if (hr != 0) return false; + if (!instances) return false; + defer (instances->Release()); - for (;;) { - ULONG found = 0; - ISetupInstance *instance = NULL; - auto hr = instances->Next(1, &instance, &found); - if (hr != S_OK) break; + for (;;) { + ULONG found = 0; + ISetupInstance *instance = NULL; + auto hr = instances->Next(1, &instance, &found); + if (hr != S_OK) break; - defer (instance->Release()); + defer (instance->Release()); - BSTR bstr_inst_path; - hr = instance->GetInstallationPath(&bstr_inst_path); - if (hr != S_OK) continue; - defer (SysFreeString(bstr_inst_path)); + BSTR bstr_inst_path; + hr = instance->GetInstallationPath(&bstr_inst_path); + if (hr != S_OK) continue; + defer (SysFreeString(bstr_inst_path)); - auto tools_filename = concat(bstr_inst_path, L"\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); - defer (free(tools_filename)); + auto tools_filename = concat(bstr_inst_path, L"\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); + defer (free(tools_filename)); - FILE *f = nullptr; - auto open_result = _wfopen_s(&f, tools_filename, L"rt"); - if (open_result != 0) continue; - if (!f) continue; - defer (fclose(f)); + FILE *f = nullptr; + auto open_result = _wfopen_s(&f, tools_filename, L"rt"); + if (open_result != 0) continue; + if (!f) continue; + defer (fclose(f)); - LARGE_INTEGER tools_file_size; - auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); - BOOL success = GetFileSizeEx(file_handle, &tools_file_size); - if (!success) continue; + LARGE_INTEGER tools_file_size; + auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); + BOOL success = GetFileSizeEx(file_handle, &tools_file_size); + if (!success) continue; - auto version_bytes = (tools_file_size.QuadPart + 1) * 2; // Warning: This multiplication by 2 presumes there is no variable-length encoding in the wchars (wacky characters in the file could betray this expectation). - if (version_bytes > 0x7FFFFFFF) continue; // Avoid overflow. + auto version_bytes = (tools_file_size.QuadPart + 1) * 2; // Warning: This multiplication by 2 presumes there is no variable-length encoding in the wchars (wacky characters in the file could betray this expectation). + if (version_bytes > 0x7FFFFFFF) continue; // Avoid overflow. - wchar_t *version = (wchar_t *)calloc(1, (usize)version_bytes); - defer (free(version)); + wchar_t *version = (wchar_t *)calloc(1, (usize)version_bytes); + defer (free(version)); - auto read_result = fgetws(version, (int)version_bytes, f); - if (!read_result) continue; + auto read_result = fgetws(version, (int)version_bytes, f); + if (!read_result) continue; - auto version_tail = wcschr(version, '\n'); - if (version_tail) *version_tail = 0; // Stomp the data, because nobody cares about it. + auto version_tail = wcschr(version, '\n'); + if (version_tail) *version_tail = 0; // Stomp the data, because nobody cares about it. - wchar_t *library_path = nullptr; - if (build_context.metrics.arch == TargetArch_amd64) { - library_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x64\\"); - } else if (build_context.metrics.arch == TargetArch_i386) { - library_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x86\\"); - } else { - continue; - } + wchar_t *library_path = nullptr; + if (build_context.metrics.arch == TargetArch_amd64) { + library_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x64\\"); + } else if (build_context.metrics.arch == TargetArch_i386) { + library_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x86\\"); + } else { + continue; + } - auto library_file = concat(library_path, L"vcruntime.lib"); // @Speed: Could have library_path point to this string, with a smaller count, to save on memory flailing! + auto library_file = concat(library_path, L"vcruntime.lib"); // @Speed: Could have library_path point to this string, with a smaller count, to save on memory flailing! - if (os_file_exists(library_file)) { - wchar_t *link_exe_path = nullptr; - if (build_context.metrics.arch == TargetArch_amd64) { - link_exe_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx64\\x64\\"); - } else if (build_context.metrics.arch == TargetArch_i386) { - link_exe_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx86\\x86\\"); - } else { - continue; - } + if (os_file_exists(library_file)) { + wchar_t *link_exe_path = nullptr; + if (build_context.metrics.arch == TargetArch_amd64) { + link_exe_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx64\\x64\\"); + } else if (build_context.metrics.arch == TargetArch_i386) { + link_exe_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx86\\x86\\"); + } else { + continue; + } - result->vs_exe_path = link_exe_path; - result->vs_library_path = library_path; - return true; - } + result->vs_exe_path = link_exe_path; + result->vs_library_path = library_path; + return true; + } - /* - Ryan Saunderson said: - "Clang uses the 'SetupInstance->GetInstallationVersion' / ISetupHelper->ParseVersion to find the newest version - and then reads the tools file to define the tools path - which is definitely better than what i did." + /* + Ryan Saunderson said: + "Clang uses the 'SetupInstance->GetInstallationVersion' / ISetupHelper->ParseVersion to find the newest version + and then reads the tools file to define the tools path - which is definitely better than what i did." - So... @Incomplete: Should probably pick the newest version... - */ - } - } + So... @Incomplete: Should probably pick the newest version... + */ + } + } - // If we get here, we didn't find Visual Studio 2017. Try earlier versions. - { - HKEY vs7_key; - rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &vs7_key); - if (rc != S_OK) return false; - defer (RegCloseKey(vs7_key)); + // If we get here, we didn't find Visual Studio 2017. Try earlier versions. + { + HKEY vs7_key; + rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &vs7_key); + if (rc != S_OK) return false; + defer (RegCloseKey(vs7_key)); - // Hardcoded search for 4 prior Visual Studio versions. Is there something better to do here? - wchar_t const *versions[] = { L"14.0", L"13.0", L"12.0", L"11.0", L"10.0", L"9.0", }; - const int NUM_VERSIONS = sizeof(versions) / sizeof(versions[0]); + // Hardcoded search for 4 prior Visual Studio versions. Is there something better to do here? + wchar_t const *versions[] = { L"14.0", L"13.0", L"12.0", L"11.0", L"10.0", L"9.0", }; + const int NUM_VERSIONS = sizeof(versions) / sizeof(versions[0]); - for (int i = 0; i < NUM_VERSIONS; i++) { - wchar_t const *v = versions[i]; + for (int i = 0; i < NUM_VERSIONS; i++) { + wchar_t const *v = versions[i]; - DWORD dw_type; - DWORD cb_data; + DWORD dw_type; + DWORD cb_data; - auto rc = RegQueryValueExW(vs7_key, v, NULL, &dw_type, NULL, &cb_data); - if ((rc == ERROR_FILE_NOT_FOUND) || (dw_type != REG_SZ)) { - continue; - } + auto rc = RegQueryValueExW(vs7_key, v, NULL, &dw_type, NULL, &cb_data); + if ((rc == ERROR_FILE_NOT_FOUND) || (dw_type != REG_SZ)) { + continue; + } - auto buffer = (wchar_t *)calloc(1, cb_data); - if (!buffer) return false; - defer (free(buffer)); + auto buffer = (wchar_t *)calloc(1, cb_data); + if (!buffer) return false; + defer (free(buffer)); - rc = RegQueryValueExW(vs7_key, v, NULL, NULL, (LPBYTE)buffer, &cb_data); - if (rc != 0) continue; + rc = RegQueryValueExW(vs7_key, v, NULL, NULL, (LPBYTE)buffer, &cb_data); + if (rc != 0) continue; - // @Robustness: Do the zero-termination thing suggested in the RegQueryValue docs? + // @Robustness: Do the zero-termination thing suggested in the RegQueryValue docs? - wchar_t *lib_path = nullptr; + wchar_t *lib_path = nullptr; - if (build_context.metrics.arch == TargetArch_amd64) { - lib_path = concat(buffer, L"VC\\Lib\\amd64\\"); - } else if (build_context.metrics.arch == TargetArch_i386) { - lib_path = concat(buffer, L"VC\\Lib\\"); - } else { - continue; - } + if (build_context.metrics.arch == TargetArch_amd64) { + lib_path = concat(buffer, L"VC\\Lib\\amd64\\"); + } else if (build_context.metrics.arch == TargetArch_i386) { + lib_path = concat(buffer, L"VC\\Lib\\"); + } else { + continue; + } - // Check to see whether a vcruntime.lib actually exists here. - auto vcruntime_filename = concat(lib_path, L"vcruntime.lib"); - defer (free(vcruntime_filename)); + // Check to see whether a vcruntime.lib actually exists here. + auto vcruntime_filename = concat(lib_path, L"vcruntime.lib"); + defer (free(vcruntime_filename)); - if (os_file_exists(vcruntime_filename)) { - if (build_context.metrics.arch == TargetArch_amd64) { - result->vs_exe_path = concat(buffer, L"VC\\bin\\"); - } else if (build_context.metrics.arch == TargetArch_i386) { - // result->vs_exe_path = concat(buffer, L"VC\\bin\\amd64_x86\\"); - result->vs_exe_path = concat(buffer, L"VC\\bin\\x86_amd64\\"); - } else { - continue; - } + if (os_file_exists(vcruntime_filename)) { + if (build_context.metrics.arch == TargetArch_amd64) { + result->vs_exe_path = concat(buffer, L"VC\\bin\\"); + } else if (build_context.metrics.arch == TargetArch_i386) { + result->vs_exe_path = concat(buffer, L"VC\\bin\\x86_amd64\\"); + } else { + continue; + } - result->vs_library_path = lib_path; - return true; - } + result->vs_library_path = lib_path; + return true; + } - free(lib_path); - } + free(lib_path); + } + // If we get here, we failed to find anything. + } - // If we get here, we failed to find anything. - } - - return false; + return false; } - -Find_Result find_visual_studio_and_windows_sdk() { - Find_Result result = {}; - - find_windows_kit_root(&result); - - - if (result.windows_sdk_root) { - if (build_context.metrics.arch == TargetArch_amd64) { - result.windows_sdk_um_library_path = concat(result.windows_sdk_root, L"um\\x64\\"); - result.windows_sdk_ucrt_library_path = concat(result.windows_sdk_root, L"ucrt\\x64\\"); - } else if (build_context.metrics.arch == TargetArch_i386) { - result.windows_sdk_um_library_path = concat(result.windows_sdk_root, L"um\\x86\\"); - result.windows_sdk_ucrt_library_path = concat(result.windows_sdk_root, L"ucrt\\x86\\"); - } - } - - bool ok = find_visual_studio_by_fighting_through_microsoft_craziness(&result); - - if (!ok) { - result.vs_exe_path = concat(L"", L""); - result.vs_library_path = concat(L"", L""); - } - - return result; -} - -String mc_wstring_to_string(wchar_t const *str) { - return string16_to_string(mc_allocator, make_string16_c(str)); -} - - Find_Result_Utf8 find_visual_studio_and_windows_sdk_utf8() { - Find_Result result = find_visual_studio_and_windows_sdk(); - defer (free_resources(&result)); + Find_Result_Utf8 r = {}; + find_windows_kit_root(&r); - Find_Result_Utf8 r = {}; - r.windows_sdk_version = result.windows_sdk_version; + if (r.windows_sdk_root.len > 0) { + if (build_context.metrics.arch == TargetArch_amd64) { + r.windows_sdk_um_library_path = mc_concat(r.windows_sdk_root, str_lit("um\\x64\\")); + r.windows_sdk_ucrt_library_path = mc_concat(r.windows_sdk_root, str_lit("ucrt\\x64\\")); + } else if (build_context.metrics.arch == TargetArch_i386) { + r.windows_sdk_um_library_path = mc_concat(r.windows_sdk_root, str_lit("um\\x86\\")); + r.windows_sdk_ucrt_library_path = mc_concat(r.windows_sdk_root, str_lit("ucrt\\x86\\")); + } + } - r.windows_sdk_root = mc_wstring_to_string(result.windows_sdk_root); - r.windows_sdk_um_library_path = mc_wstring_to_string(result.windows_sdk_um_library_path); - r.windows_sdk_ucrt_library_path = mc_wstring_to_string(result.windows_sdk_ucrt_library_path); - r.vs_exe_path = mc_wstring_to_string(result.vs_exe_path); - r.vs_library_path = mc_wstring_to_string(result.vs_library_path); + Find_Result result = {}; + bool ok = find_visual_studio_by_fighting_through_microsoft_craziness(&result); + + if (ok) { + r.vs_exe_path = mc_wstring_to_string(result.vs_exe_path); + r.vs_library_path = mc_wstring_to_string(result.vs_library_path); + } #if 0 - printf("windows_sdk_root: %.*s\n", LIT(r.windows_sdk_root)); - printf("windows_sdk_um_library_path: %.*s\n", LIT(r.windows_sdk_um_library_path)); - printf("windows_sdk_ucrt_library_path: %.*s\n", LIT(r.windows_sdk_ucrt_library_path)); - printf("vs_exe_path: %.*s\n", LIT(r.vs_exe_path)); - printf("vs_library_path: %.*s\n", LIT(r.vs_library_path)); + printf("windows_sdk_root: %.*s\n", LIT(r.windows_sdk_root)); + printf("windows_sdk_um_library_path: %.*s\n", LIT(r.windows_sdk_um_library_path)); + printf("windows_sdk_ucrt_library_path: %.*s\n", LIT(r.windows_sdk_ucrt_library_path)); + printf("vs_exe_path: %.*s\n", LIT(r.vs_exe_path)); + printf("vs_library_path: %.*s\n", LIT(r.vs_library_path)); - gb_exit(1); + gb_exit(1); #endif - return r; -} - + return r; +} \ No newline at end of file From 609ddf28b73817f4043aed27fb8056eb1eacffc0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 14:56:36 +0100 Subject: [PATCH 204/254] Add intrinsics `nontemporal_store` and `nontemporal_load` --- core/intrinsics/intrinsics.odin | 3 +++ src/check_builtin.cpp | 6 ++---- src/checker_builtin_procs.hpp | 4 ++++ src/llvm_backend_proc.cpp | 8 ++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index fa9b0ecec..8becd998d 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -12,6 +12,9 @@ soa_struct :: proc($N: int, $T: typeid) -> type/#soa[N]T volatile_load :: proc(dst: ^$T) -> T --- volatile_store :: proc(dst: ^$T, val: T) -> T --- +nontemporal_load :: proc(dst: ^$T) -> T --- +nontemporal_store :: proc(dst: ^$T, val: T) -> T --- + // Trapping debug_trap :: proc() --- trap :: proc() -> ! --- diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index e93e63d4d..ba34a177b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4025,9 +4025,8 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 break; case BuiltinProc_volatile_store: - /*fallthrough*/ case BuiltinProc_unaligned_store: - /*fallthrough*/ + case BuiltinProc_nontemporal_store: case BuiltinProc_atomic_store: { Type *elem = nullptr; @@ -4074,9 +4073,8 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case BuiltinProc_volatile_load: - /*fallthrough*/ case BuiltinProc_unaligned_load: - /*fallthrough*/ + case BuiltinProc_nontemporal_load: case BuiltinProc_atomic_load: { Type *elem = nullptr; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 3ef97b361..2dd775193 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -80,6 +80,8 @@ enum BuiltinProcId { BuiltinProc_unaligned_store, BuiltinProc_unaligned_load, + BuiltinProc_nontemporal_store, + BuiltinProc_nontemporal_load, BuiltinProc_prefetch_read_instruction, BuiltinProc_prefetch_read_data, @@ -367,6 +369,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("unaligned_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("unaligned_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("nontemporal_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("nontemporal_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("prefetch_read_instruction"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("prefetch_read_data"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 93481352b..2b7cad5cd 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2111,6 +2111,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return {}; case BuiltinProc_volatile_store: + case BuiltinProc_nontemporal_store: case BuiltinProc_atomic_store: case BuiltinProc_atomic_store_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); @@ -2120,6 +2121,9 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMValueRef instr = LLVMBuildStore(p->builder, val.value, dst.value); switch (id) { case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; + case BuiltinProc_nontemporal_store: + // TODO(bill): BuiltinProc_nontemporal_store + break; case BuiltinProc_atomic_store: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; case BuiltinProc_atomic_store_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[2])); break; } @@ -2130,6 +2134,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, } case BuiltinProc_volatile_load: + case BuiltinProc_nontemporal_load: case BuiltinProc_atomic_load: case BuiltinProc_atomic_load_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); @@ -2137,6 +2142,9 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMValueRef instr = LLVMBuildLoad(p->builder, dst.value, ""); switch (id) { case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; + case BuiltinProc_nontemporal_load: + // TODO(bill): BuiltinProc_nontemporal_load + break; case BuiltinProc_atomic_load: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; case BuiltinProc_atomic_load_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[1])); break; } From f383bf3136835d93af5e9110ab14c8529d9cfb82 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 14:59:09 +0100 Subject: [PATCH 205/254] Add `_mm_stream_ps` --- core/simd/x86/sse.odin | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index 1af5a65f1..b43ccb953 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -421,6 +421,10 @@ _MM_TRANSPOSE4_PS :: #force_inline proc "c" (row0, row1, row2, row3: ^__m128) { row3^ = _mm_movelh_ps(tmp3, tmp1) } +_mm_stream_ps :: proc(addr: [^]f32, a: __m128) { + intrinsics.nontemporal_store((^__m128)(addr), a) +} + @(default_calling_convention="c") @(private) From e079a7009ddcf8c2d662ebf9a4a76f5365c30f4e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 16:09:31 +0100 Subject: [PATCH 206/254] Begin work on sse2.odin --- core/simd/x86/sse2.odin | 274 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 core/simd/x86/sse2.odin diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin new file mode 100644 index 000000000..b50e33d28 --- /dev/null +++ b/core/simd/x86/sse2.odin @@ -0,0 +1,274 @@ +//+build i386, amd64 +package simd_x86 + +import "core:simd" + +_mm_pause :: #force_inline proc "c" () { + pause() +} +_mm_clflush :: #force_inline proc "c" (p: rawptr) { + clflush(p) +} +_mm_lfence :: #force_inline proc "c" () { + lfence() +} +_mm_mfence :: #force_inline proc "c" () { + mfence() +} + +_mm_add_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i8x16)a + y := transmute(simd.i8x16)b + return transmute(__m128i)simd.add(x, y) +} +_mm_add_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i16x8)a + y := transmute(simd.i16x8)b + return transmute(__m128i)simd.add(x, y) +} +_mm_add_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i32x4)a + y := transmute(simd.i32x4)b + return transmute(__m128i)simd.add(x, y) +} +_mm_add_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i64x2)a + y := transmute(simd.i64x2)b + return transmute(__m128i)simd.add(x, y) +} +_mm_adds_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i8x16)a + y := transmute(simd.i8x16)b + return transmute(__m128i)simd.add_sat(x, y) +} +_mm_adds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i16x8)a + y := transmute(simd.i16x8)b + return transmute(__m128i)simd.add_sat(x, y) +} +_mm_adds_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.u8x16)a + y := transmute(simd.u8x16)b + return transmute(__m128i)simd.add_sat(x, y) +} +_mm_adds_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.u16x8)a + y := transmute(simd.u16x8)b + return transmute(__m128i)simd.add_sat(x, y) +} +_mm_avg_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.u8x16)a + y := transmute(simd.u8x16)b + return transmute(__m128i)pavgb(x, y) +} +_mm_avg_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.u16x8)a + y := transmute(simd.u16x8)b + return transmute(__m128i)pavgw(x, y) +} + +_mm_madd_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i16x8)a + y := transmute(simd.i16x8)b + return transmute(__m128i)pmaddwd(x, y) +} +_mm_max_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i16x8)a + y := transmute(simd.i16x8)b + return transmute(__m128i)pmaxsw(x, y) +} +_mm_max_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.u8x16)a + y := transmute(simd.u8x16)b + return transmute(__m128i)pmaxub(x, y) +} +_mm_min_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.i16x8)a + y := transmute(simd.i16x8)b + return transmute(__m128i)pminsw(x, y) +} +_mm_min_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + x := transmute(simd.u8x16)a + y := transmute(simd.u8x16)b + return transmute(__m128i)pminub(x, y) +} + + +_mm_castpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { + return transmute(__m128)a +} +_mm_castpd_si128 :: #force_inline proc "c" (a: __m128d) -> __m128i { + return transmute(__m128i)a +} +_mm_castps_pd :: #force_inline proc "c" (a: __m128) -> __m128d { + return transmute(__m128d)a +} +_mm_castps_si128 :: #force_inline proc "c" (a: __m128) -> __m128i { + return transmute(__m128i)a +} +_mm_castsi128_pd :: #force_inline proc "c" (a: __m128i) -> __m128d { + return transmute(__m128d)a +} +_mm_castsi128_ps :: #force_inline proc "c" (a: __m128i) -> __m128 { + return transmute(__m128)a +} + + +_mm_undefined_pd :: #force_inline proc "c" () -> __m128d { + return __m128d{0, 0} +} +_mm_undefined_si128 :: #force_inline proc "c" () -> __m128i { + return __m128i{0, 0} +} +_mm_unpackhi_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.shuffle(a, b, 1, 3) +} +_mm_unpacklo_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.shuffle(a, b, 0, 2) +} + + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name="llvm.x86.sse2.pause") + pause :: proc() --- + @(link_name="llvm.x86.sse2.clflush") + clflush :: proc(p: rawptr) --- + @(link_name="llvm.x86.sse2.lfence") + lfence :: proc() --- + @(link_name="llvm.x86.sse2.mfence") + mfence :: proc() --- + @(link_name="llvm.x86.sse2.pavg.b") + pavgb :: proc(a, b: simd.u8x16) -> simd.u8x16 --- + @(link_name="llvm.x86.sse2.pavg.w") + pavgw :: proc(a, b: simd.u16x8) -> simd.u16x8 --- + @(link_name="llvm.x86.sse2.pmadd.wd") + pmaddwd :: proc(a, b: simd.i16x8) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.pmaxs.w") + pmaxsw :: proc(a, b: simd.i16x8) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.pmaxu.b") + pmaxub :: proc(a, b: simd.u8x16) -> simd.u8x16 --- + @(link_name="llvm.x86.sse2.pmins.w") + pminsw :: proc(a, b: simd.i16x8) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.pminu.b") + pminub :: proc(a, b: simd.u8x16) -> simd.u8x16 --- + @(link_name="llvm.x86.sse2.pmulh.w") + pmulhw :: proc(a, b: simd.i16x8) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.pmulhu.w") + pmulhuw :: proc(a, b: simd.u16x8) -> simd.u16x8 --- + @(link_name="llvm.x86.sse2.pmulu.dq") + pmuludq :: proc(a, b: simd.u32x4) -> simd.u64x2 --- + @(link_name="llvm.x86.sse2.psad.bw") + psadbw :: proc(a, b: simd.u8x16) -> simd.u64x2 --- + @(link_name="llvm.x86.sse2.pslli.w") + pslliw :: proc(a: simd.i16x8, #const imm8: u32) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.psll.w") + psllw :: proc(a: simd.i16x8, count: simd.i16x8) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.pslli.d") + psllid :: proc(a: simd.i32x4, #const imm8: u32) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.psll.d") + pslld :: proc(a: simd.i32x4, count: simd.i32x4) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.pslli.q") + pslliq :: proc(a: simd.i64x2, #const imm8: u32) -> simd.i64x2 --- + @(link_name="llvm.x86.sse2.psll.q") + psllq :: proc(a: simd.i64x2, count: simd.i64x2) -> simd.i64x2 --- + @(link_name="llvm.x86.sse2.psrai.w") + psraiw :: proc(a: simd.i16x8, #const imm8: u32) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.psra.w") + psraw :: proc(a: simd.i16x8, count: simd.i16x8) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.psrai.d") + psraid :: proc(a: simd.i32x4, #const imm8: u32) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.psra.d") + psrad :: proc(a: simd.i32x4, count: simd.i32x4) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.psrli.w") + psrliw :: proc(a: simd.i16x8, #const imm8: u32) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.psrl.w") + psrlw :: proc(a: simd.i16x8, count: simd.i16x8) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.psrli.d") + psrlid :: proc(a: simd.i32x4, #const imm8: u32) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.psrl.d") + psrld :: proc(a: simd.i32x4, count: simd.i32x4) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.psrli.q") + psrliq :: proc(a: simd.i64x2, #const imm8: u32) -> simd.i64x2 --- + @(link_name="llvm.x86.sse2.psrl.q") + psrlq :: proc(a: simd.i64x2, count: simd.i64x2) -> simd.i64x2 --- + @(link_name="llvm.x86.sse2.cvtdq2ps") + cvtdq2ps :: proc(a: simd.i32x4) -> __m128 --- + @(link_name="llvm.x86.sse2.cvtps2dq") + cvtps2dq :: proc(a: __m128) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.maskmov.dqu") + maskmovdqu :: proc(a: simd.i8x16, mask: simd.i8x16, mem_addr: rawptr) --- + @(link_name="llvm.x86.sse2.packsswb.128") + packsswb :: proc(a: simd.i16x8, b: simd.i16x8) -> simd.i8x16 --- + @(link_name="llvm.x86.sse2.packssdw.128") + packssdw :: proc(a: simd.i32x4, b: simd.i32x4) -> simd.i16x8 --- + @(link_name="llvm.x86.sse2.packuswb.128") + packuswb :: proc(a: simd.i16x8, b: simd.i16x8) -> simd.u8x16 --- + @(link_name="llvm.x86.sse2.pmovmskb.128") + pmovmskb :: proc(a: simd.i8x16) -> i32 --- + @(link_name="llvm.x86.sse2.max.sd") + maxsd :: proc(a: __m128d, b: __m128d) -> __m128d --- + @(link_name="llvm.x86.sse2.max.pd") + maxpd :: proc(a: __m128d, b: __m128d) -> __m128d --- + @(link_name="llvm.x86.sse2.min.sd") + minsd :: proc(a: __m128d, b: __m128d) -> __m128d --- + @(link_name="llvm.x86.sse2.min.pd") + minpd :: proc(a: __m128d, b: __m128d) -> __m128d --- + @(link_name="llvm.x86.sse2.sqrt.sd") + sqrtsd :: proc(a: __m128d) -> __m128d --- + @(link_name="llvm.x86.sse2.sqrt.pd") + sqrtpd :: proc(a: __m128d) -> __m128d --- + @(link_name="llvm.x86.sse2.cmp.sd") + cmpsd :: proc(a: __m128d, b: __m128d, imm8: i8) -> __m128d --- + @(link_name="llvm.x86.sse2.cmp.pd") + cmppd :: proc(a: __m128d, b: __m128d, imm8: i8) -> __m128d --- + @(link_name="llvm.x86.sse2.comieq.sd") + comieqsd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.comilt.sd") + comiltsd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.comile.sd") + comilesd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.comigt.sd") + comigtsd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.comige.sd") + comigesd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.comineq.sd") + comineqsd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.ucomieq.sd") + ucomieqsd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.ucomilt.sd") + ucomiltsd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.ucomile.sd") + ucomilesd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.ucomigt.sd") + ucomigtsd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.ucomige.sd") + ucomigesd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.ucomineq.sd") + ucomineqsd :: proc(a: __m128d, b: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.movmsk.pd") + movmskpd :: proc(a: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.cvtpd2ps") + cvtpd2ps :: proc(a: __m128d) -> __m128 --- + @(link_name="llvm.x86.sse2.cvtps2pd") + cvtps2pd :: proc(a: __m128) -> __m128d --- + @(link_name="llvm.x86.sse2.cvtpd2dq") + cvtpd2dq :: proc(a: __m128d) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.cvtsd2si") + cvtsd2si :: proc(a: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.cvtsd2ss") + cvtsd2ss :: proc(a: __m128, b: __m128d) -> __m128 --- + @(link_name="llvm.x86.sse2.cvtss2sd") + cvtss2sd :: proc(a: __m128d, b: __m128) -> __m128d --- + @(link_name="llvm.x86.sse2.cvttpd2dq") + cvttpd2dq :: proc(a: __m128d) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.cvttsd2si") + cvttsd2si :: proc(a: __m128d) -> i32 --- + @(link_name="llvm.x86.sse2.cvttps2dq") + cvttps2dq :: proc(a: __m128) -> simd.i32x4 --- + @(link_name="llvm.x86.sse2.storeu.dq") + storeudq :: proc(mem_addr: rawptr, a: __m128i) --- + @(link_name="llvm.x86.sse2.storeu.pd") + storeupd :: proc(mem_addr: rawptr, a: __m128d) --- +} From c48ef7d70bed18edc8feea63d6deceb10133aa12 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 16:39:54 +0100 Subject: [PATCH 207/254] Add shifts --- core/simd/x86/sse.odin | 20 +-- core/simd/x86/sse2.odin | 295 ++++++++++++++++++++++++++++----------- core/simd/x86/types.odin | 15 +- 3 files changed, 233 insertions(+), 97 deletions(-) diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index b43ccb953..50211872e 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -107,24 +107,16 @@ _mm_max_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { } _mm_and_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { - x := transmute(__m128i)a - y := transmute(__m128i)b - return transmute(__m128)simd.and(x, y) + return transmute(__m128)simd.and(transmute(__m128i)a, transmute(__m128i)b) } _mm_andnot_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { - x := transmute(__m128i)a - y := transmute(__m128i)b - return transmute(__m128)simd.and_not(x, y) + return transmute(__m128)simd.and_not(transmute(__m128i)a, transmute(__m128i)b) } _mm_or_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { - x := transmute(__m128i)a - y := transmute(__m128i)b - return transmute(__m128)simd.or(x, y) + return transmute(__m128)simd.or(transmute(__m128i)a, transmute(__m128i)b) } _mm_xor_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { - x := transmute(__m128i)a - y := transmute(__m128i)b - return transmute(__m128)simd.xor(x, y) + return transmute(__m128)simd.xor(transmute(__m128i)a, transmute(__m128i)b) } @@ -301,7 +293,7 @@ _mm_movelh_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 0, 1, 4, 5) } -_mm_movemask_ps :: proc(a: __m128) -> u32 { +_mm_movemask_ps :: #force_inline proc "c" (a: __m128) -> u32 { return movmskps(a) } @@ -421,7 +413,7 @@ _MM_TRANSPOSE4_PS :: #force_inline proc "c" (row0, row1, row2, row3: ^__m128) { row3^ = _mm_movelh_ps(tmp3, tmp1) } -_mm_stream_ps :: proc(addr: [^]f32, a: __m128) { +_mm_stream_ps :: #force_inline proc "c" (addr: [^]f32, a: __m128) { intrinsics.nontemporal_store((^__m128)(addr), a) } diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index b50e33d28..dd616c219 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -17,83 +17,214 @@ _mm_mfence :: #force_inline proc "c" () { } _mm_add_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i8x16)a - y := transmute(simd.i8x16)b - return transmute(__m128i)simd.add(x, y) + return transmute(__m128i)simd.add(transmute(i8x16)a, transmute(i8x16)b) } _mm_add_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i16x8)a - y := transmute(simd.i16x8)b - return transmute(__m128i)simd.add(x, y) + return transmute(__m128i)simd.add(transmute(i16x8)a, transmute(i16x8)b) } _mm_add_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i32x4)a - y := transmute(simd.i32x4)b - return transmute(__m128i)simd.add(x, y) + return transmute(__m128i)simd.add(transmute(i32x4)a, transmute(i32x4)b) } _mm_add_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i64x2)a - y := transmute(simd.i64x2)b - return transmute(__m128i)simd.add(x, y) + return transmute(__m128i)simd.add(transmute(i64x2)a, transmute(i64x2)b) } _mm_adds_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i8x16)a - y := transmute(simd.i8x16)b - return transmute(__m128i)simd.add_sat(x, y) + return transmute(__m128i)simd.add_sat(transmute(i8x16)a, transmute(i8x16)b) } _mm_adds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i16x8)a - y := transmute(simd.i16x8)b - return transmute(__m128i)simd.add_sat(x, y) + return transmute(__m128i)simd.add_sat(transmute(i16x8)a, transmute(i16x8)b) } _mm_adds_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.u8x16)a - y := transmute(simd.u8x16)b - return transmute(__m128i)simd.add_sat(x, y) + return transmute(__m128i)simd.add_sat(transmute(u8x16)a, transmute(u8x16)b) } _mm_adds_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.u16x8)a - y := transmute(simd.u16x8)b - return transmute(__m128i)simd.add_sat(x, y) + return transmute(__m128i)simd.add_sat(transmute(u16x8)a, transmute(u16x8)b) } _mm_avg_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.u8x16)a - y := transmute(simd.u8x16)b - return transmute(__m128i)pavgb(x, y) + return transmute(__m128i)pavgb(transmute(u8x16)a, transmute(u8x16)b) } _mm_avg_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.u16x8)a - y := transmute(simd.u16x8)b - return transmute(__m128i)pavgw(x, y) + return transmute(__m128i)pavgw(transmute(u16x8)a, transmute(u16x8)b) } _mm_madd_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i16x8)a - y := transmute(simd.i16x8)b - return transmute(__m128i)pmaddwd(x, y) + return transmute(__m128i)pmaddwd(transmute(i16x8)a, transmute(i16x8)b) } _mm_max_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i16x8)a - y := transmute(simd.i16x8)b - return transmute(__m128i)pmaxsw(x, y) + return transmute(__m128i)pmaxsw(transmute(i16x8)a, transmute(i16x8)b) } _mm_max_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.u8x16)a - y := transmute(simd.u8x16)b - return transmute(__m128i)pmaxub(x, y) + return transmute(__m128i)pmaxub(transmute(u8x16)a, transmute(u8x16)b) } _mm_min_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.i16x8)a - y := transmute(simd.i16x8)b - return transmute(__m128i)pminsw(x, y) + return transmute(__m128i)pminsw(transmute(i16x8)a, transmute(i16x8)b) } _mm_min_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { - x := transmute(simd.u8x16)a - y := transmute(simd.u8x16)b - return transmute(__m128i)pminub(x, y) + return transmute(__m128i)pminub(transmute(u8x16)a, transmute(u8x16)b) } +_mm_mulhi_epi16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)pmulhw(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_mulhi_epu16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)pmulhuw(transmute(u16x8)a, transmute(u16x8)b) +} +_mm_mullo_epi16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.mul(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_mul_epu32 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)pmuludq(transmute(u32x4)a, transmute(u32x4)b) +} +_mm_sad_epu8 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)psadbw(transmute(u8x16)a, transmute(u8x16)b) +} +_mm_sub_epi8 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.sub(transmute(i8x16)a, transmute(i8x16)b) +} +_mm_sub_epi16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.sub(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_sub_epi32 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.sub(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_sub_epi64 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.sub(transmute(i64x2)a, transmute(i64x2)b) +} +_mm_subs_epi8 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.sub_sat(transmute(i8x16)a, transmute(i8x16)b) +} +_mm_subs_epi16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.sub_sat(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_subs_epu8 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.sub_sat(transmute(u8x16)a, transmute(u8x16)b) +} +_mm_subs_epu16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.sub_sat(transmute(u16x8)a, transmute(u16x8)b) +} + + + +@(private) +_mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + shift :: IMM8 & 0xff + + return transmute(__m128i)simd.shuffle( + transmute(i8x16)a, + i8x16(0), + 0 when shift > 15 else (16 - shift + 0), + 1 when shift > 15 else (16 - shift + 1), + 2 when shift > 15 else (16 - shift + 2), + 3 when shift > 15 else (16 - shift + 3), + 4 when shift > 15 else (16 - shift + 4), + 5 when shift > 15 else (16 - shift + 5), + 6 when shift > 15 else (16 - shift + 6), + 7 when shift > 15 else (16 - shift + 7), + 8 when shift > 15 else (16 - shift + 8), + 9 when shift > 15 else (16 - shift + 9), + 10 when shift > 15 else (16 - shift + 10), + 11 when shift > 15 else (16 - shift + 11), + 12 when shift > 15 else (16 - shift + 12), + 13 when shift > 15 else (16 - shift + 13), + 14 when shift > 15 else (16 - shift + 14), + 15 when shift > 15 else (16 - shift + 15), + ) +} + +@(private) +_mm_srli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + shift :: IMM8 + return transmute(__m128i)simd.shuffle( + transmute(i8x16)a, + i8x16(0), + 0 + 16 when shift > 15 else (shift + 0), + 1 + 16 when shift > 15 else (shift + 1), + 2 + 16 when shift > 15 else (shift + 2), + 3 + 16 when shift > 15 else (shift + 3), + 4 + 16 when shift > 15 else (shift + 4), + 5 + 16 when shift > 15 else (shift + 5), + 6 + 16 when shift > 15 else (shift + 6), + 7 + 16 when shift > 15 else (shift + 7), + 8 + 16 when shift > 15 else (shift + 8), + 9 + 16 when shift > 15 else (shift + 9), + 10 + 16 when shift > 15 else (shift + 10), + 11 + 16 when shift > 15 else (shift + 11), + 12 + 16 when shift > 15 else (shift + 12), + 13 + 16 when shift > 15 else (shift + 13), + 14 + 16 when shift > 15 else (shift + 14), + 15 + 16 when shift > 15 else (shift + 15), + ) +} + + +_mm_slli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return _mm_slli_si128_impl(a, IMM8) +} +_mm_bslli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return _mm_slli_si128_impl(a, IMM8) +} + + + +_mm_bsrli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return _mm_srli_si128_impl(a, IMM8) +} +_mm_slli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return transmute(__m128i)pslliw(transmute(i16x8)a, IMM8) +} +_mm_sll_epi16 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { + return transmute(__m128i)psllw(transmute(i16x8)a, transmute(i16x8)count) +} +_mm_slli_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return transmute(__m128i)psllid(transmute(i32x4)a, IMM8) +} +_mm_sll_epi32 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { + return transmute(__m128i)pslld(transmute(i32x4)a, transmute(i32x4)count) +} +_mm_slli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return transmute(__m128i)pslliq(transmute(i64x2)a, IMM8) +} +_mm_sll_epi64 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { + return transmute(__m128i)psllq(transmute(i64x2)a, transmute(i64x2)count) +} +_mm_srai_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return transmute(__m128i)psraiw(transmute(i16x8)a. IMM8) +} +_mm_sra_epi16 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { + return transmute(__m128i)psraw(transmute(i16x8)a, transmute(i16x8)count) +} +_mm_srai_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return transmute(__m128i)psraid(transmute(i32x4)a, IMM8) +} +_mm_sra_epi32 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { + return transmute(__m128i)psrad(transmute(i32x4)a, transmute(i32x4)count) +} + +_mm_srli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return _mm_srli_si128_impl(a, IMM8) +} +_mm_srli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return transmute(__m128i)psrliw(transmute(i16x8)a. IMM8) +} +_mm_srl_epi16 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { + return transmute(__m128i)psrlw(transmute(i16x8)a, transmute(i16x8)count) +} +_mm_srli_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return transmute(__m128i)psrlid(transmute(i32x4)a, IMM8) +} +_mm_srl_epi32 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { + return transmute(__m128i)psrld(transmute(i32x4)a, transmute(i32x4)count) +} +_mm_srli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + return transmute(__m128i)psrliq(transmute(i64x2)a, IMM8) +} +_mm_srl_epi64 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { + return transmute(__m128i)psrlq(transmute(i64x2)a, transmute(i64x2)count) +} + + + _mm_castpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { return transmute(__m128)a } @@ -140,73 +271,73 @@ foreign _ { @(link_name="llvm.x86.sse2.mfence") mfence :: proc() --- @(link_name="llvm.x86.sse2.pavg.b") - pavgb :: proc(a, b: simd.u8x16) -> simd.u8x16 --- + pavgb :: proc(a, b: u8x16) -> u8x16 --- @(link_name="llvm.x86.sse2.pavg.w") - pavgw :: proc(a, b: simd.u16x8) -> simd.u16x8 --- + pavgw :: proc(a, b: u16x8) -> u16x8 --- @(link_name="llvm.x86.sse2.pmadd.wd") - pmaddwd :: proc(a, b: simd.i16x8) -> simd.i32x4 --- + pmaddwd :: proc(a, b: i16x8) -> i32x4 --- @(link_name="llvm.x86.sse2.pmaxs.w") - pmaxsw :: proc(a, b: simd.i16x8) -> simd.i16x8 --- + pmaxsw :: proc(a, b: i16x8) -> i16x8 --- @(link_name="llvm.x86.sse2.pmaxu.b") - pmaxub :: proc(a, b: simd.u8x16) -> simd.u8x16 --- + pmaxub :: proc(a, b: u8x16) -> u8x16 --- @(link_name="llvm.x86.sse2.pmins.w") - pminsw :: proc(a, b: simd.i16x8) -> simd.i16x8 --- + pminsw :: proc(a, b: i16x8) -> i16x8 --- @(link_name="llvm.x86.sse2.pminu.b") - pminub :: proc(a, b: simd.u8x16) -> simd.u8x16 --- + pminub :: proc(a, b: u8x16) -> u8x16 --- @(link_name="llvm.x86.sse2.pmulh.w") - pmulhw :: proc(a, b: simd.i16x8) -> simd.i16x8 --- + pmulhw :: proc(a, b: i16x8) -> i16x8 --- @(link_name="llvm.x86.sse2.pmulhu.w") - pmulhuw :: proc(a, b: simd.u16x8) -> simd.u16x8 --- + pmulhuw :: proc(a, b: u16x8) -> u16x8 --- @(link_name="llvm.x86.sse2.pmulu.dq") - pmuludq :: proc(a, b: simd.u32x4) -> simd.u64x2 --- + pmuludq :: proc(a, b: u32x4) -> u64x2 --- @(link_name="llvm.x86.sse2.psad.bw") - psadbw :: proc(a, b: simd.u8x16) -> simd.u64x2 --- + psadbw :: proc(a, b: u8x16) -> u64x2 --- @(link_name="llvm.x86.sse2.pslli.w") - pslliw :: proc(a: simd.i16x8, #const imm8: u32) -> simd.i16x8 --- + pslliw :: proc(a: i16x8, #const imm8: u32) -> i16x8 --- @(link_name="llvm.x86.sse2.psll.w") - psllw :: proc(a: simd.i16x8, count: simd.i16x8) -> simd.i16x8 --- + psllw :: proc(a: i16x8, count: i16x8) -> i16x8 --- @(link_name="llvm.x86.sse2.pslli.d") - psllid :: proc(a: simd.i32x4, #const imm8: u32) -> simd.i32x4 --- + psllid :: proc(a: i32x4, #const imm8: u32) -> i32x4 --- @(link_name="llvm.x86.sse2.psll.d") - pslld :: proc(a: simd.i32x4, count: simd.i32x4) -> simd.i32x4 --- + pslld :: proc(a: i32x4, count: i32x4) -> i32x4 --- @(link_name="llvm.x86.sse2.pslli.q") - pslliq :: proc(a: simd.i64x2, #const imm8: u32) -> simd.i64x2 --- + pslliq :: proc(a: i64x2, #const imm8: u32) -> i64x2 --- @(link_name="llvm.x86.sse2.psll.q") - psllq :: proc(a: simd.i64x2, count: simd.i64x2) -> simd.i64x2 --- + psllq :: proc(a: i64x2, count: i64x2) -> i64x2 --- @(link_name="llvm.x86.sse2.psrai.w") - psraiw :: proc(a: simd.i16x8, #const imm8: u32) -> simd.i16x8 --- + psraiw :: proc(a: i16x8, #const imm8: u32) -> i16x8 --- @(link_name="llvm.x86.sse2.psra.w") - psraw :: proc(a: simd.i16x8, count: simd.i16x8) -> simd.i16x8 --- + psraw :: proc(a: i16x8, count: i16x8) -> i16x8 --- @(link_name="llvm.x86.sse2.psrai.d") - psraid :: proc(a: simd.i32x4, #const imm8: u32) -> simd.i32x4 --- + psraid :: proc(a: i32x4, #const imm8: u32) -> i32x4 --- @(link_name="llvm.x86.sse2.psra.d") - psrad :: proc(a: simd.i32x4, count: simd.i32x4) -> simd.i32x4 --- + psrad :: proc(a: i32x4, count: i32x4) -> i32x4 --- @(link_name="llvm.x86.sse2.psrli.w") - psrliw :: proc(a: simd.i16x8, #const imm8: u32) -> simd.i16x8 --- + psrliw :: proc(a: i16x8, #const imm8: u32) -> i16x8 --- @(link_name="llvm.x86.sse2.psrl.w") - psrlw :: proc(a: simd.i16x8, count: simd.i16x8) -> simd.i16x8 --- + psrlw :: proc(a: i16x8, count: i16x8) -> i16x8 --- @(link_name="llvm.x86.sse2.psrli.d") - psrlid :: proc(a: simd.i32x4, #const imm8: u32) -> simd.i32x4 --- + psrlid :: proc(a: i32x4, #const imm8: u32) -> i32x4 --- @(link_name="llvm.x86.sse2.psrl.d") - psrld :: proc(a: simd.i32x4, count: simd.i32x4) -> simd.i32x4 --- + psrld :: proc(a: i32x4, count: i32x4) -> i32x4 --- @(link_name="llvm.x86.sse2.psrli.q") - psrliq :: proc(a: simd.i64x2, #const imm8: u32) -> simd.i64x2 --- + psrliq :: proc(a: i64x2, #const imm8: u32) -> i64x2 --- @(link_name="llvm.x86.sse2.psrl.q") - psrlq :: proc(a: simd.i64x2, count: simd.i64x2) -> simd.i64x2 --- + psrlq :: proc(a: i64x2, count: i64x2) -> i64x2 --- @(link_name="llvm.x86.sse2.cvtdq2ps") - cvtdq2ps :: proc(a: simd.i32x4) -> __m128 --- + cvtdq2ps :: proc(a: i32x4) -> __m128 --- @(link_name="llvm.x86.sse2.cvtps2dq") - cvtps2dq :: proc(a: __m128) -> simd.i32x4 --- + cvtps2dq :: proc(a: __m128) -> i32x4 --- @(link_name="llvm.x86.sse2.maskmov.dqu") - maskmovdqu :: proc(a: simd.i8x16, mask: simd.i8x16, mem_addr: rawptr) --- + maskmovdqu :: proc(a: i8x16, mask: i8x16, mem_addr: rawptr) --- @(link_name="llvm.x86.sse2.packsswb.128") - packsswb :: proc(a: simd.i16x8, b: simd.i16x8) -> simd.i8x16 --- + packsswb :: proc(a: i16x8, b: i16x8) -> i8x16 --- @(link_name="llvm.x86.sse2.packssdw.128") - packssdw :: proc(a: simd.i32x4, b: simd.i32x4) -> simd.i16x8 --- + packssdw :: proc(a: i32x4, b: i32x4) -> i16x8 --- @(link_name="llvm.x86.sse2.packuswb.128") - packuswb :: proc(a: simd.i16x8, b: simd.i16x8) -> simd.u8x16 --- + packuswb :: proc(a: i16x8, b: i16x8) -> u8x16 --- @(link_name="llvm.x86.sse2.pmovmskb.128") - pmovmskb :: proc(a: simd.i8x16) -> i32 --- + pmovmskb :: proc(a: i8x16) -> i32 --- @(link_name="llvm.x86.sse2.max.sd") maxsd :: proc(a: __m128d, b: __m128d) -> __m128d --- @(link_name="llvm.x86.sse2.max.pd") @@ -254,7 +385,7 @@ foreign _ { @(link_name="llvm.x86.sse2.cvtps2pd") cvtps2pd :: proc(a: __m128) -> __m128d --- @(link_name="llvm.x86.sse2.cvtpd2dq") - cvtpd2dq :: proc(a: __m128d) -> simd.i32x4 --- + cvtpd2dq :: proc(a: __m128d) -> i32x4 --- @(link_name="llvm.x86.sse2.cvtsd2si") cvtsd2si :: proc(a: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.cvtsd2ss") @@ -262,11 +393,11 @@ foreign _ { @(link_name="llvm.x86.sse2.cvtss2sd") cvtss2sd :: proc(a: __m128d, b: __m128) -> __m128d --- @(link_name="llvm.x86.sse2.cvttpd2dq") - cvttpd2dq :: proc(a: __m128d) -> simd.i32x4 --- + cvttpd2dq :: proc(a: __m128d) -> i32x4 --- @(link_name="llvm.x86.sse2.cvttsd2si") cvttsd2si :: proc(a: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.cvttps2dq") - cvttps2dq :: proc(a: __m128) -> simd.i32x4 --- + cvttps2dq :: proc(a: __m128) -> i32x4 --- @(link_name="llvm.x86.sse2.storeu.dq") storeudq :: proc(mem_addr: rawptr, a: __m128i) --- @(link_name="llvm.x86.sse2.storeu.pd") diff --git a/core/simd/x86/types.odin b/core/simd/x86/types.odin index 3c94d74a0..06a2cd41e 100644 --- a/core/simd/x86/types.odin +++ b/core/simd/x86/types.odin @@ -1,6 +1,8 @@ //+build i386, amd64 package simd_x86 +import "core:simd" + bf16 :: u16 __m128i :: #simd[2]i64 @@ -41,4 +43,15 @@ _MM_MANTISSA_NORM_ENUM :: i32 /// The `MM_MANTISSA_SIGN_ENUM` type used to specify mantissa signed operations in AVX-512 intrinsics. _MM_MANTISSA_SIGN_ENUM :: i32 -_MM_PERM_ENUM :: i32 \ No newline at end of file +_MM_PERM_ENUM :: i32 + +@(private) u8x16 :: simd.u8x16 +@(private) i8x16 :: simd.i8x16 +@(private) u16x8 :: simd.u16x8 +@(private) i16x8 :: simd.i16x8 +@(private) u32x4 :: simd.u32x4 +@(private) i32x4 :: simd.i32x4 +@(private) u64x2 :: simd.u64x2 +@(private) i64x2 :: simd.i64x2 +@(private) f32x4 :: simd.f32x4 +@(private) f64x2 :: simd.f64x2 From 4e30a64d9fbc8b8eede7a0d857bfa26df5857fe2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 16:49:52 +0100 Subject: [PATCH 208/254] Add more sse2 intrinsics --- core/simd/x86/sse2.odin | 109 ++++++++++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 22 deletions(-) diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index dd616c219..970704bb7 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -64,43 +64,43 @@ _mm_min_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { } -_mm_mulhi_epi16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_mulhi_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmulhw(transmute(i16x8)a, transmute(i16x8)b) } -_mm_mulhi_epu16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_mulhi_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmulhuw(transmute(u16x8)a, transmute(u16x8)b) } -_mm_mullo_epi16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_mullo_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.mul(transmute(i16x8)a, transmute(i16x8)b) } -_mm_mul_epu32 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_mul_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmuludq(transmute(u32x4)a, transmute(u32x4)b) } -_mm_sad_epu8 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_sad_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psadbw(transmute(u8x16)a, transmute(u8x16)b) } -_mm_sub_epi8 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_sub_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i8x16)a, transmute(i8x16)b) } -_mm_sub_epi16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_sub_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i16x8)a, transmute(i16x8)b) } -_mm_sub_epi32 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_sub_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i32x4)a, transmute(i32x4)b) } -_mm_sub_epi64 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_sub_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i64x2)a, transmute(i64x2)b) } -_mm_subs_epi8 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_subs_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(i8x16)a, transmute(i8x16)b) } -_mm_subs_epi16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_subs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(i16x8)a, transmute(i16x8)b) } -_mm_subs_epu8 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_subs_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(u8x16)a, transmute(u8x16)b) } -_mm_subs_epu16 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { +_mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(u16x8)a, transmute(u16x8)b) } @@ -166,64 +166,129 @@ _mm_bslli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { } - _mm_bsrli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_srli_si128_impl(a, IMM8) } _mm_slli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)pslliw(transmute(i16x8)a, IMM8) } -_mm_sll_epi16 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { +_mm_sll_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psllw(transmute(i16x8)a, transmute(i16x8)count) } _mm_slli_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psllid(transmute(i32x4)a, IMM8) } -_mm_sll_epi32 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { +_mm_sll_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)pslld(transmute(i32x4)a, transmute(i32x4)count) } _mm_slli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)pslliq(transmute(i64x2)a, IMM8) } -_mm_sll_epi64 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { +_mm_sll_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psllq(transmute(i64x2)a, transmute(i64x2)count) } _mm_srai_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psraiw(transmute(i16x8)a. IMM8) } -_mm_sra_epi16 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { +_mm_sra_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psraw(transmute(i16x8)a, transmute(i16x8)count) } _mm_srai_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psraid(transmute(i32x4)a, IMM8) } -_mm_sra_epi32 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { +_mm_sra_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrad(transmute(i32x4)a, transmute(i32x4)count) } + _mm_srli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_srli_si128_impl(a, IMM8) } _mm_srli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrliw(transmute(i16x8)a. IMM8) } -_mm_srl_epi16 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { +_mm_srl_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrlw(transmute(i16x8)a, transmute(i16x8)count) } _mm_srli_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrlid(transmute(i32x4)a, IMM8) } -_mm_srl_epi32 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { +_mm_srl_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrld(transmute(i32x4)a, transmute(i32x4)count) } _mm_srli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrliq(transmute(i64x2)a, IMM8) } -_mm_srl_epi64 :: #force_inline proc "c" (a: __m128i, count: __m128i) -> __m128i { +_mm_srl_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrlq(transmute(i64x2)a, transmute(i64x2)count) } +_mm_and_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return simd.and(a, b) +} +_mm_andnot_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return simd.and_not(b, a) +} +_mm_or_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return simd.or(a, b) +} +_mm_xor_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return simd.xor(a, b) +} +_mm_cmpeq_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_eq(transmute(i8x16)a, transmute(i8x16)b) +} +_mm_cmpeq_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_eq(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_cmpeq_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_eq(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_cmpgt_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_gt(transmute(i8x16)a, transmute(i8x16)b) +} +_mm_cmpgt_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_gt(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_cmpgt_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_gt(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_cmplt_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_lt(transmute(i8x16)a, transmute(i8x16)b) +} +_mm_cmplt_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_lt(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_cmplt_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_lt(transmute(i32x4)a, transmute(i32x4)b) +} + + +_mm_cvtepi32_pd :: #force_inline proc "c" (a: __m128i) -> __m128d { + v := transmute(i32x4)a + return cast(__m128d)simd.shuffle(v, v, 0, 1) +} +_mm_cvtsi32_sd :: #force_inline proc "c" (a: __m128d, b: i32) -> __m128d { + return simd.replace(a, 0, f64(b)) +} +_mm_cvtepi32_ps :: #force_inline proc "c" (a: __m128i) -> __m128 { + return cvtdq2ps(transmute(i32x4)a) +} +_mm_cvtps_epi32 :: #force_inline proc "c" (a: __m128) -> __m128i { + return transmute(__m128i)cvtps2dq(a) +} +_mm_cvtsi32_si128 :: #force_inline proc "c" (a: i32) -> __m128i { + return transmute(__m128i)i32x4{a, 0, 0, 0} +} +_mm_cvtsi128_si32 :: #force_inline proc "c" (a: __m128i) -> i32 { + return simd.extract(transmute(i32x4)a, 0) +} + + + + + _mm_castpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { return transmute(__m128)a From 20fe6d102a8469eac180bc0051c5359acad07ac1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 16:58:35 +0100 Subject: [PATCH 209/254] Add load and stores and sets --- core/simd/x86/sse2.odin | 75 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index 970704bb7..bba842820 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -1,6 +1,7 @@ //+build i386, amd64 package simd_x86 +import "core:intrinsics" import "core:simd" _mm_pause :: #force_inline proc "c" () { @@ -287,6 +288,80 @@ _mm_cvtsi128_si32 :: #force_inline proc "c" (a: __m128i) -> i32 { +_mm_set_epi64x :: #force_inline proc "c" (e1, e0: i64) -> __m128i { + return transmute(__m128i)i64x2{e0, e1} +} +_mm_set_epi32 :: #force_inline proc "c" (e3, e2, e1, e0: i32) -> __m128i { + return transmute(__m128i)i32x4{e0, e1, e2, e3} +} +_mm_set_epi16 :: #force_inline proc "c" (e7, e6, e5, e4, e3, e2, e1, e0: i16) -> __m128i { + return transmute(__m128i)i16x8{e0, e1, e2, e3, e4, e5, e6, e7} +} +_mm_set_epi8 :: #force_inline proc "c" (e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0: i8) -> __m128i { + return transmute(__m128i)i8x16{e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15} +} +_mm_set1_epi64x :: #force_inline proc "c" (a: i64) -> __m128i { + return _mm_set_epi64x(a, a) +} +_mm_set1_epi32 :: #force_inline proc "c" (a: i32) -> __m128i { + return _mm_set_epi32(a, a, a, a) +} +_mm_set1_epi16 :: #force_inline proc "c" (a: i16) -> __m128i { + return _mm_set_epi16(a, a, a, a, a, a, a, a) +} +_mm_set1_epi8 :: #force_inline proc "c" (a: i8) -> __m128i { + return _mm_set_epi8(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a) +} +_mm_setr_epi32 :: #force_inline proc "c" (e3, e2, e1, e0: i32) -> __m128i { + return _mm_set_epi32(e0, e1, e2, e3) +} +_mm_setr_epi16 :: #force_inline proc "c" (e7, e6, e5, e4, e3, e2, e1, e0: i16) -> __m128i { + return _mm_set_epi16(e0, e1, e2, e3, e4, e5, e6, e7) +} +_mm_setr_epi8 :: #force_inline proc "c" (e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0: i8) -> __m128i { + return _mm_set_epi8(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15) +} +_mm_setzero_si128 :: #force_inline proc "c" () -> __m128i { + return _mm_set1_epi64x(0) +} + + +_mm_loadl_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { + return _mm_set_epi64x(0, intrinsics.unaligned_load((^i64)(mem_addr))) +} +_mm_load_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { + return mem_addr^ +} +_mm_loadu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { + dst := _mm_undefined_si128() + intrinsics.mem_copy_non_overlapping(&dst, mem_addr, size_of(__m128i)) + return dst +} +_mm_maskmoveu_si128 :: #force_inline proc "c" (a, mask: __m128i, mem_addr: rawptr) { + maskmovdqu(transmute(i8x16)a, transmute(i8x16)mask, mem_addr) +} +_mm_store_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { + mem_addr^ = a +} +_mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { + storeudq(mem_addr, a) +} +_mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { + a := a + intrinsics.mem_copy_non_overlapping(mem_addr, &a, 8) +} +_mm_stream_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { + intrinsics.nontemporal_store(mem_addr, a) +} +_mm_stream_si32 :: #force_inline proc "c" (mem_addr: ^i32, a: i32) { + intrinsics.nontemporal_store(mem_addr, a) +} +_mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { + zero := _mm_setzero_si128() + return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)zero, 0, 2) +} + + From 20c5033b38e108b1e127cc6b8be61cce043cba9e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 17:07:48 +0100 Subject: [PATCH 210/254] Add pack and unpack --- core/simd/x86/sse2.odin | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index bba842820..f52981639 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -364,6 +364,101 @@ _mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { +_mm_packs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)packsswb(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_packs_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)packssdw(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_packus_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)packuswb(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_extract_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> i32 { + return i32(simd.extract(transmute(u16x8)a, IMM8)) +} +_mm_insert_epi16 :: #force_inline proc "c" (a: __m128i, i: i32, $IMM8: u32) -> __m128i { + return i32(simd.replace(transmute(u16x8)a, IMM8, i16(i))) +} +_mm_movemask_epi8 :: #force_inline proc "c" (a: __m128i) -> i32 { + return pmovmskb(transmute(i8x16)a) +} +_mm_shuffle_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + v := transmute(i32x4)a + return transmute(__m128i)simd.shuffle( + v, + v, + IMM8 & 0b11, + (IMM8 >> 2) & 0b11, + (IMM8 >> 4) & 0b11, + (IMM8 >> 6) & 0b11, + ) +} +_mm_shufflehi_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + v := transmute(i16x8)a + return transmute(__m128i)simd.shuffle( + v, + v, + 0, + 1, + 2, + 3, + (IMM8 & 0b11) + 4, + ((IMM8 >> 2) & 0b11) + 4, + ((IMM8 >> 4) & 0b11) + 4, + ((IMM8 >> 6) & 0b11) + 4, + ) +} +_mm_shufflelo_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { + v := transmute(i16x8)a + return transmute(__m128i)simd.shuffle( + v, + v, + IMM8 & 0b11, + (IMM8 >> 2) & 0b11, + (IMM8 >> 4) & 0b11, + (IMM8 >> 6) & 0b11, + 4, + 5, + 6, + 7, + ) +} +_mm_unpackhi_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.shuffle( + transmute(i8x16)a, + transmute(i8x16)b, + 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31, + ) +} +_mm_unpackhi_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.shuffle(transmute(i16x8)a, transmute(i16x8)b, 4, 12, 5, 13, 6, 14, 7, 15) +} +_mm_unpackhi_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.shuffle(transmute(i32x4)a, transmute(i32x4)b, 2, 6, 3, 7) +} +_mm_unpackhi_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 1, 3) +} +_mm_unpacklo_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.shuffle( + transmute(i8x16)a, + transmute(i8x16)b, + 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23, + ) +} +_mm_unpacklo_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.shuffle(transmute(i16x8)a, transmute(i16x8)b, 0, 8, 1, 9, 2, 10, 3, 11) +} +_mm_unpacklo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.shuffle(transmute(i32x4)a, transmute(i32x4)b, 0, 4, 1, 5) +} +_mm_unpacklo_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 0, 2) +} + + + + _mm_castpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { return transmute(__m128)a From 2f7bd154a251968dd90bed749b6510d69a1e6afd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 27 May 2022 20:59:46 +0200 Subject: [PATCH 211/254] Additional cleanup of microsoft_craziness.h. --- src/build_settings.cpp | 10 +++ src/gb/gb.h | 2 +- src/microsoft_craziness.h | 147 +++++++++++--------------------------- 3 files changed, 54 insertions(+), 105 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 9c996aef3..e9f5f2099 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1234,6 +1234,16 @@ bool init_build_paths(String init_filename) { return false; } + if (!build_context.use_lld && find_result.vs_exe_path.len == 0) { + gb_printf_err("link.exe not found.\n"); + return false; + } + + if (find_result.vs_library_path.len == 0) { + gb_printf_err("VS library path not found.\n"); + return false; + } + if (find_result.windows_sdk_um_library_path.len > 0) { GB_ASSERT(find_result.windows_sdk_ucrt_library_path.len > 0); diff --git a/src/gb/gb.h b/src/gb/gb.h index 3b2d6434c..48d3c9aec 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -1680,7 +1680,7 @@ GB_DEF gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, c GB_DEF void gb_file_free_contents(gbFileContents *fc); -// TODO(bill): Should these have different na,es as they do not take in a gbFile * ??? +// TODO(bill): Should these have different names as they do not take in a gbFile * ??? GB_DEF b32 gb_file_exists (char const *filepath); GB_DEF gbFileTime gb_file_last_write_time(char const *filepath); GB_DEF b32 gb_file_copy (char const *existing_filename, char const *new_filename, b32 fail_if_exists); diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index c2efb6b84..98707a4a1 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -181,7 +181,6 @@ void mc_find_close(HANDLE handle) { // COM objects for the ridiculous Microsoft craziness. - typedef WCHAR* BSTR; typedef const WCHAR* LPCOLESTR; @@ -215,44 +214,6 @@ struct DECLSPEC_UUID("42843719-DB4C-46C2-8E7C-64F1816EFD5B") DECLSPEC_NOVTABLE I // The beginning of the actual code that does things. - -struct Version_Data { - i32 best_version[4]; // For Windows 8 versions, only two of these numbers are used. - wchar_t const *best_name; -}; - -bool os_file_exists(wchar_t const *name) { - // @Robustness: What flags do we really want to check here? - - auto attrib = GetFileAttributesW(name); - if (attrib == INVALID_FILE_ATTRIBUTES) return false; - if (attrib & FILE_ATTRIBUTE_DIRECTORY) return false; - - return true; -} - -wchar_t *concat(wchar_t const *a, wchar_t const *b, wchar_t const *c = nullptr, wchar_t const *d = nullptr) { - // Concatenate up to 4 wide strings together. Allocated with malloc. - // If you don't like that, use a programming language that actually - // helps you with using custom allocators. Or just edit the code. - - isize len_a = string16_len(a); - isize len_b = string16_len(b); - isize len_c = string16_len(c); - isize len_d = string16_len(d); - - wchar_t *result = (wchar_t *)calloc(2, (len_a + len_b + len_c + len_d + 1)); - gb_memmove(result, a, len_a*2); - gb_memmove(result + len_a, b, len_b*2); - - if (c) gb_memmove(result + len_a + len_b, c, len_c * 2); - if (d) gb_memmove(result + len_a + len_b + len_c, d, len_d * 2); - - result[len_a + len_b + len_c + len_d] = 0; - - return result; -} - struct Version_Data_Utf8 { i32 best_version[4]; // For Windows 8 versions, only two of these numbers are used. String best_name; @@ -426,7 +387,7 @@ void find_windows_kit_root(Find_Result_Utf8 *result) { // If we get here, we failed to find anything. } -bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *result) { +bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result_Utf8 *result) { // The name of this procedure is kind of cryptic. Its purpose is // to fight through Microsoft craziness. The things that the fine // Visual Studio team want you to do, JUST TO FIND A SINGLE FOLDER @@ -470,64 +431,49 @@ bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *res defer (instance->Release()); - BSTR bstr_inst_path; - hr = instance->GetInstallationPath(&bstr_inst_path); + wchar_t* inst_path_wide; + hr = instance->GetInstallationPath(&inst_path_wide); if (hr != S_OK) continue; - defer (SysFreeString(bstr_inst_path)); + defer (SysFreeString(inst_path_wide)); - auto tools_filename = concat(bstr_inst_path, L"\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); - defer (free(tools_filename)); + String inst_path = mc_wstring_to_string(inst_path_wide); + defer (mc_free(inst_path)); - FILE *f = nullptr; - auto open_result = _wfopen_s(&f, tools_filename, L"rt"); - if (open_result != 0) continue; - if (!f) continue; - defer (fclose(f)); + String tools_filename = mc_concat(inst_path, str_lit("\\VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt")); + defer (mc_free(tools_filename)); - LARGE_INTEGER tools_file_size; - auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); - BOOL success = GetFileSizeEx(file_handle, &tools_file_size); - if (!success) continue; + gbFileContents tool_version = gb_file_read_contents(mc_allocator, true, (const char*)tools_filename.text); + defer (gb_file_free_contents(&tool_version)); - auto version_bytes = (tools_file_size.QuadPart + 1) * 2; // Warning: This multiplication by 2 presumes there is no variable-length encoding in the wchars (wacky characters in the file could betray this expectation). - if (version_bytes > 0x7FFFFFFF) continue; // Avoid overflow. + String version_string = make_string((const u8*)tool_version.data, tool_version.size); + version_string = string_trim_whitespace(version_string); - wchar_t *version = (wchar_t *)calloc(1, (usize)version_bytes); - defer (free(version)); + String base_path = mc_concat(inst_path, str_lit("\\VC\\Tools\\MSVC\\"), version_string); + defer (mc_free(base_path)); - auto read_result = fgetws(version, (int)version_bytes, f); - if (!read_result) continue; - - auto version_tail = wcschr(version, '\n'); - if (version_tail) *version_tail = 0; // Stomp the data, because nobody cares about it. - - wchar_t *library_path = nullptr; + String library_path = {}; if (build_context.metrics.arch == TargetArch_amd64) { - library_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x64\\"); + library_path = mc_concat(base_path, str_lit("\\lib\\x64\\")); } else if (build_context.metrics.arch == TargetArch_i386) { - library_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\lib\\x86\\"); + library_path = mc_concat(base_path, str_lit("\\lib\\x86\\")); } else { continue; } - auto library_file = concat(library_path, L"vcruntime.lib"); // @Speed: Could have library_path point to this string, with a smaller count, to save on memory flailing! + String library_file = mc_concat(library_path, str_lit("vcruntime.lib")); - if (os_file_exists(library_file)) { - wchar_t *link_exe_path = nullptr; + if (gb_file_exists((const char*)library_file.text)) { if (build_context.metrics.arch == TargetArch_amd64) { - link_exe_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx64\\x64\\"); + result->vs_exe_path = mc_concat(base_path, str_lit("\\bin\\Hostx64\\x64\\")); } else if (build_context.metrics.arch == TargetArch_i386) { - link_exe_path = concat(bstr_inst_path, L"\\VC\\Tools\\MSVC\\", version, L"\\bin\\Hostx86\\x86\\"); + result->vs_exe_path = mc_concat(base_path, str_lit("\\bin\\Hostx86\\x86\\")); } else { continue; } - - result->vs_exe_path = link_exe_path; result->vs_library_path = library_path; return true; } - /* Ryan Saunderson said: "Clang uses the 'SetupInstance->GetInstallationVersion' / ISetupHelper->ParseVersion to find the newest version @@ -542,65 +488,64 @@ bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result *res { HKEY vs7_key; rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &vs7_key); - if (rc != S_OK) return false; + if (rc != S_OK) return false; defer (RegCloseKey(vs7_key)); // Hardcoded search for 4 prior Visual Studio versions. Is there something better to do here? - wchar_t const *versions[] = { L"14.0", L"13.0", L"12.0", L"11.0", L"10.0", L"9.0", }; + char const *versions[] = { "14.0", "13.0", "12.0", "11.0", "10.0", "9.0", }; const int NUM_VERSIONS = sizeof(versions) / sizeof(versions[0]); for (int i = 0; i < NUM_VERSIONS; i++) { - wchar_t const *v = versions[i]; + char const *v = versions[i]; DWORD dw_type; - DWORD cb_data; + DWORD required_length; - auto rc = RegQueryValueExW(vs7_key, v, NULL, &dw_type, NULL, &cb_data); + auto rc = RegQueryValueExA(vs7_key, v, NULL, &dw_type, NULL, &required_length); if ((rc == ERROR_FILE_NOT_FOUND) || (dw_type != REG_SZ)) { continue; } - auto buffer = (wchar_t *)calloc(1, cb_data); - if (!buffer) return false; - defer (free(buffer)); + DWORD length = required_length + 2; // The +2 is for the maybe optional zero later on. Probably we are over-allocating. + char *c_str = gb_alloc_array(mc_allocator, char, length); - rc = RegQueryValueExW(vs7_key, v, NULL, NULL, (LPBYTE)buffer, &cb_data); + rc = RegQueryValueExA(vs7_key, v, NULL, NULL, (LPBYTE)c_str, &length); if (rc != 0) continue; - // @Robustness: Do the zero-termination thing suggested in the RegQueryValue docs? + if (c_str[required_length]) { + c_str[required_length+1] = 0; + } + String base_path = make_string_c(c_str); - wchar_t *lib_path = nullptr; + String lib_path = {}; if (build_context.metrics.arch == TargetArch_amd64) { - lib_path = concat(buffer, L"VC\\Lib\\amd64\\"); + lib_path = mc_concat(base_path, str_lit("VC\\Lib\\amd64\\")); } else if (build_context.metrics.arch == TargetArch_i386) { - lib_path = concat(buffer, L"VC\\Lib\\"); + lib_path = mc_concat(base_path, str_lit("VC\\Lib\\")); } else { continue; } // Check to see whether a vcruntime.lib actually exists here. - auto vcruntime_filename = concat(lib_path, L"vcruntime.lib"); - defer (free(vcruntime_filename)); + String vcruntime_filename = mc_concat(lib_path, str_lit("vcruntime.lib")); + defer (mc_free(vcruntime_filename)); - if (os_file_exists(vcruntime_filename)) { + if (gb_file_exists((const char*)vcruntime_filename.text)) { if (build_context.metrics.arch == TargetArch_amd64) { - result->vs_exe_path = concat(buffer, L"VC\\bin\\"); + result->vs_exe_path = mc_concat(base_path, str_lit("VC\\bin\\")); } else if (build_context.metrics.arch == TargetArch_i386) { - result->vs_exe_path = concat(buffer, L"VC\\bin\\x86_amd64\\"); + result->vs_exe_path = mc_concat(base_path, str_lit("VC\\bin\\x86_amd64\\")); } else { continue; } - result->vs_library_path = lib_path; return true; } - - free(lib_path); + mc_free(lib_path); } // If we get here, we failed to find anything. } - return false; } @@ -618,13 +563,7 @@ Find_Result_Utf8 find_visual_studio_and_windows_sdk_utf8() { } } - Find_Result result = {}; - bool ok = find_visual_studio_by_fighting_through_microsoft_craziness(&result); - - if (ok) { - r.vs_exe_path = mc_wstring_to_string(result.vs_exe_path); - r.vs_library_path = mc_wstring_to_string(result.vs_library_path); - } + find_visual_studio_by_fighting_through_microsoft_craziness(&r); #if 0 printf("windows_sdk_root: %.*s\n", LIT(r.windows_sdk_root)); From 5c10b35df7b174e883a5523b82f4124a1951a27d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 22:26:04 +0100 Subject: [PATCH 212/254] Fix sqrt for simd --- src/check_builtin.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ba34a177b..ad227489b 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3704,12 +3704,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 return false; } } - if (!is_type_float(x.type)) { - gbString xts = type_to_string(x.type); - error(x.expr, "Expected a floating point value for '%.*s', got %s", LIT(builtin_name), xts); - gb_string_free(xts); - return false; - } else if (x.mode == Addressing_Constant) { + if (is_type_float(x.type) && x.mode == Addressing_Constant) { f64 v = exact_value_to_f64(x.value); operand->mode = Addressing_Constant; From 92ed9e0b94c36e9191126e040c30d49be624a341 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 27 May 2022 23:48:31 +0200 Subject: [PATCH 213/254] Refactor Walter's PR. --- src/build_settings.cpp | 212 +------------------------------------- src/microsoft_craziness.h | 204 ++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+), 209 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 202f2fd50..e9f5f2099 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1186,203 +1186,6 @@ void init_build_context(TargetMetrics *cross_target) { // NOTE(IC): In order to find Visual C++ paths without relying on environment variables. // NOTE(Jeroen): No longer needed in `main.cpp -> linker_stage`. We now resolve those paths in `init_build_paths`. #include "microsoft_craziness.h" - -// NOTE(WalterPlinge): Environment variables can help to find Visual C++ and WinSDK paths for both -// official and portable installations (like mmozeiko's portable msvc script). This will only use -// the first paths it finds, and won't overwrite any values that `result` already has. -bool find_msvc_install_from_env_vars(gbAllocator allocator, Find_Result_Utf8 *result) { - if (build_context.metrics.arch != TargetArch_amd64 && build_context.metrics.arch != TargetArch_i386) { - return false; - } - - bool sdk_found = false; - if(result->windows_sdk_root.len > 0 - && result->windows_sdk_um_library_path.len > 0 - && result->windows_sdk_ucrt_library_path.len > 0) { - sdk_found = true; - } - - // We can find windows sdk using the following combination of env vars: - // (UniversalCRTSdkDir or WindowsSdkDir) and (WindowsSDKLibVersion or WindowsSDKVersion) - if (!sdk_found) { - // These appear to be suitable env vars used by Visual Studio - char const *win_sdk_ver_env = gb_get_env("WindowsSDKVersion", allocator); - char const *win_sdk_lib_env = gb_get_env("WindowsSDKLibVersion", allocator); - char const *win_sdk_dir_env = gb_get_env("WindowsSdkDir", allocator); - char const *crt_sdk_dir_env = gb_get_env("UniversalCRTSdkDir", allocator); - defer (gb_free(allocator, (void*)win_sdk_ver_env)); - defer (gb_free(allocator, (void*)win_sdk_lib_env)); - defer (gb_free(allocator, (void*)win_sdk_dir_env)); - defer (gb_free(allocator, (void*)crt_sdk_dir_env)); - - // NOTE(WalterPlinge): If any combination is found, let's just assume they are correct - if ((win_sdk_ver_env || win_sdk_lib_env) && (win_sdk_dir_env || crt_sdk_dir_env)) { - //? Maybe we need to handle missing '\' at end of strings, so far it doesn't seem an issue - String dir = win_sdk_dir_env - ? make_string_c(win_sdk_dir_env) - : make_string_c(crt_sdk_dir_env); - String ver = win_sdk_ver_env - ? make_string_c(win_sdk_ver_env) - : make_string_c(win_sdk_lib_env); - - // These have trailing '\' as we are just composing the path - String um_dir = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("um\\x64\\") - : make_string_c("um\\x86\\"); - String ucrt_dir = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("ucrt\\x64\\") - : make_string_c("ucrt\\x86\\"); - - result->windows_sdk_root = concatenate3_strings(allocator, dir, make_string_c("Lib\\"), ver); - result->windows_sdk_um_library_path = concatenate_strings(allocator, result->windows_sdk_root, um_dir); - result->windows_sdk_ucrt_library_path = concatenate_strings(allocator, result->windows_sdk_root, ucrt_dir); - - sdk_found = true; - } - } - - // If we haven't found it yet, we can loop through LIB for specific folders - //? This may not be robust enough using `um\x64` and `ucrt\x64` - if (!sdk_found) { - char const *lib_env = gb_get_env("LIB", allocator); - defer (gb_free(allocator, (void*)lib_env)); - if (lib_env) { - String lib = make_string_c(lib_env); - - // NOTE(WalterPlinge): I don't know if there's a chance for the LIB variable - // to be set without a trailing '\' (apart from manually), so we can just - // check paths without it (see use of `String end` in the loop below) - String um_dir = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("um\\x64") - : make_string_c("um\\x86"); - String ucrt_dir = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("ucrt\\x64") - : make_string_c("ucrt\\x86"); - - isize lo = {0}; - isize hi = {0}; - for (isize c = 0; c <= lib.len; c += 1) { - if (c != lib.len && lib[c] != ';') { - continue; - } - hi = c; - String dir = substring(lib, lo, hi); - defer (lo = hi + 1); - - // Remove the last slash so we can match with the strings above - String end = dir[dir.len - 1] == '\\' - ? substring(dir, 0, dir.len - 1) - : substring(dir, 0, dir.len); - - // Find one and we can make the other - if (string_ends_with(end, um_dir)) { - result->windows_sdk_um_library_path = concatenate_strings(allocator, end, make_string_c("\\")); - break; - } else if (string_ends_with(end, ucrt_dir)) { - result->windows_sdk_ucrt_library_path = concatenate_strings(allocator, end, make_string_c("\\")); - break; - } - } - - // Get the root from the one we found, and make the other - // NOTE(WalterPlinge): we need to copy the string so that we don't risk a double free - if (result->windows_sdk_um_library_path.len > 0) { - String root = substring(result->windows_sdk_um_library_path, 0, result->windows_sdk_um_library_path.len - 1 - um_dir.len); - result->windows_sdk_root = copy_string(allocator, root); - result->windows_sdk_ucrt_library_path = concatenate3_strings(allocator, result->windows_sdk_root, ucrt_dir, make_string_c("\\")); - } else if (result->windows_sdk_ucrt_library_path.len > 0) { - String root = substring(result->windows_sdk_ucrt_library_path, 0, result->windows_sdk_ucrt_library_path.len - 1 - ucrt_dir.len); - result->windows_sdk_root = copy_string(allocator, root); - result->windows_sdk_um_library_path = concatenate3_strings(allocator, result->windows_sdk_root, um_dir, make_string_c("\\")); - } - - if (result->windows_sdk_root.len > 0) { - sdk_found = true; - } - } - } - - // NOTE(WalterPlinge): So far this function assumes it will only be called if MSVC was - // installed using mmozeiko's portable msvc script, which uses the windows 10 sdk. - // This may need to be changed later if it ends up causing problems. - if (sdk_found && result->windows_sdk_version == 0) { - result->windows_sdk_version = 10; - } - - bool vs_found = false; - if (result->vs_exe_path.len > 0 && result->vs_library_path.len > 0) { - vs_found = true; - } - - // We can find visual studio using VCToolsInstallDir - if (!vs_found) { - char const *vctid_env = gb_get_env("VCToolsInstallDir", allocator); - defer (gb_free(allocator, (void*)vctid_env)); - if (vctid_env) { - String vctid = make_string_c(vctid_env); - String exe = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("bin\\Hostx64\\x64\\") - : make_string_c("bin\\Hostx86\\x86\\"); - String lib = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("lib\\x64\\") - : make_string_c("lib\\x86\\"); - result->vs_exe_path = concatenate_strings(allocator, vctid, exe); - result->vs_library_path = concatenate_strings(allocator, vctid, lib); - vs_found = true; - } - } - - // If we haven't found it yet, we can loop through Path for specific folders - if (!vs_found) { - char const *path_env = gb_get_env("Path", allocator); - defer (gb_free(allocator, (void*)path_env)); - if (path_env) { - String path = make_string_c(path_env); - - String exe = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("bin\\Hostx64\\x64") - : make_string_c("bin\\Hostx86\\x86"); - String lib = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("lib\\x64") - : make_string_c("lib\\x86"); - - isize lo = {0}; - isize hi = {0}; - for (isize c = 0; c <= path.len; c += 1) { - if (c != path.len && path[c] != ';') { - continue; - } - - hi = c; - String dir = substring(path, lo, hi); - defer (lo = hi + 1); - - String end = dir[dir.len - 1] == '\\' - ? substring(dir, 0, dir.len - 1) - : substring(dir, 0, dir.len); - - // check if cl.exe and link.exe exist in this folder - String cl = concatenate_strings(allocator, end, make_string_c("\\cl.exe")); - String link = concatenate_strings(allocator, end, make_string_c("\\link.exe")); - defer (gb_free(allocator, cl.text)); - defer (gb_free(allocator, link.text)); - - if (!string_ends_with(end, exe) || !gb_file_exists((char *)cl.text) || !gb_file_exists((char *)link.text)) { - continue; - } - - String root = substring(end, 0, end.len - exe.len); - - result->vs_exe_path = concatenate_strings(allocator, end, make_string_c("\\")); - result->vs_library_path = concatenate3_strings(allocator, root, lib, make_string_c("\\")); - - vs_found = true; - } - } - } - - return sdk_found && vs_found; -} #endif // NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate. @@ -1426,18 +1229,9 @@ bool init_build_paths(String init_filename) { Find_Result_Utf8 find_result = find_visual_studio_and_windows_sdk_utf8(); defer (mc_free_all()); - bool all_found = - find_result.windows_sdk_root.len > 0 && - find_result.windows_sdk_um_library_path.len > 0 && - find_result.windows_sdk_ucrt_library_path.len > 0 && - find_result.vs_exe_path.len > 0 && - find_result.vs_library_path.len > 0; - - if (find_result.windows_sdk_version == 0 || !all_found) { - if (!find_msvc_install_from_env_vars(ha, &find_result)) { - gb_printf_err("Windows SDK not found.\n"); - return false; - } + if (find_result.windows_sdk_version == 0) { + gb_printf_err("Windows SDK not found.\n"); + return false; } if (!build_context.use_lld && find_result.vs_exe_path.len == 0) { diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index 98707a4a1..f5222e943 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -87,6 +87,11 @@ String mc_concat(String a, String b, String c) { return concatenate3_strings(mc_allocator, a, b, c); } +String mc_get_env(String key) { + char const * value = gb_get_env((char const *)key.text, mc_allocator); + return make_string_c(value); +} + void mc_free(String str) { gb_free(mc_allocator, str.text); } @@ -549,6 +554,194 @@ bool find_visual_studio_by_fighting_through_microsoft_craziness(Find_Result_Utf8 return false; } +// NOTE(WalterPlinge): Environment variables can help to find Visual C++ and WinSDK paths for both +// official and portable installations (like mmozeiko's portable msvc script). This will only use +// the first paths it finds, and won't overwrite any values that `result` already has. +bool find_msvc_install_from_env_vars(Find_Result_Utf8 *result) { + if (build_context.metrics.arch != TargetArch_amd64 && build_context.metrics.arch != TargetArch_i386) { + return false; + } + + // We can find windows sdk using the following combination of env vars: + // (UniversalCRTSdkDir or WindowsSdkDir) and (WindowsSDKLibVersion or WindowsSDKVersion) + bool sdk_found = false; + + // These appear to be suitable env vars used by Visual Studio + String win_sdk_ver_env = mc_get_env(str_lit("WindowsSDKVersion")); + String win_sdk_lib_env = mc_get_env(str_lit("WindowsSDKLibVersion")); + String win_sdk_dir_env = mc_get_env(str_lit("WindowsSdkDir")); + String crt_sdk_dir_env = mc_get_env(str_lit("UniversalCRTSdkDir")); + + defer ({ + mc_free(win_sdk_ver_env); + mc_free(win_sdk_lib_env); + mc_free(win_sdk_dir_env); + mc_free(crt_sdk_dir_env); + }); + + // NOTE(WalterPlinge): If any combination is found, let's just assume they are correct + if ((win_sdk_ver_env.len || win_sdk_lib_env.len) && (win_sdk_dir_env.len || crt_sdk_dir_env.len)) { + //? Maybe we need to handle missing '\' at end of strings, so far it doesn't seem an issue + String dir = win_sdk_dir_env.len ? win_sdk_dir_env : crt_sdk_dir_env; + String ver = win_sdk_ver_env.len ? win_sdk_ver_env : win_sdk_lib_env; + + // These have trailing '\' as we are just composing the path + String um_dir = build_context.metrics.arch == TargetArch_amd64 + ? str_lit("um\\x64\\") + : str_lit("um\\x86\\"); + String ucrt_dir = build_context.metrics.arch == TargetArch_amd64 + ? str_lit("ucrt\\x64\\") + : str_lit("ucrt\\x86\\"); + + result->windows_sdk_root = mc_concat(dir, str_lit("Lib\\"), ver); + result->windows_sdk_um_library_path = mc_concat(result->windows_sdk_root, um_dir); + result->windows_sdk_ucrt_library_path = mc_concat(result->windows_sdk_root, ucrt_dir); + + sdk_found = true; + } + + // If we haven't found it yet, we can loop through LIB for specific folders + //? This may not be robust enough using `um\x64` and `ucrt\x64` + if (!sdk_found) { + char const *lib_env = gb_get_env("LIB", mc_allocator); + defer (gb_free(mc_allocator, (void*)lib_env)); + if (lib_env) { + String lib = make_string_c(lib_env); + + // NOTE(WalterPlinge): I don't know if there's a chance for the LIB variable + // to be set without a trailing '\' (apart from manually), so we can just + // check paths without it (see use of `String end` in the loop below) + String um_dir = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("um\\x64") + : make_string_c("um\\x86"); + String ucrt_dir = build_context.metrics.arch == TargetArch_amd64 + ? make_string_c("ucrt\\x64") + : make_string_c("ucrt\\x86"); + + isize lo = {0}; + isize hi = {0}; + for (isize c = 0; c <= lib.len; c += 1) { + if (c != lib.len && lib[c] != ';') { + continue; + } + hi = c; + String dir = substring(lib, lo, hi); + defer (lo = hi + 1); + + // Remove the last slash so we can match with the strings above + String end = dir[dir.len - 1] == '\\' + ? substring(dir, 0, dir.len - 1) + : substring(dir, 0, dir.len); + + // Find one and we can make the other + if (string_ends_with(end, um_dir)) { + result->windows_sdk_um_library_path = mc_concat(end, str_lit("\\")); + break; + } else if (string_ends_with(end, ucrt_dir)) { + result->windows_sdk_ucrt_library_path = mc_concat(end, str_lit("\\")); + break; + } + } + + // Get the root from the one we found, and make the other + // NOTE(WalterPlinge): we need to copy the string so that we don't risk a double free + if (result->windows_sdk_um_library_path.len > 0) { + String root = substring(result->windows_sdk_um_library_path, 0, result->windows_sdk_um_library_path.len - 1 - um_dir.len); + result->windows_sdk_root = copy_string(mc_allocator, root); + result->windows_sdk_ucrt_library_path = mc_concat(result->windows_sdk_root, ucrt_dir, str_lit("\\")); + } else if (result->windows_sdk_ucrt_library_path.len > 0) { + String root = substring(result->windows_sdk_ucrt_library_path, 0, result->windows_sdk_ucrt_library_path.len - 1 - ucrt_dir.len); + result->windows_sdk_root = copy_string(mc_allocator, root); + result->windows_sdk_um_library_path = mc_concat(result->windows_sdk_root, um_dir, str_lit("\\")); + } + + if (result->windows_sdk_root.len > 0) { + sdk_found = true; + } + } + } + + // NOTE(WalterPlinge): So far this function assumes it will only be called if MSVC was + // installed using mmozeiko's portable msvc script, which uses the windows 10 sdk. + // This may need to be changed later if it ends up causing problems. + if (sdk_found && result->windows_sdk_version == 0) { + result->windows_sdk_version = 10; + } + + bool vs_found = false; + if (result->vs_exe_path.len > 0 && result->vs_library_path.len > 0) { + vs_found = true; + } + + // We can find visual studio using VCToolsInstallDir + if (!vs_found) { + String vctid = mc_get_env(str_lit("VCToolsInstallDir")); + defer (mc_free(vctid)); + + if (vctid.len) { + String exe = build_context.metrics.arch == TargetArch_amd64 + ? str_lit("bin\\Hostx64\\x64\\") + : str_lit("bin\\Hostx86\\x86\\"); + String lib = build_context.metrics.arch == TargetArch_amd64 + ? str_lit("lib\\x64\\") + : str_lit("lib\\x86\\"); + + result->vs_exe_path = mc_concat(vctid, exe); + result->vs_library_path = mc_concat(vctid, lib); + vs_found = true; + } + } + + // If we haven't found it yet, we can loop through Path for specific folders + if (!vs_found) { + String path = mc_get_env(str_lit("Path")); + defer (mc_free(path)); + + if (path.len) { + String exe = build_context.metrics.arch == TargetArch_amd64 + ? str_lit("bin\\Hostx64\\x64") + : str_lit("bin\\Hostx86\\x86"); + String lib = build_context.metrics.arch == TargetArch_amd64 + ? str_lit("lib\\x64") + : str_lit("lib\\x86"); + + isize lo = {0}; + isize hi = {0}; + for (isize c = 0; c <= path.len; c += 1) { + if (c != path.len && path[c] != ';') { + continue; + } + + hi = c; + String dir = substring(path, lo, hi); + defer (lo = hi + 1); + + String end = dir[dir.len - 1] == '\\' + ? substring(dir, 0, dir.len - 1) + : substring(dir, 0, dir.len); + + // check if cl.exe and link.exe exist in this folder + String cl = mc_concat(end, str_lit("\\cl.exe")); + String link = mc_concat(end, str_lit("\\link.exe")); + defer (mc_free(cl)); + defer (mc_free(link)); + + if (!string_ends_with(end, exe) || !gb_file_exists((char *)cl.text) || !gb_file_exists((char *)link.text)) { + continue; + } + + String root = substring(end, 0, end.len - exe.len); + result->vs_exe_path = mc_concat(end, str_lit("\\")); + result->vs_library_path = mc_concat(root, lib, str_lit("\\")); + + vs_found = true; + } + } + } + + return sdk_found && vs_found; +} + Find_Result_Utf8 find_visual_studio_and_windows_sdk_utf8() { Find_Result_Utf8 r = {}; find_windows_kit_root(&r); @@ -565,6 +758,17 @@ Find_Result_Utf8 find_visual_studio_and_windows_sdk_utf8() { find_visual_studio_by_fighting_through_microsoft_craziness(&r); + bool all_found = + r.windows_sdk_root.len > 0 && + r.windows_sdk_um_library_path.len > 0 && + r.windows_sdk_ucrt_library_path.len > 0 && + r.vs_exe_path.len > 0 && + r.vs_library_path.len > 0; + + if (!all_found && !find_msvc_install_from_env_vars(&r)) { + return {}; + } + #if 0 printf("windows_sdk_root: %.*s\n", LIT(r.windows_sdk_root)); printf("windows_sdk_um_library_path: %.*s\n", LIT(r.windows_sdk_um_library_path)); From 1c1f5e2231dc8e1ec992ec7399407f78c98dc283 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 22:56:11 +0100 Subject: [PATCH 214/254] Complete SSE2 --- core/simd/x86/sse2.odin | 332 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 309 insertions(+), 23 deletions(-) diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index f52981639..359f19062 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -459,6 +459,292 @@ _mm_unpacklo_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_add_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(a, 0, _mm_cvtsd_f64(a) + _mm_cvtsd_f64(b)) +} +_mm_add_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.add(a, b) +} +_mm_div_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(a, 0, _mm_cvtsd_f64(a) / _mm_cvtsd_f64(b)) +} +_mm_div_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.div(a, b) +} +_mm_max_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return maxsd(a, b) +} +_mm_max_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return maxpd(a, b) +} +_mm_min_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return minsd(a, b) +} +_mm_min_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return minpd(a, b) +} +_mm_mul_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(a, 0, _mm_cvtsd_f64(a) * _mm_cvtsd_f64(b)) +} +_mm_mul_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.mul(a, b) +} +_mm_sqrt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(a, 0, _mm_cvtsd_f64(sqrtsd(b))) +} +_mm_sqrt_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { + return simd.sqrt(a) +} +_mm_sub_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(a, 0, _mm_cvtsd_f64(a) - _mm_cvtsd_f64(b)) +} +_mm_sub_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.sub(a, b) +} +_mm_and_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return transmute(__m128d)_mm_and_si128(transmute(__m128i)a, transmute(__m128i)b) +} +_mm_andnot_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return transmute(__m128d)_mm_andnot_si128(transmute(__m128i)a, transmute(__m128i)b) +} +_mm_or_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return transmute(__m128d)_mm_or_si128(transmute(__m128i)a, transmute(__m128i)b) +} +_mm_xor_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return transmute(__m128d)_mm_xor_si128(transmute(__m128i)a, transmute(__m128i)b) +} + + + + +_mm_cmpeq_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmpsd(a, b, 0) +} +_mm_cmplt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmpsd(a, b, 1) +} +_mm_cmple_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmpsd(a, b, 2) +} +_mm_cmpgt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(_mm_cmplt_sd(b, a), 1, simd.extract(a, 1)) +} +_mm_cmpge_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(_mm_cmple_sd(b, a), 1, simd.extract(a, 1)) +} +_mm_cmpord_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmpsd(a, b, 7) +} +_mm_cmpunord_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmpsd(a, b, 3) +} +_mm_cmpneq_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmpsd(a, b, 4) +} +_mm_cmpnlt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmpsd(a, b, 5) +} +_mm_cmpnle_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmpsd(a, b, 6) +} +_mm_cmpngt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(_mm_cmpnlt_sd(b, a), 1, simd.extract(a, 1)) +} +_mm_cmpnge_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return simd.replace(_mm_cmpnle_sd(b, a), 1, simd.extract(a, 1)) +} +_mm_cmpeq_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmppd(a, b, 0) +} +_mm_cmplt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmppd(a, b, 1) +} +_mm_cmple_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmppd(a, b, 2) +} +_mm_cmpgt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return _mm_cmplt_pd(b, a) +} +_mm_cmpge_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return _mm_cmple_pd(b, a) +} +_mm_cmpord_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmppd(a, b, 7) +} +_mm_cmpunord_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmppd(a, b, 3) +} +_mm_cmpneq_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmppd(a, b, 4) +} +_mm_cmpnlt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmppd(a, b, 5) +} +_mm_cmpnle_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return cmppd(a, b, 6) +} +_mm_cmpngt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return _mm_cmpnlt_pd(b, a) +} +_mm_cmpnge_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return _mm_cmpnle_pd(b, a) +} +_mm_comieq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return comieqsd(a, b) +} +_mm_comilt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return comiltsd(a, b) +} +_mm_comile_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return comilesd(a, b) +} +_mm_comigt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return comigtsd(a, b) +} +_mm_comige_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return comigesd(a, b) +} +_mm_comineq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return comineqsd(a, b) +} +_mm_ucomieq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return ucomieqsd(a, b) +} +_mm_ucomilt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return ucomiltsd(a, b) +} +_mm_ucomile_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return ucomilesd(a, b) +} +_mm_ucomigt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return ucomigtsd(a, b) +} +_mm_ucomige_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return ucomigesd(a, b) +} +_mm_ucomineq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { + return ucomineqsd(a, b) +} + + + + + +_mm_cvtpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { + return cvtpd2ps(a) +} +_mm_cvtps_pd :: #force_inline proc "c" (a: __m128) -> __m128d { + return cvtps2pd(a) +} +_mm_cvtpd_epi32 :: #force_inline proc "c" (a: __m128d) -> __m128i { + return transmute(__m128i)cvtpd2dq(a) +} +_mm_cvtsd_si32 :: #force_inline proc "c" (a: __m128d) -> i32 { + return cvtsd2si(a) +} +_mm_cvtsd_ss :: #force_inline proc "c" (a, b: __m128d) -> __m128 { + return cvtsd2ss(a, b) +} +_mm_cvtsd_f64 :: #force_inline proc "c" (a: __m128d) -> f64 { + return simd.extract(a, 0) +} +_mm_cvtss_sd :: #force_inline proc "c" (a, b: __m128) -> __m128d { + return cvtss2sd(a, b) +} +_mm_cvttpd_epi32 :: #force_inline proc "c" (a: __m128d) -> __m128i { + return transmute(__m128i)cvttpd2dq(a) +} +_mm_cvttsd_si32 :: #force_inline proc "c" (a: __m128d) -> i32 { + return cvttsd2si(a) +} +_mm_cvttps_epi32 :: #force_inline proc "c" (a: __m128) -> __m128i { + return transmute(__m128i)cvttps2dq(a) +} +_mm_set_sd :: #force_inline proc "c" (a: f64) -> __m128d { + return _mm_set_pd(0.0, a) +} +_mm_set1_pd :: #force_inline proc "c" (a: f64) -> __m128d { + return _mm_set_pd(a, a) +} +_mm_set_pd1 :: #force_inline proc "c" (a: f64) -> __m128d { + return _mm_set_pd(a, a) +} +_mm_set_pd :: #force_inline proc "c" (a: f64, b: f64) -> __m128d { + return __m128d{b, a} +} +_mm_setr_pd :: #force_inline proc "c" (a: f64, b: f64) -> __m128d { + return _mm_set_pd(b, a) +} +_mm_setzero_pd :: #force_inline proc "c" () -> __m128d { + return _mm_set_pd(0.0, 0.0) +} +_mm_movemask_pd :: #force_inline proc "c" (a: __m128d) -> i32 { + return movmskpd(a) +} +_mm_load_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { + return (^__m128d)(mem_addr)^ +} +_mm_load_sd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { + return _mm_setr_pd(mem_addr^, 0.) +} +_mm_loadh_pd :: #force_inline proc "c" (a: __m128d, mem_addr: ^f64) -> __m128d { + return _mm_setr_pd(simd.extract(a, 0), mem_addr^) +} +_mm_loadl_pd :: #force_inline proc "c" (a: __m128d, mem_addr: ^f64) -> __m128d { + return _mm_setr_pd(mem_addr^, simd.extract(a, 1)) +} +_mm_stream_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + intrinsics.nontemporal_store((^__m128d)(mem_addr), a) +} +_mm_store_sd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + mem_addr^ = simd.extract(a, 0) +} +_mm_store_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + (^__m128d)(mem_addr)^ = a +} +_mm_storeu_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + storeupd(mem_addr, a) +} +_mm_store1_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + (^__m128d)(mem_addr)^ = simd.shuffle(a, a, 0, 0) +} +_mm_store_pd1 :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + (^__m128d)(mem_addr)^ = simd.shuffle(a, a, 0, 0) +} +_mm_storer_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + (^__m128d)(mem_addr)^ = simd.shuffle(a, a, 1, 0) +} +_mm_storeh_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + mem_addr^ = simd.extract(a, 1) +} +_mm_storel_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { + mem_addr^ = simd.extract(a, 0) +} +_mm_load1_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { + d := mem_addr^ + return _mm_setr_pd(d, d) +} +_mm_load_pd1 :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { + return _mm_load1_pd(mem_addr) +} +_mm_loadr_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { + a := _mm_load_pd(mem_addr) + return simd.shuffle(a, a, 1, 0) +} +_mm_loadu_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { + dst := _mm_undefined_pd() + intrinsics.mem_copy_non_overlapping(&dst, mem_addr, size_of(__m128d)) + return dst +} +_mm_shuffle_pd :: #force_inline proc "c" (a, b: __m128d, $MASK: u32) -> __m128d { + return simd.shuffle(a, b, MASK&0b1, ((MASK>>1)&0b1) + 2) +} +_mm_move_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return _mm_setr_pd(simd.extract(b, 0), simd.extract(a, 1)) +} + + + _mm_castpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { return transmute(__m128)a @@ -566,53 +852,53 @@ foreign _ { @(link_name="llvm.x86.sse2.maskmov.dqu") maskmovdqu :: proc(a: i8x16, mask: i8x16, mem_addr: rawptr) --- @(link_name="llvm.x86.sse2.packsswb.128") - packsswb :: proc(a: i16x8, b: i16x8) -> i8x16 --- + packsswb :: proc(a, b: i16x8) -> i8x16 --- @(link_name="llvm.x86.sse2.packssdw.128") - packssdw :: proc(a: i32x4, b: i32x4) -> i16x8 --- + packssdw :: proc(a, b: i32x4) -> i16x8 --- @(link_name="llvm.x86.sse2.packuswb.128") - packuswb :: proc(a: i16x8, b: i16x8) -> u8x16 --- + packuswb :: proc(a, b: i16x8) -> u8x16 --- @(link_name="llvm.x86.sse2.pmovmskb.128") pmovmskb :: proc(a: i8x16) -> i32 --- @(link_name="llvm.x86.sse2.max.sd") - maxsd :: proc(a: __m128d, b: __m128d) -> __m128d --- + maxsd :: proc(a, b: __m128d) -> __m128d --- @(link_name="llvm.x86.sse2.max.pd") - maxpd :: proc(a: __m128d, b: __m128d) -> __m128d --- + maxpd :: proc(a, b: __m128d) -> __m128d --- @(link_name="llvm.x86.sse2.min.sd") - minsd :: proc(a: __m128d, b: __m128d) -> __m128d --- + minsd :: proc(a, b: __m128d) -> __m128d --- @(link_name="llvm.x86.sse2.min.pd") - minpd :: proc(a: __m128d, b: __m128d) -> __m128d --- + minpd :: proc(a, b: __m128d) -> __m128d --- @(link_name="llvm.x86.sse2.sqrt.sd") sqrtsd :: proc(a: __m128d) -> __m128d --- @(link_name="llvm.x86.sse2.sqrt.pd") sqrtpd :: proc(a: __m128d) -> __m128d --- @(link_name="llvm.x86.sse2.cmp.sd") - cmpsd :: proc(a: __m128d, b: __m128d, imm8: i8) -> __m128d --- + cmpsd :: proc(a, b: __m128d, imm8: i8) -> __m128d --- @(link_name="llvm.x86.sse2.cmp.pd") - cmppd :: proc(a: __m128d, b: __m128d, imm8: i8) -> __m128d --- + cmppd :: proc(a, b: __m128d, imm8: i8) -> __m128d --- @(link_name="llvm.x86.sse2.comieq.sd") - comieqsd :: proc(a: __m128d, b: __m128d) -> i32 --- + comieqsd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.comilt.sd") - comiltsd :: proc(a: __m128d, b: __m128d) -> i32 --- + comiltsd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.comile.sd") - comilesd :: proc(a: __m128d, b: __m128d) -> i32 --- + comilesd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.comigt.sd") - comigtsd :: proc(a: __m128d, b: __m128d) -> i32 --- + comigtsd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.comige.sd") - comigesd :: proc(a: __m128d, b: __m128d) -> i32 --- + comigesd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.comineq.sd") - comineqsd :: proc(a: __m128d, b: __m128d) -> i32 --- + comineqsd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.ucomieq.sd") - ucomieqsd :: proc(a: __m128d, b: __m128d) -> i32 --- + ucomieqsd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.ucomilt.sd") - ucomiltsd :: proc(a: __m128d, b: __m128d) -> i32 --- + ucomiltsd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.ucomile.sd") - ucomilesd :: proc(a: __m128d, b: __m128d) -> i32 --- + ucomilesd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.ucomigt.sd") - ucomigtsd :: proc(a: __m128d, b: __m128d) -> i32 --- + ucomigtsd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.ucomige.sd") - ucomigesd :: proc(a: __m128d, b: __m128d) -> i32 --- + ucomigesd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.ucomineq.sd") - ucomineqsd :: proc(a: __m128d, b: __m128d) -> i32 --- + ucomineqsd :: proc(a, b: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.movmsk.pd") movmskpd :: proc(a: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.cvtpd2ps") @@ -624,9 +910,9 @@ foreign _ { @(link_name="llvm.x86.sse2.cvtsd2si") cvtsd2si :: proc(a: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.cvtsd2ss") - cvtsd2ss :: proc(a: __m128, b: __m128d) -> __m128 --- + cvtsd2ss :: proc(a, b: __m128d) -> __m128 --- @(link_name="llvm.x86.sse2.cvtss2sd") - cvtss2sd :: proc(a: __m128d, b: __m128) -> __m128d --- + cvtss2sd :: proc(a, b: __m128) -> __m128d --- @(link_name="llvm.x86.sse2.cvttpd2dq") cvttpd2dq :: proc(a: __m128d) -> i32x4 --- @(link_name="llvm.x86.sse2.cvttsd2si") From 8518d3b2327885538993afa2655b83abb71b82e3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 22:57:16 +0100 Subject: [PATCH 215/254] Rename to `non_temporaral_*` --- core/simd/x86/sse.odin | 2 +- core/simd/x86/sse2.odin | 6 +++--- src/check_builtin.cpp | 4 ++-- src/checker_builtin_procs.hpp | 8 ++++---- src/llvm_backend_proc.cpp | 12 ++++++------ 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index 50211872e..e697d5f5e 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -414,7 +414,7 @@ _MM_TRANSPOSE4_PS :: #force_inline proc "c" (row0, row1, row2, row3: ^__m128) { } _mm_stream_ps :: #force_inline proc "c" (addr: [^]f32, a: __m128) { - intrinsics.nontemporal_store((^__m128)(addr), a) + intrinsics.non_temporal_store((^__m128)(addr), a) } diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index 359f19062..a47d2a09d 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -351,10 +351,10 @@ _mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { intrinsics.mem_copy_non_overlapping(mem_addr, &a, 8) } _mm_stream_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { - intrinsics.nontemporal_store(mem_addr, a) + intrinsics.non_temporal_store(mem_addr, a) } _mm_stream_si32 :: #force_inline proc "c" (mem_addr: ^i32, a: i32) { - intrinsics.nontemporal_store(mem_addr, a) + intrinsics.non_temporal_store(mem_addr, a) } _mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { zero := _mm_setzero_si128() @@ -694,7 +694,7 @@ _mm_loadl_pd :: #force_inline proc "c" (a: __m128d, mem_addr: ^f64) -> __m128d { return _mm_setr_pd(mem_addr^, simd.extract(a, 1)) } _mm_stream_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { - intrinsics.nontemporal_store((^__m128d)(mem_addr), a) + intrinsics.non_temporal_store((^__m128d)(mem_addr), a) } _mm_store_sd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { mem_addr^ = simd.extract(a, 0) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index ad227489b..9fa9cc590 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4021,7 +4021,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case BuiltinProc_volatile_store: case BuiltinProc_unaligned_store: - case BuiltinProc_nontemporal_store: + case BuiltinProc_non_temporal_store: case BuiltinProc_atomic_store: { Type *elem = nullptr; @@ -4069,7 +4069,7 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 case BuiltinProc_volatile_load: case BuiltinProc_unaligned_load: - case BuiltinProc_nontemporal_load: + case BuiltinProc_non_temporal_load: case BuiltinProc_atomic_load: { Type *elem = nullptr; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 2dd775193..1d7ee0a34 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -80,8 +80,8 @@ enum BuiltinProcId { BuiltinProc_unaligned_store, BuiltinProc_unaligned_load, - BuiltinProc_nontemporal_store, - BuiltinProc_nontemporal_load, + BuiltinProc_non_temporal_store, + BuiltinProc_non_temporal_load, BuiltinProc_prefetch_read_instruction, BuiltinProc_prefetch_read_data, @@ -369,8 +369,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("unaligned_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("unaligned_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("nontemporal_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("nontemporal_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("non_temporal_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("non_temporal_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("prefetch_read_instruction"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("prefetch_read_data"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 2b7cad5cd..4d0df2861 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2111,7 +2111,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return {}; case BuiltinProc_volatile_store: - case BuiltinProc_nontemporal_store: + case BuiltinProc_non_temporal_store: case BuiltinProc_atomic_store: case BuiltinProc_atomic_store_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); @@ -2121,8 +2121,8 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMValueRef instr = LLVMBuildStore(p->builder, val.value, dst.value); switch (id) { case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; - case BuiltinProc_nontemporal_store: - // TODO(bill): BuiltinProc_nontemporal_store + case BuiltinProc_non_temporal_store: + // TODO(bill): BuiltinProc_non_temporal_store break; case BuiltinProc_atomic_store: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; case BuiltinProc_atomic_store_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[2])); break; @@ -2134,7 +2134,7 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, } case BuiltinProc_volatile_load: - case BuiltinProc_nontemporal_load: + case BuiltinProc_non_temporal_load: case BuiltinProc_atomic_load: case BuiltinProc_atomic_load_explicit: { lbValue dst = lb_build_expr(p, ce->args[0]); @@ -2142,8 +2142,8 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMValueRef instr = LLVMBuildLoad(p->builder, dst.value, ""); switch (id) { case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; - case BuiltinProc_nontemporal_load: - // TODO(bill): BuiltinProc_nontemporal_load + case BuiltinProc_non_temporal_load: + // TODO(bill): BuiltinProc_non_temporal_load break; case BuiltinProc_atomic_load: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; case BuiltinProc_atomic_load_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[1])); break; From 026540040df94309280d6991eba6b2dcc303bd76 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 23:00:52 +0100 Subject: [PATCH 216/254] Add SSE3 support --- core/simd/x86/sse3.odin | 58 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 core/simd/x86/sse3.odin diff --git a/core/simd/x86/sse3.odin b/core/simd/x86/sse3.odin new file mode 100644 index 000000000..6468ea268 --- /dev/null +++ b/core/simd/x86/sse3.odin @@ -0,0 +1,58 @@ +//+build i386, amd64 +package simd_x86 + +import "core:intrinsics" +import "core:simd" + +_mm_addsub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return addsubps(a, b) +} +_mm_addsub_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { + return addsubpd(a, b) +} +_mm_hadd_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { + return haddpd(a, b) +} +_mm_hadd_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return haddps(a, b) +} +_mm_hsub_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { + return hsubpd(a, b) +} +_mm_hsub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return hsubps(a, b) +} +_mm_lddqu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { + return transmute(__m128i)lddqu(mem_addr) +} +_mm_movedup_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { + return simd.shuffle(a, a, 0, 0) +} +_mm_loaddup_pd :: #force_inline proc "c" (mem_addr: [^]f64) -> __m128d { + return _mm_load1_pd(mem_addr) +} +_mm_movehdup_ps :: #force_inline proc "c" (a: __m128) -> __m128 { + return simd.shuffle(a, a, 1, 1, 3, 3) +} +_mm_moveldup_ps :: #force_inline proc "c" (a: __m128) -> __m128 { + return simd.shuffle(a, a, 0, 0, 2, 2) +} + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name = "llvm.x86.sse3.addsub.ps") + addsubps :: proc(a, b: __m128) -> __m128 --- + @(link_name = "llvm.x86.sse3.addsub.pd") + addsubpd :: proc(a: __m128d, b: __m128d) -> __m128d --- + @(link_name = "llvm.x86.sse3.hadd.pd") + haddpd :: proc(a: __m128d, b: __m128d) -> __m128d --- + @(link_name = "llvm.x86.sse3.hadd.ps") + haddps :: proc(a, b: __m128) -> __m128 --- + @(link_name = "llvm.x86.sse3.hsub.pd") + hsubpd :: proc(a: __m128d, b: __m128d) -> __m128d --- + @(link_name = "llvm.x86.sse3.hsub.ps") + hsubps :: proc(a, b: __m128) -> __m128 --- + @(link_name = "llvm.x86.sse3.ldu.dq") + lddqu :: proc(mem_addr: rawptr) -> i8x16 --- +} \ No newline at end of file From f28e3276e765848c7a1af51010a314c539af427f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 28 May 2022 00:02:49 +0200 Subject: [PATCH 217/254] One more change. --- src/microsoft_craziness.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index f5222e943..c3a7c2629 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -612,11 +612,11 @@ bool find_msvc_install_from_env_vars(Find_Result_Utf8 *result) { // to be set without a trailing '\' (apart from manually), so we can just // check paths without it (see use of `String end` in the loop below) String um_dir = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("um\\x64") - : make_string_c("um\\x86"); + ? str_lit("um\\x64") + : str_lit("um\\x86"); String ucrt_dir = build_context.metrics.arch == TargetArch_amd64 - ? make_string_c("ucrt\\x64") - : make_string_c("ucrt\\x86"); + ? str_lit("ucrt\\x64") + : str_lit("ucrt\\x86"); isize lo = {0}; isize hi = {0}; From 4db533ff71e9f11ff21e845509790a355020a944 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 27 May 2022 23:07:33 +0100 Subject: [PATCH 218/254] Add ssse3 support --- core/simd/x86/ssse3.odin | 124 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 core/simd/x86/ssse3.odin diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin new file mode 100644 index 000000000..920dddd85 --- /dev/null +++ b/core/simd/x86/ssse3.odin @@ -0,0 +1,124 @@ +//+build i386, amd64 +package simd_x86 + +import "core:intrinsics" +import "core:simd" +_ :: simd + +_mm_abs_epi8 :: #force_inline proc "c" (a: __m128i) -> __m128i { + return transmute(__m128i)pabsb128(transmute(i8x16)a) +} +_mm_abs_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { + return transmute(__m128i)pabsw128(transmute(i16x8)a) +} +_mm_abs_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { + return transmute(__m128i)pabsd128(transmute(i32x4)a) +} +_mm_shuffle_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pshufb128(transmute(u8x16)a, transmute(u8x16)b) +} +_mm_alignr_epi8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u32) -> __m128i { + shift :: IMM8 + + // If palignr is shifting the pair of vectors more than the size of two + // lanes, emit zero. + if shift > 32 { + return _mm_set1_epi8(0) + } + a, b := a, b + if shift > 16 { + a, b = _mm_set1_epi8(0), a + } + + return transmute(__m128i)simd.shuffle( + transmute(i8x16)b, + transmute(i8x16)a, + 0 when shift > 32 else shift - 16 + 0 when shift > 16 else shift + 0, + 1 when shift > 32 else shift - 16 + 1 when shift > 16 else shift + 1, + 2 when shift > 32 else shift - 16 + 2 when shift > 16 else shift + 2, + 3 when shift > 32 else shift - 16 + 3 when shift > 16 else shift + 3, + 4 when shift > 32 else shift - 16 + 4 when shift > 16 else shift + 4, + 5 when shift > 32 else shift - 16 + 5 when shift > 16 else shift + 5, + 6 when shift > 32 else shift - 16 + 6 when shift > 16 else shift + 6, + 7 when shift > 32 else shift - 16 + 7 when shift > 16 else shift + 7, + 8 when shift > 32 else shift - 16 + 8 when shift > 16 else shift + 8, + 9 when shift > 32 else shift - 16 + 9 when shift > 16 else shift + 9, + 10 when shift > 32 else shift - 16 + 10 when shift > 16 else shift + 10, + 11 when shift > 32 else shift - 16 + 11 when shift > 16 else shift + 11, + 12 when shift > 32 else shift - 16 + 12 when shift > 16 else shift + 12, + 13 when shift > 32 else shift - 16 + 13 when shift > 16 else shift + 13, + 14 when shift > 32 else shift - 16 + 14 when shift > 16 else shift + 14, + 15 when shift > 32 else shift - 16 + 15 when shift > 16 else shift + 15, + ) +} + + +_mm_hadd_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)phaddw128(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_hadds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)phaddsw128(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_hadd_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)phaddd128(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_hsub_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)phsubw128(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_hsubs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)phsubsw128(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_hsub_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)phsubd128(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_maddubs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pmaddubsw128(transmute(u8x16)a, transmute(i8x16)b) +} +_mm_mulhrs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pmulhrsw128(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_sign_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)psignb128(transmute(i8x16)a, transmute(i8x16)b) +} +_mm_sign_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)psignw128(transmute(i16x8)a, transmute(i16x8)b) +} +_mm_sign_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)psignd128(transmute(i32x4)a, transmute(i32x4)b) +} + + + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name = "llvm.x86.ssse3.pabs.b.128") + pabsb128 :: proc(a: i8x16) -> u8x16 --- + @(link_name = "llvm.x86.ssse3.pabs.w.128") + pabsw128 :: proc(a: i16x8) -> u16x8 --- + @(link_name = "llvm.x86.ssse3.pabs.d.128") + pabsd128 :: proc(a: i32x4) -> u32x4 --- + @(link_name = "llvm.x86.ssse3.pshuf.b.128") + pshufb128 :: proc(a, b: u8x16) -> u8x16 --- + @(link_name = "llvm.x86.ssse3.phadd.w.128") + phaddw128 :: proc(a, b: i16x8) -> i16x8 --- + @(link_name = "llvm.x86.ssse3.phadd.sw.128") + phaddsw128 :: proc(a, b: i16x8) -> i16x8 --- + @(link_name = "llvm.x86.ssse3.phadd.d.128") + phaddd128 :: proc(a, b: i32x4) -> i32x4 --- + @(link_name = "llvm.x86.ssse3.phsub.w.128") + phsubw128 :: proc(a, b: i16x8) -> i16x8 --- + @(link_name = "llvm.x86.ssse3.phsub.sw.128") + phsubsw128 :: proc(a, b: i16x8) -> i16x8 --- + @(link_name = "llvm.x86.ssse3.phsub.d.128") + phsubd128 :: proc(a, b: i32x4) -> i32x4 --- + @(link_name = "llvm.x86.ssse3.pmadd.ub.sw.128") + pmaddubsw128 :: proc(a: u8x16, b: i8x16) -> i16x8 --- + @(link_name = "llvm.x86.ssse3.pmul.hr.sw.128") + pmulhrsw128 :: proc(a, b: i16x8) -> i16x8 --- + @(link_name = "llvm.x86.ssse3.psign.b.128") + psignb128 :: proc(a, b: i8x16) -> i8x16 --- + @(link_name = "llvm.x86.ssse3.psign.w.128") + psignw128 :: proc(a, b: i16x8) -> i16x8 --- + @(link_name = "llvm.x86.ssse3.psign.d.128") + psignd128 :: proc(a, b: i32x4) -> i32x4 ---} \ No newline at end of file From cf8a4b981298105ca1a60b0897917fb804729f4f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 28 May 2022 13:52:56 +0200 Subject: [PATCH 219/254] Don't crash if SDK not found during ENV fallback. --- src/microsoft_craziness.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index c3a7c2629..5480f3bea 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -93,11 +93,11 @@ String mc_get_env(String key) { } void mc_free(String str) { - gb_free(mc_allocator, str.text); + if (str.len) gb_free(mc_allocator, str.text); } void mc_free(String16 str) { - gb_free(mc_allocator, str.text); + if (str.len) gb_free(mc_allocator, str.text); } void mc_free_all() { @@ -603,11 +603,10 @@ bool find_msvc_install_from_env_vars(Find_Result_Utf8 *result) { // If we haven't found it yet, we can loop through LIB for specific folders //? This may not be robust enough using `um\x64` and `ucrt\x64` if (!sdk_found) { - char const *lib_env = gb_get_env("LIB", mc_allocator); - defer (gb_free(mc_allocator, (void*)lib_env)); - if (lib_env) { - String lib = make_string_c(lib_env); + String lib = mc_get_env(str_lit("LIB")); + defer (mc_free(lib)); + if (lib.len) { // NOTE(WalterPlinge): I don't know if there's a chance for the LIB variable // to be set without a trailing '\' (apart from manually), so we can just // check paths without it (see use of `String end` in the loop below) @@ -669,6 +668,7 @@ bool find_msvc_install_from_env_vars(Find_Result_Utf8 *result) { } bool vs_found = false; + if (result->vs_exe_path.len > 0 && result->vs_library_path.len > 0) { vs_found = true; } @@ -765,8 +765,8 @@ Find_Result_Utf8 find_visual_studio_and_windows_sdk_utf8() { r.vs_exe_path.len > 0 && r.vs_library_path.len > 0; - if (!all_found && !find_msvc_install_from_env_vars(&r)) { - return {}; + if (!all_found) { + find_msvc_install_from_env_vars(&r); } #if 0 From 618d3bf62fbcfa6ca7f827ad4090143b8535b4a2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 28 May 2022 13:42:58 +0100 Subject: [PATCH 220/254] Improve vector comparison `==` `!=` for horizontal reduction --- src/llvm_backend_expr.cpp | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 55b76b93a..1894e85f6 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2585,16 +2585,35 @@ lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue ri } GB_ASSERT_MSG(mask != nullptr, "Unhandled comparison kind %s (%s) %.*s %s (%s)", type_to_string(left.type), type_to_string(base_type(left.type)), LIT(token_strings[op_kind]), type_to_string(right.type), type_to_string(base_type(right.type))); - // TODO(bill): is this a good approach to dealing with comparisons of vectors? - char const *name = "llvm.vector.reduce.umax"; - LLVMTypeRef types[1] = {LLVMTypeOf(mask)}; - unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name)); - GB_ASSERT_MSG(id != 0, "Unable to find %s.%s", name, LLVMPrintTypeToString(types[0])); - LLVMValueRef ip = LLVMGetIntrinsicDeclaration(p->module->mod, id, types, gb_count_of(types)); + /* NOTE(bill, 2022-05-28): + Thanks to Per Vognsen, sign extending to + a vector of the same width as the input vector, bit casting to an integer, + and then comparing against zero is the better option + See: https://lists.llvm.org/pipermail/llvm-dev/2012-September/053046.html - LLVMValueRef args[1] = {}; - args[0] = mask; - res.value = LLVMBuildCall(p->builder, ip, args, gb_count_of(args), ""); + // Example assuming 128-bit vector + + %1 = <4 x float> ... + %2 = <4 x float> ... + %3 = fcmp oeq <4 x float> %1, %2 + %4 = sext <4 x i1> %3 to <4 x i32> + %5 = bitcast <4 x i32> %4 to i128 + %6 = icmp ne i128 %5, 0 + br i1 %6, label %true1, label %false2 + + This will result in 1 cmpps + 1 ptest + 1 br + (even without SSE4.1, contrary to what the mail list states, because of pmovmskb) + + */ + + unsigned count = cast(unsigned)get_array_type_count(a); + unsigned elem_sz = cast(unsigned)(type_size_of(elem)*8); + LLVMTypeRef mask_type = LLVMVectorType(LLVMIntTypeInContext(p->module->ctx, elem_sz), count); + mask = LLVMBuildSExtOrBitCast(p->builder, mask, mask_type, ""); + + LLVMTypeRef mask_int_type = LLVMIntTypeInContext(p->module->ctx, cast(unsigned)(8*type_size_of(a))); + LLVMValueRef mask_int = LLVMBuildBitCast(p->builder, mask, mask_int_type, ""); + res.value = LLVMBuildICmp(p->builder, LLVMIntNE, mask_int, LLVMConstNull(LLVMTypeOf(mask_int)), ""); return res; } else { From bb4329711ce8edaa55e90290a1a5a7756cba7fd5 Mon Sep 17 00:00:00 2001 From: William Roe Date: Sat, 28 May 2022 15:21:07 +0100 Subject: [PATCH 221/254] [os] Darwin: Add os.exists() --- core/os/os_darwin.odin | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index 41dc8345c..b5e67558c 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -473,6 +473,11 @@ is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { is_file :: proc {is_file_path, is_file_handle} is_dir :: proc {is_dir_path, is_dir_handle} +exists :: proc(path: string) -> bool { + cpath := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_access(cpath, O_RDONLY) + return res == 0 +} rename :: proc(old: string, new: string) -> bool { old_cstr := strings.clone_to_cstring(old, context.temp_allocator) From d7eaf0f87b7677e84bf1f65c34305801748c39ee Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 28 May 2022 15:41:11 +0100 Subject: [PATCH 222/254] Add `intrinsics.x86_cpuid` and `intrinsics.x86_xgetbv` --- core/intrinsics/intrinsics.odin | 4 +++ core/simd/x86/ssse3.odin | 3 +- src/build_settings.cpp | 9 +++++ src/check_builtin.cpp | 60 ++++++++++++++++++++++++++++++- src/checker_builtin_procs.hpp | 6 ++++ src/llvm_backend_proc.cpp | 63 +++++++++++++++++++++++---------- 6 files changed, 124 insertions(+), 21 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 8becd998d..89f9a5f20 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -278,6 +278,10 @@ wasm_memory_size :: proc(index: uintptr) -> int --- wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -> u32 --- wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) --- +// x86 Targets (i386, amd64) +cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- +xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- + // Darwin targets only objc_object :: struct{} diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index 920dddd85..4abd4c84c 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -121,4 +121,5 @@ foreign _ { @(link_name = "llvm.x86.ssse3.psign.w.128") psignw128 :: proc(a, b: i16x8) -> i16x8 --- @(link_name = "llvm.x86.ssse3.psign.d.128") - psignd128 :: proc(a, b: i32x4) -> i32x4 ---} \ No newline at end of file + psignd128 :: proc(a, b: i32x4) -> i32x4 --- +} \ No newline at end of file diff --git a/src/build_settings.cpp b/src/build_settings.cpp index b458d8308..27e09a0db 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -629,6 +629,15 @@ bool is_arch_wasm(void) { return false; } +bool is_arch_x86(void) { + switch (build_context.metrics.arch) { + case TargetArch_i386: + case TargetArch_amd64: + return true; + } + return false; +} + bool allow_check_foreign_filepath(void) { switch (build_context.metrics.arch) { case TargetArch_wasm32: diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 9fa9cc590..f8ac545be 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1060,8 +1060,8 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call operand->type = t_untyped_integer; operand->mode = Addressing_Constant; operand->value = exact_value_i64(result); + return true; } - default: GB_PANIC("Unhandled simd intrinsic: %.*s", LIT(builtin_name)); } @@ -5275,6 +5275,64 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32 } break; + case BuiltinProc_x86_cpuid: + { + if (!is_arch_x86()) { + error(call, "'%.*s' is only allowed on x86 targets (i386, amd64)", LIT(builtin_name)); + return false; + } + + Operand ax = {}; + Operand cx = {}; + + check_expr_with_type_hint(c, &ax, ce->args[0], t_u32); if (ax.mode == Addressing_Invalid) return false; + check_expr_with_type_hint(c, &cx, ce->args[1], t_u32); if (cx.mode == Addressing_Invalid) return false; + convert_to_typed(c, &ax, t_u32); if (ax.mode == Addressing_Invalid) return false; + convert_to_typed(c, &cx, t_u32); if (cx.mode == Addressing_Invalid) return false; + if (!are_types_identical(ax.type, t_u32)) { + gbString str = type_to_string(ax.type); + error(ax.expr, "'%.*s' expected a u32, got %s", LIT(builtin_name), str); + gb_string_free(str); + return false; + } + if (!are_types_identical(cx.type, t_u32)) { + gbString str = type_to_string(cx.type); + error(cx.expr, "'%.*s' expected a u32, got %s", LIT(builtin_name), str); + gb_string_free(str); + return false; + } + Type *types[4] = {t_u32, t_u32, t_u32, t_u32}; // eax ebc ecx edx + operand->type = alloc_type_tuple_from_field_types(types, gb_count_of(types), false, false); + operand->mode = Addressing_Value; + operand->value = {}; + return true; + } + break; + case BuiltinProc_x86_xgetbv: + { + if (!is_arch_x86()) { + error(call, "'%.*s' is only allowed on x86 targets (i386, amd64)", LIT(builtin_name)); + return false; + } + + Operand cx = {}; + check_expr_with_type_hint(c, &cx, ce->args[0], t_u32); if (cx.mode == Addressing_Invalid) return false; + convert_to_typed(c, &cx, t_u32); if (cx.mode == Addressing_Invalid) return false; + if (!are_types_identical(cx.type, t_u32)) { + gbString str = type_to_string(cx.type); + error(cx.expr, "'%.*s' expected a u32, got %s", LIT(builtin_name), str); + gb_string_free(str); + return false; + } + + Type *types[2] = {t_u32, t_u32}; + operand->type = alloc_type_tuple_from_field_types(types, gb_count_of(types), false, false); + operand->mode = Addressing_Value; + operand->value = {}; + return true; + } + break; + } return true; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 1d7ee0a34..35f14c6a8 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -187,6 +187,9 @@ BuiltinProc__simd_end, // Platform specific intrinsics BuiltinProc_syscall, + BuiltinProc_x86_cpuid, + BuiltinProc_x86_xgetbv, + // Constant type tests BuiltinProc__type_begin, @@ -470,10 +473,13 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_x86__MM_SHUFFLE"), 4, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("x86_cpuid"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("x86_xgetbv"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 4d0df2861..8cbb533bc 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1007,9 +1007,9 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const lbValue res = {}; res.type = tv.type; - lbValue arg0 = lb_build_expr(p, ce->args[0]); - lbValue arg1 = {}; - lbValue arg2 = {}; + lbValue arg0 = {}; if (ce->args.count > 0) arg0 = lb_build_expr(p, ce->args[0]); + lbValue arg1 = {}; if (ce->args.count > 1) arg0 = lb_build_expr(p, ce->args[1]); + lbValue arg2 = {}; if (ce->args.count > 2) arg0 = lb_build_expr(p, ce->args[2]); Type *elem = base_array_type(arg0.type); @@ -1024,7 +1024,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_mul: case BuiltinProc_simd_div: case BuiltinProc_simd_rem: - arg1 = lb_build_expr(p, ce->args[1]); if (is_float) { switch (builtin_id) { case BuiltinProc_simd_add: op_code = LLVMFAdd; break; @@ -1062,7 +1061,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_shr: // Odin logic case BuiltinProc_simd_shl_masked: // C logic case BuiltinProc_simd_shr_masked: // C logic - arg1 = lb_build_expr(p, ce->args[1]); { i64 sz = type_size_of(elem); GB_ASSERT(arg0.type->kind == Type_SimdVector); @@ -1098,7 +1096,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_or: case BuiltinProc_simd_xor: case BuiltinProc_simd_and_not: - arg1 = lb_build_expr(p, ce->args[1]); switch (builtin_id) { case BuiltinProc_simd_and: op_code = LLVMAnd; break; case BuiltinProc_simd_or: op_code = LLVMOr; break; @@ -1143,7 +1140,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const } return res; case BuiltinProc_simd_max: - arg1 = lb_build_expr(p, ce->args[1]); if (is_float) { LLVMValueRef cond = LLVMBuildFCmp(p->builder, LLVMRealOGT, arg0.value, arg1.value, ""); res.value = LLVMBuildSelect(p->builder, cond, arg0.value, arg1.value, ""); @@ -1158,7 +1154,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_lanes_le: case BuiltinProc_simd_lanes_gt: case BuiltinProc_simd_lanes_ge: - arg1 = lb_build_expr(p, ce->args[1]); if (is_float) { LLVMRealPredicate pred = cast(LLVMRealPredicate)0; switch (builtin_id) { @@ -1193,12 +1188,9 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const break; case BuiltinProc_simd_extract: - arg1 = lb_build_expr(p, ce->args[1]); res.value = LLVMBuildExtractElement(p->builder, arg0.value, arg1.value, ""); return res; case BuiltinProc_simd_replace: - arg1 = lb_build_expr(p, ce->args[1]); - arg2 = lb_build_expr(p, ce->args[2]); res.value = LLVMBuildInsertElement(p->builder, arg0.value, arg2.value, arg1.value, ""); return res; @@ -1283,12 +1275,9 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_shuffle: { - arg1 = lb_build_expr(p, ce->args[1]); - Type *vt = arg0.type; GB_ASSERT(vt->kind == Type_SimdVector); - i64 indices_count = ce->args.count-2; i64 max_count = vt->SimdVector.count*2; GB_ASSERT(indices_count <= max_count); @@ -1394,8 +1383,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_add_sat: case BuiltinProc_simd_sub_sat: { - arg1 = lb_build_expr(p, ce->args[1]); - char const *name = nullptr; switch (builtin_id) { case BuiltinProc_simd_add_sat: name = is_signed ? "llvm.sadd.sat" : "llvm.uadd.sat"; break; @@ -1417,9 +1404,6 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const case BuiltinProc_simd_clamp: { - arg1 = lb_build_expr(p, ce->args[1]); - arg2 = lb_build_expr(p, ce->args[2]); - LLVMValueRef v = arg0.value; LLVMValueRef min = arg1.value; LLVMValueRef max = arg2.value; @@ -2737,6 +2721,47 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, return res; } + + case BuiltinProc_x86_cpuid: + { + Type *param_types[2] = {t_u32, t_u32}; + Type *type = alloc_type_proc_from_types(param_types, gb_count_of(param_types), tv.type, false, ProcCC_None); + LLVMTypeRef func_type = LLVMGetElementType(lb_type(p->module, type)); + LLVMValueRef the_asm = llvm_get_inline_asm( + func_type, + str_lit("cpuid"), + str_lit("={ax},={bx},={cx},={dx},{ax},{cx}"), + true + ); + GB_ASSERT(the_asm != nullptr); + + LLVMValueRef args[2] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_u32).value; + args[1] = lb_emit_conv(p, lb_build_expr(p, ce->args[1]), t_u32).value; + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall2(p->builder, func_type, the_asm, args, gb_count_of(args), ""); + return res; + } + case BuiltinProc_x86_xgetbv: + { + Type *type = alloc_type_proc_from_types(&t_u32, 1, tv.type, false, ProcCC_None); + LLVMTypeRef func_type = LLVMGetElementType(lb_type(p->module, type)); + LLVMValueRef the_asm = llvm_get_inline_asm( + func_type, + str_lit("xgetbv"), + str_lit("={ax},={dx},{cx}"), + true + ); + GB_ASSERT(the_asm != nullptr); + + LLVMValueRef args[1] = {}; + args[0] = lb_emit_conv(p, lb_build_expr(p, ce->args[0]), t_u32).value; + lbValue res = {}; + res.type = tv.type; + res.value = LLVMBuildCall2(p->builder, func_type, the_asm, args, gb_count_of(args), ""); + return res; + } } GB_PANIC("Unhandled built-in procedure %.*s", LIT(builtin_procs[id].name)); From c60d7842cd829371129f52064dd8722516e5d197 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 28 May 2022 15:41:27 +0100 Subject: [PATCH 223/254] Remove old code --- core/sys/cpu/cpu.odin | 33 ------------------- core/sys/cpu/cpu_x86.odin | 67 --------------------------------------- 2 files changed, 100 deletions(-) delete mode 100644 core/sys/cpu/cpu.odin delete mode 100644 core/sys/cpu/cpu_x86.odin diff --git a/core/sys/cpu/cpu.odin b/core/sys/cpu/cpu.odin deleted file mode 100644 index b99fe01d8..000000000 --- a/core/sys/cpu/cpu.odin +++ /dev/null @@ -1,33 +0,0 @@ -package sys_cpu - -Cache_Line_Pad :: struct {_: [_cache_line_size]byte}; - -initialized: bool; - -x86: struct { - _: Cache_Line_Pad, - has_aes: bool, // AES hardware implementation (AES NI) - has_adx: bool, // Multi-precision add-carry instruction extensions - has_avx: bool, // Advanced vector extension - has_avx2: bool, // Advanced vector extension 2 - has_bmi1: bool, // Bit manipulation instruction set 1 - has_bmi2: bool, // Bit manipulation instruction set 2 - has_erms: bool, // Enhanced REP for MOVSB and STOSB - has_fma: bool, // Fused-multiply-add instructions - has_os_xsave: bool, // OS supports XSAVE/XRESTOR for saving/restoring XMM registers. - has_pclmulqdq: bool, // PCLMULQDQ instruction - most often used for AES-GCM - has_popcnt: bool, // Hamming weight instruction POPCNT. - has_rdrand: bool, // RDRAND instruction (on-chip random number generator) - has_rdseed: bool, // RDSEED instruction (on-chip random number generator) - has_sse2: bool, // Streaming SIMD extension 2 (always available on amd64) - has_sse3: bool, // Streaming SIMD extension 3 - has_ssse3: bool, // Supplemental streaming SIMD extension 3 - has_sse41: bool, // Streaming SIMD extension 4 and 4.1 - has_sse42: bool, // Streaming SIMD extension 4 and 4.2 - _: Cache_Line_Pad, -}; - - -init :: proc() { - _init(); -} diff --git a/core/sys/cpu/cpu_x86.odin b/core/sys/cpu/cpu_x86.odin deleted file mode 100644 index 146822e61..000000000 --- a/core/sys/cpu/cpu_x86.odin +++ /dev/null @@ -1,67 +0,0 @@ -//+build i386, amd64 -package sys_cpu - -_cache_line_size :: 64; - -cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) { - return expand_to_tuple(asm(u32, u32) -> struct{eax, ebc, ecx, edx: u32} { - "cpuid", - "={ax},={bx},={cx},={dx},{ax},{cx}", - }(ax, cx)); -} - -xgetbv :: proc() -> (eax, edx: u32) { - return expand_to_tuple(asm(u32) -> struct{eax, edx: u32} { - "xgetbv", - "={ax},={dx},{cx}", - }(0)); -} - -_init :: proc() { - is_set :: proc(hwc: u32, value: u32) -> bool { - return hwc&value != 0; - } - - initialized = true; - - max_id, _, _, _ := cpuid(0, 0); - - if max_id < 1 { - return; - } - - _, _, ecx1, edx1 := cpuid(1, 0); - - x86.has_sse2 = is_set(26, edx1); - - x86.has_sse3 = is_set(0, ecx1); - x86.has_pclmulqdq = is_set(1, ecx1); - x86.has_ssse3 = is_set(9, ecx1); - x86.has_fma = is_set(12, ecx1); - x86.has_sse41 = is_set(19, ecx1); - x86.has_sse42 = is_set(20, ecx1); - x86.has_popcnt = is_set(23, ecx1); - x86.has_aes = is_set(25, ecx1); - x86.has_os_xsave = is_set(27, ecx1); - x86.has_rdrand = is_set(30, ecx1); - - os_supports_avx := false; - if x86.has_os_xsave { - eax, _ := xgetbv(); - os_supports_avx = is_set(1, eax) && is_set(2, eax); - } - - x86.has_avx = is_set(28, ecx1) && os_supports_avx; - - if max_id < 7 { - return; - } - - _, ebx7, _, _ := cpuid(7, 0); - x86.has_bmi1 = is_set(3, ebx7); - x86.has_avx2 = is_set(5, ebx7) && os_supports_avx; - x86.has_bmi2 = is_set(8, ebx7); - x86.has_erms = is_set(9, ebx7); - x86.has_rdseed = is_set(18, ebx7); - x86.has_adx = is_set(19, ebx7); -} From 910799cc5ff8032c1ff12709fced65d98b37902d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 28 May 2022 15:54:41 +0100 Subject: [PATCH 224/254] Add `cpu_features` for `core:simd/x86` --- core/simd/x86/cpu.odin | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 core/simd/x86/cpu.odin diff --git a/core/simd/x86/cpu.odin b/core/simd/x86/cpu.odin new file mode 100644 index 000000000..14e90c0f0 --- /dev/null +++ b/core/simd/x86/cpu.odin @@ -0,0 +1,94 @@ +//+build i386, amd64 +package simd_x86 + +import "core:intrinsics" + +// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- +cpuid :: intrinsics.x86_cpuid + +// xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- +xgetbv :: intrinsics.x86_xgetbv + + +CPU_Feature :: enum u64 { + aes, // AES hardware implementation (AES NI) + adx, // Multi-precision add-carry instruction extensions + avx, // Advanced vector extension + avx2, // Advanced vector extension 2 + bmi1, // Bit manipulation instruction set 1 + bmi2, // Bit manipulation instruction set 2 + erms, // Enhanced REP for MOVSB and STOSB + fma, // Fused-multiply-add instructions + os_xsave, // OS supports XSAVE/XRESTOR for saving/restoring XMM registers. + pclmulqdq, // PCLMULQDQ instruction - most often used for AES-GCM + popcnt, // Hamming weight instruction POPCNT. + rdrand, // RDRAND instruction (on-chip random number generator) + rdseed, // RDSEED instruction (on-chip random number generator) + sse2, // Streaming SIMD extension 2 (always available on amd64) + sse3, // Streaming SIMD extension 3 + ssse3, // Supplemental streaming SIMD extension 3 + sse41, // Streaming SIMD extension 4 and 4.1 + sse42, // Streaming SIMD extension 4 and 4.2 +} + +CPU_Features :: distinct bit_set[CPU_Feature; u64] + +cpu_features: Maybe(CPU_Features) + +@(init, private) +init_cpu_features :: proc "c" () { + is_set :: #force_inline proc "c" (hwc: u32, value: u32) -> bool { + return hwc&value != 0 + } + try_set :: #force_inline proc "c" (set: ^CPU_Features, feature: CPU_Feature, hwc: u32, value: u32) { + if is_set(hwc, value) { + set^ += {feature} + } + } + + max_id, _, _, _ := cpuid(0, 0) + if max_id < 1 { + return + } + + set: CPU_Features + + _, _, ecx1, edx1 := cpuid(1, 0) + + try_set(&set, .sse2, 26, edx1) + try_set(&set, .sse3, 0, ecx1) + try_set(&set, .pclmulqdq, 1, ecx1) + try_set(&set, .ssse3, 9, ecx1) + try_set(&set, .fma, 12, ecx1) + try_set(&set, .sse41, 19, ecx1) + try_set(&set, .sse42, 20, ecx1) + try_set(&set, .popcnt, 23, ecx1) + try_set(&set, .aes, 25, ecx1) + try_set(&set, .os_xsave, 27, ecx1) + try_set(&set, .rdrand, 30, ecx1) + + os_supports_avx := false + if .os_xsave in set { + eax, _ := xgetbv(0) + os_supports_avx = is_set(1, eax) && is_set(2, eax) + } + if os_supports_avx { + try_set(&set, .avx, 28, ecx1) + } + + if max_id < 7 { + return + } + + _, ebx7, _, _ := cpuid(7, 0) + try_set(&set, .bmi1, 3, ebx7) + if os_supports_avx { + try_set(&set, .avx2, 5, ebx7) + } + try_set(&set, .bmi2, 8, ebx7) + try_set(&set, .erms, 9, ebx7) + try_set(&set, .rdseed, 18, ebx7) + try_set(&set, .adx, 19, ebx7) + + cpu_features = set +} From 3ad2cde833dc7b8ad83185f251f1ee544b4e3ef8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 13:34:59 +0100 Subject: [PATCH 225/254] Add amd64 specific instructions --- core/simd/x86/sse.odin | 21 ++++++++++++++++++++ core/simd/x86/sse2.odin | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index e697d5f5e..eb1950ea3 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -417,6 +417,18 @@ _mm_stream_ps :: #force_inline proc "c" (addr: [^]f32, a: __m128) { intrinsics.non_temporal_store((^__m128)(addr), a) } +when ODIN_ARCH == .amd64 { + _mm_cvtss_si64 :: #force_inline proc "c"(a: __m128) -> i64 { + return cvtss2si64(a) + } + _mm_cvttss_si64 :: #force_inline proc "c"(a: __m128) -> i64 { + return cvttss2si64(a) + } + _mm_cvtsi64_ss :: #force_inline proc "c"(a: __m128, b: i64) -> __m128 { + return cvtsi642ss(a, b) + } +} + @(default_calling_convention="c") @(private) @@ -493,4 +505,13 @@ foreign _ { prefetch :: proc(p: rawptr, #const rw, loc, ty: u32) --- @(link_name="llvm.x86.sse.cmp.ss") cmpss :: proc(a, b: __m128, #const imm8: u8) -> __m128 --- + + + // amd64 only + @(link_name="llvm.x86.sse.cvtss2si64") + cvtss2si64 :: proc(a: __m128) -> i64 --- + @(link_name="llvm.x86.sse.cvttss2si64") + cvttss2si64 :: proc(a: __m128) -> i64 --- + @(link_name="llvm.x86.sse.cvtsi642ss") + cvtsi642ss :: proc(a: __m128, b: i64) -> __m128 --- } diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index a47d2a09d..8be1815fa 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -780,6 +780,43 @@ _mm_unpacklo_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { } +when ODIN_ARCH == .amd64 { + _mm_cvtsd_si64 :: #force_inline proc "c" (a: __m128d) -> i64 { + return cvtsd2si64(a) + } + _mm_cvtsd_si64x :: #force_inline proc "c" (a: __m128d) -> i64 { + return _mm_cvtsd_si64(a) + } + _mm_cvttsd_si64 :: #force_inline proc "c" (a: __m128d) -> i64 { + return cvttsd2si64(a) + } + _mm_cvttsd_si64x :: #force_inline proc "c" (a: __m128d) -> i64 { + return _mm_cvttsd_si64(a) + } + _mm_stream_si64 :: #force_inline proc "c" (mem_addr: ^i64, a: i64) { + intrinsics.non_temporal_store(mem_addr, a) + } + _mm_cvtsi64_si128 :: #force_inline proc "c" (a: i64) -> __m128i { + return _mm_set_epi64x(0, a) + } + _mm_cvtsi64x_si128 :: #force_inline proc "c" (a: i64) -> __m128i { + return _mm_cvtsi64_si128(a) + } + _mm_cvtsi128_si64 :: #force_inline proc "c" (a: __m128i) -> i64 { + return simd.extract(transmute(i64x2)a, 0) + } + _mm_cvtsi128_si64x :: #force_inline proc "c" (a: __m128i) -> i64 { + return _mm_cvtsi128_si64(a) + } + _mm_cvtsi64_sd :: #force_inline proc "c" (a: __m128d, b: i64) -> __m128d { + return simd.replace(a, 0, f64(b)) + } + _mm_cvtsi64x_sd :: #force_inline proc "c" (a: __m128d, b: i64) -> __m128d { + return _mm_cvtsi64_sd(a, b) + } +} + + @(default_calling_convention="c") @(private) foreign _ { @@ -923,4 +960,10 @@ foreign _ { storeudq :: proc(mem_addr: rawptr, a: __m128i) --- @(link_name="llvm.x86.sse2.storeu.pd") storeupd :: proc(mem_addr: rawptr, a: __m128d) --- + + // amd64 only + @(link_name="llvm.x86.sse2.cvtsd2si64") + cvtsd2si64 :: proc(a: __m128d) -> i64 --- + @(link_name="llvm.x86.sse2.cvttsd2si64") + cvttsd2si64 :: proc(a: __m128d) -> i64 --- } From 7f3540b7f541b75b1c4b4db558636d8295297c3f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 13:36:55 +0100 Subject: [PATCH 226/254] Add abm.odin --- core/simd/x86/abm.odin | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 core/simd/x86/abm.odin diff --git a/core/simd/x86/abm.odin b/core/simd/x86/abm.odin new file mode 100644 index 000000000..f1898811f --- /dev/null +++ b/core/simd/x86/abm.odin @@ -0,0 +1,20 @@ +//+build i386, amd64 +package simd_x86 + +import "core:intrinsics" + +_lzcnt_u32 :: #force_inline proc "c" (x: u32) -> u32 { + return intrinsics.count_leading_zeros(x) +} +_popcnt32 :: #force_inline proc "c" (x: u32) -> i32 { + return i32(intrinsics.count_ones(x)) +} + +when ODIN_ARCH == .amd64 { + _lzcnt_u64 :: #force_inline proc "c" (x: u64) -> u64 { + return intrinsics.count_leading_zeros(x) + } + _popcnt64 :: #force_inline proc "c" (x: u64) -> i32 { + return i32(intrinsics.count_ones(x)) + } +} \ No newline at end of file From 77d4409549d27b904676af5f33d8d034c6cae53e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 13:40:16 +0100 Subject: [PATCH 227/254] Add adx.odin --- core/simd/x86/adx.odin | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 core/simd/x86/adx.odin diff --git a/core/simd/x86/adx.odin b/core/simd/x86/adx.odin new file mode 100644 index 000000000..4de4324a2 --- /dev/null +++ b/core/simd/x86/adx.odin @@ -0,0 +1,53 @@ +//+build i386, amd64 +package simd_x86 + +import "core:intrinsics" + +_addcarry_u32 :: #force_inline proc "c" (c_in: u8, a: u32, b: u32, out: ^u32) -> u8 { + x, y := llvm_addcarry_u32(c_in, a, b) + out^ = y + return x +} +_addcarryx_u32 :: #force_inline proc "c" (c_in: u8, a: u32, b: u32, out: ^u32) -> u8 { + return llvm_addcarryx_u32(c_in, a, b, out) +} +_subborrow_u32 :: #force_inline proc "c" (c_in: u8, a: u32, b: u32, out: ^u32) -> u8 { + x, y := llvm_subborrow_u32(c_in, a, b) + out^ = y + return x +} + +when ODIN_ARCH == .amd64 { + _addcarry_u64 :: #force_inline proc "c" (c_in: u8, a: u64, b: u64, out: ^u64) -> u8 { + x, y := llvm_addcarry_u64(c_in, a, b) + out^ = y + return x + } + _addcarryx_u64 :: #force_inline proc "c" (c_in: u8, a: u64, b: u64, out: ^u64) -> u8 { + return llvm_addcarryx_u64(c_in, a, b, out) + } + _subborrow_u64 :: #force_inline proc "c" (c_in: u8, a: u64, b: u64, out: ^u64) -> u8 { + x, y := llvm_subborrow_u64(c_in, a, b) + out^ = y + return x + } +} + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name="llvm.x86.addcarry.32") + llvm_addcarry_u32 :: proc(a: u8, b: u32, c: u32) -> (u8, u32) --- + @(link_name="llvm.x86.addcarryx.u32") + llvm_addcarryx_u32 :: proc(a: u8, b: u32, c: u32, d: rawptr) -> u8 --- + @(link_name="llvm.x86.subborrow.32") + llvm_subborrow_u32 :: proc(a: u8, b: u32, c: u32) -> (u8, u32) --- + + // amd64 only + @(link_name="llvm.x86.addcarry.64") + llvm_addcarry_u64 :: proc(a: u8, b: u64, c: u64) -> (u8, u64) --- + @(link_name="llvm.x86.addcarryx.u64") + llvm_addcarryx_u64 :: proc(a: u8, b: u64, c: u64, d: rawptr) -> u8 --- + @(link_name="llvm.x86.subborrow.64") + llvm_subborrow_u64 :: proc(a: u8, b: u64, c: u64) -> (u8, u64) --- +} From 846f8377b2416a9f77618de00e50c7314a3b7322 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 13:44:00 +0100 Subject: [PATCH 228/254] Add fxsr.odin --- core/simd/x86/adx.odin | 2 -- core/simd/x86/fxsr.odin | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 core/simd/x86/fxsr.odin diff --git a/core/simd/x86/adx.odin b/core/simd/x86/adx.odin index 4de4324a2..13acf923d 100644 --- a/core/simd/x86/adx.odin +++ b/core/simd/x86/adx.odin @@ -1,8 +1,6 @@ //+build i386, amd64 package simd_x86 -import "core:intrinsics" - _addcarry_u32 :: #force_inline proc "c" (c_in: u8, a: u32, b: u32, out: ^u32) -> u8 { x, y := llvm_addcarry_u32(c_in, a, b) out^ = y diff --git a/core/simd/x86/fxsr.odin b/core/simd/x86/fxsr.odin new file mode 100644 index 000000000..24269391e --- /dev/null +++ b/core/simd/x86/fxsr.odin @@ -0,0 +1,33 @@ +//+build i386, amd64 +package simd_x86 + +_fxsave :: #force_inline proc "c" (mem_addr: rawptr) { + fxsave(mem_addr) +} +_fxrstor :: #force_inline proc "c" (mem_addr: rawptr) { + fxrstor(mem_addr) +} + +when ODIN_ARCH == .amd64 { + _fxsave64 :: #force_inline proc "c" (mem_addr: rawptr) { + fxsave64(mem_addr) + } + _fxrstor64 :: #force_inline proc "c" (mem_addr: rawptr) { + fxrstor64(mem_addr) + } +} + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name="llvm.x86.fxsave") + fxsave :: proc(p: rawptr) --- + @(link_name="llvm.x86.fxrstor") + fxrstor :: proc(p: rawptr) --- + + // amd64 only + @(link_name="llvm.x86.fxsave64") + fxsave64 :: proc(p: rawptr) --- + @(link_name="llvm.x86.fxrstor64") + fxrstor64 :: proc(p: rawptr) --- +} \ No newline at end of file From babfba5e8fd7be50e394d426be2db07c477e538d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 13:48:20 +0100 Subject: [PATCH 229/254] Add rdtsc.odin --- core/simd/x86/rdtsc.odin | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 core/simd/x86/rdtsc.odin diff --git a/core/simd/x86/rdtsc.odin b/core/simd/x86/rdtsc.odin new file mode 100644 index 000000000..0527fc084 --- /dev/null +++ b/core/simd/x86/rdtsc.odin @@ -0,0 +1,19 @@ +//+build i386, amd64 +package simd_x86 + +_rdtsc :: #force_inline proc "c" () -> u64 { + return rdtsc() +} + +__rdtscp :: #force_inline proc "c" (aux: ^u32) -> u64 { + return rdtscp(aux) +} + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name="llvm.x86.rdtsc") + rdtsc :: proc() -> u64 --- + @(link_name="llvm.x86.rdtscp") + rdtscp :: proc(aux: rawptr) -> u64 --- +} \ No newline at end of file From 136d50a745fb7b29a51cd3336d486e6c40c98dbb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 29 May 2022 14:48:44 +0200 Subject: [PATCH 230/254] Fix SDK detection if no SDK installed. --- src/microsoft_craziness.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index 5480f3bea..49936e6f5 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -707,8 +707,8 @@ bool find_msvc_install_from_env_vars(Find_Result_Utf8 *result) { isize lo = {0}; isize hi = {0}; - for (isize c = 0; c <= path.len; c += 1) { - if (c != path.len && path[c] != ';') { + for (isize c = 0; c < path.len; c += 1) { + if (path[c] != ';') { continue; } From 0ccbea17aa874b50dd943d83ff39c1e1270f4ea2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 13:50:54 +0100 Subject: [PATCH 231/254] Add pclmulqdq.odin --- core/simd/x86/pclmulqdq.odin | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 core/simd/x86/pclmulqdq.odin diff --git a/core/simd/x86/pclmulqdq.odin b/core/simd/x86/pclmulqdq.odin new file mode 100644 index 000000000..94496fb04 --- /dev/null +++ b/core/simd/x86/pclmulqdq.odin @@ -0,0 +1,13 @@ +//+build i386, amd64 +package simd_x86 + +_mm_clmulepi64_si128 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i { + return pclmulqdq(a, b, u8(IMM8)) +} + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name="llvm.x86.pclmulqdq") + pclmulqdq :: proc(a, round_key: __m128i, #const imm8: u8) -> __m128i --- +} \ No newline at end of file From f5e5eac3b9c8458eef17ffe062507fd0b531f0a0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 14:46:05 +0100 Subject: [PATCH 232/254] Add cmpxchg16b --- core/simd/x86/cmpxchg16b.odin | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 core/simd/x86/cmpxchg16b.odin diff --git a/core/simd/x86/cmpxchg16b.odin b/core/simd/x86/cmpxchg16b.odin new file mode 100644 index 000000000..d575dd9df --- /dev/null +++ b/core/simd/x86/cmpxchg16b.odin @@ -0,0 +1,8 @@ +//+build amd64 +package simd_x86 + +import "core:intrinsics" + +cmpxchg16b :: #force_inline proc "c" (dst: ^u128, old, new: u128, $success, $failure: intrinsics.Atomic_Memory_Order) -> (val: u128) { + return intrinsics.atomic_compare_exchange_strong_explicit(dst, old, new, success, failure) +} \ No newline at end of file From bc3bf939e0760daeba75e29a82d2c1e8811651bb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 14:56:25 +0100 Subject: [PATCH 233/254] Add sha.odin --- core/simd/x86/sha.odin | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 core/simd/x86/sha.odin diff --git a/core/simd/x86/sha.odin b/core/simd/x86/sha.odin new file mode 100644 index 000000000..c60293a8d --- /dev/null +++ b/core/simd/x86/sha.odin @@ -0,0 +1,43 @@ +//+build i386, amd64 +package simd_x86 + +_mm_sha1msg1_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)sha1msg1(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_sha1msg2_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)sha1msg2(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_sha1nexte_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)sha1nexte(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_sha1rnds4_epu32 :: #force_inline proc "c" (a, b: __m128i, $FUNC: u32) -> __m128i where 0 <= FUNC, FUNC <= 3 { + return transmute(__m128i)sha1rnds4(transmute(i32x4)a, transmute(i32x4)b, u8(FUNC & 0xff)) +} +_mm_sha256msg1_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)sha256msg1(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_sha256msg2_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)sha256msg2(transmute(i32x4)a, transmute(i32x4)b) +} +_mm_sha256rnds2_epu32 :: #force_inline proc "c" (a, b, k: __m128i) -> __m128i { + return transmute(__m128i)sha256rnds2(transmute(i32x4)a, transmute(i32x4)b, transmute(i32x4)k) +} + +@(default_calling_convention="c") +@(private) +foreign _ { + @(link_name="llvm.x86.sha1msg1") + sha1msg1 :: proc(a, b: i32x4) -> i32x4 --- + @(link_name="llvm.x86.sha1msg2") + sha1msg2 :: proc(a, b: i32x4) -> i32x4 --- + @(link_name="llvm.x86.sha1nexte") + sha1nexte :: proc(a, b: i32x4) -> i32x4 --- + @(link_name="llvm.x86.sha1rnds4") + sha1rnds4 :: proc(a, b: i32x4, #const c: u8) -> i32x4 --- + @(link_name="llvm.x86.sha256msg1") + sha256msg1 :: proc(a, b: i32x4) -> i32x4 --- + @(link_name="llvm.x86.sha256msg2") + sha256msg2 :: proc(a, b: i32x4) -> i32x4 --- + @(link_name="llvm.x86.sha256rnds2") + sha256rnds2 :: proc(a, b, k: i32x4) -> i32x4 --- +} \ No newline at end of file From f6dfa33697b0b5e3cb9b6b49214d8e02f26cb723 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 15:11:15 +0100 Subject: [PATCH 234/254] Use single line attributes --- core/simd/x86/adx.odin | 3 +-- core/simd/x86/fxsr.odin | 3 +-- core/simd/x86/pclmulqdq.odin | 3 +-- core/simd/x86/rdtsc.odin | 3 +-- core/simd/x86/sha.odin | 3 +-- core/simd/x86/sse.odin | 3 +-- core/simd/x86/sse2.odin | 3 +-- core/simd/x86/sse3.odin | 3 +-- core/simd/x86/ssse3.odin | 3 +-- 9 files changed, 9 insertions(+), 18 deletions(-) diff --git a/core/simd/x86/adx.odin b/core/simd/x86/adx.odin index 13acf923d..e73aa03a6 100644 --- a/core/simd/x86/adx.odin +++ b/core/simd/x86/adx.odin @@ -31,8 +31,7 @@ when ODIN_ARCH == .amd64 { } } -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name="llvm.x86.addcarry.32") llvm_addcarry_u32 :: proc(a: u8, b: u32, c: u32) -> (u8, u32) --- diff --git a/core/simd/x86/fxsr.odin b/core/simd/x86/fxsr.odin index 24269391e..847678d29 100644 --- a/core/simd/x86/fxsr.odin +++ b/core/simd/x86/fxsr.odin @@ -17,8 +17,7 @@ when ODIN_ARCH == .amd64 { } } -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name="llvm.x86.fxsave") fxsave :: proc(p: rawptr) --- diff --git a/core/simd/x86/pclmulqdq.odin b/core/simd/x86/pclmulqdq.odin index 94496fb04..ba4ecf35f 100644 --- a/core/simd/x86/pclmulqdq.odin +++ b/core/simd/x86/pclmulqdq.odin @@ -5,8 +5,7 @@ _mm_clmulepi64_si128 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m return pclmulqdq(a, b, u8(IMM8)) } -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name="llvm.x86.pclmulqdq") pclmulqdq :: proc(a, round_key: __m128i, #const imm8: u8) -> __m128i --- diff --git a/core/simd/x86/rdtsc.odin b/core/simd/x86/rdtsc.odin index 0527fc084..91dcc4ec9 100644 --- a/core/simd/x86/rdtsc.odin +++ b/core/simd/x86/rdtsc.odin @@ -9,8 +9,7 @@ __rdtscp :: #force_inline proc "c" (aux: ^u32) -> u64 { return rdtscp(aux) } -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name="llvm.x86.rdtsc") rdtsc :: proc() -> u64 --- diff --git a/core/simd/x86/sha.odin b/core/simd/x86/sha.odin index c60293a8d..d907ce6a6 100644 --- a/core/simd/x86/sha.odin +++ b/core/simd/x86/sha.odin @@ -23,8 +23,7 @@ _mm_sha256rnds2_epu32 :: #force_inline proc "c" (a, b, k: __m128i) -> __m128i { return transmute(__m128i)sha256rnds2(transmute(i32x4)a, transmute(i32x4)b, transmute(i32x4)k) } -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name="llvm.x86.sha1msg1") sha1msg1 :: proc(a, b: i32x4) -> i32x4 --- diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index eb1950ea3..a564f243a 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -430,8 +430,7 @@ when ODIN_ARCH == .amd64 { } -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name="llvm.x86.sse.add.ss") addss :: proc(a, b: __m128) -> __m128 --- diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index 8be1815fa..cb2e61f46 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -817,8 +817,7 @@ when ODIN_ARCH == .amd64 { } -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name="llvm.x86.sse2.pause") pause :: proc() --- diff --git a/core/simd/x86/sse3.odin b/core/simd/x86/sse3.odin index 6468ea268..9766a43e6 100644 --- a/core/simd/x86/sse3.odin +++ b/core/simd/x86/sse3.odin @@ -38,8 +38,7 @@ _mm_moveldup_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return simd.shuffle(a, a, 0, 0, 2, 2) } -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name = "llvm.x86.sse3.addsub.ps") addsubps :: proc(a, b: __m128) -> __m128 --- diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index 4abd4c84c..6c6f28008 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -89,8 +89,7 @@ _mm_sign_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { -@(default_calling_convention="c") -@(private) +@(private, default_calling_convention="c") foreign _ { @(link_name = "llvm.x86.ssse3.pabs.b.128") pabsb128 :: proc(a: i8x16) -> u8x16 --- From cef022539ebd41a4a80707f1a702e09e6748ade0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 29 May 2022 15:13:14 +0100 Subject: [PATCH 235/254] Rename to `lanes_rotate_left`, `lanes_rotate_right`, `lanes_reverse` --- core/simd/simd.odin | 6 +++--- src/check_builtin.cpp | 6 +++--- src/checker_builtin_procs.hpp | 12 ++++++------ src/llvm_backend_proc.cpp | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index ed7e418f3..390ff377a 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -134,10 +134,10 @@ nearest :: intrinsics.simd_nearest to_bits :: intrinsics.simd_to_bits -lanes_reverse :: intrinsics.simd_reverse +lanes_reverse :: intrinsics.simd_lanes_reverse -lanes_rotate_left :: intrinsics.simd_rotate_left -lanes_rotate_right :: intrinsics.simd_rotate_right +lanes_rotate_left :: intrinsics.simd_lanes_rotate_left +lanes_rotate_right :: intrinsics.simd_lanes_rotate_right count_ones :: intrinsics.count_ones count_zeros :: intrinsics.count_zeros diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index f8ac545be..92e3987a0 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -919,7 +919,7 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } - case BuiltinProc_simd_reverse: + case BuiltinProc_simd_lanes_reverse: { Operand x = {}; check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; @@ -933,8 +933,8 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return true; } - case BuiltinProc_simd_rotate_left: - case BuiltinProc_simd_rotate_right: + case BuiltinProc_simd_lanes_rotate_left: + case BuiltinProc_simd_lanes_rotate_right: { Operand x = {}; check_expr(c, &x, ce->args[0]); if (x.mode == Addressing_Invalid) return false; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 35f14c6a8..2e27cc026 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -175,9 +175,9 @@ BuiltinProc__simd_begin, BuiltinProc_simd_to_bits, - BuiltinProc_simd_reverse, - BuiltinProc_simd_rotate_left, - BuiltinProc_simd_rotate_right, + BuiltinProc_simd_lanes_reverse, + BuiltinProc_simd_lanes_rotate_left, + BuiltinProc_simd_lanes_rotate_right, // Platform specific SIMD intrinsics @@ -468,9 +468,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("simd_to_bits"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_reverse"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_rotate_left"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("simd_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_reverse"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_rotate_left"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("simd_lanes_rotate_right"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("simd_x86__MM_SHUFFLE"), 4, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 8cbb533bc..1e3591bf1 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1330,7 +1330,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } - case BuiltinProc_simd_reverse: + case BuiltinProc_simd_lanes_reverse: { i64 count = get_array_type_count(arg0.type); LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, count); @@ -1345,8 +1345,8 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const return res; } - case BuiltinProc_simd_rotate_left: - case BuiltinProc_simd_rotate_right: + case BuiltinProc_simd_lanes_rotate_left: + case BuiltinProc_simd_lanes_rotate_right: { i64 count = get_array_type_count(arg0.type); @@ -1358,7 +1358,7 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const ExactValue val = exact_value_to_integer(tv.value); GB_ASSERT(val.kind == ExactValue_Integer); BigInt *bi = &val.value_integer; - if (builtin_id == BuiltinProc_simd_rotate_right) { + if (builtin_id == BuiltinProc_simd_lanes_rotate_right) { big_int_neg(bi, bi); } big_int_rem(bi, bi, &bi_count); From 9fa41a97b9c2d9894034b16750a6e7dd0cb196c5 Mon Sep 17 00:00:00 2001 From: hasen Date: Mon, 30 May 2022 22:27:13 +0900 Subject: [PATCH 236/254] Missing lib imports for raylib on macOS The following frameworks are required for linking to work (due to dependency on glfw): Cocoa, OpenGL, IOKit --- vendor/raylib/raylib.odin | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index ec057cd78..3fc3e051f 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -106,7 +106,12 @@ when ODIN_OS == .Windows { "system:pthread", } } else when ODIN_OS == .Darwin { - foreign import lib "macos/libraylib.a" + foreign import lib { + "macos/libraylib.a", + "system:Cocoa.framework", + "system:OpenGL.framework", + "system:IOKit.framework", + } } else { foreign import lib "system:raylib" } From f3aefbc4434b92fc3fda74c942c953b08dd18a62 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 14:53:12 +0100 Subject: [PATCH 237/254] `@(require_target_feature=)` `@(enable_target_feature=)` require_target_feature - required by the target micro-architecture enable_target_feature - will be enabled for the specified procedure only --- core/simd/x86/fxsr.odin | 4 + core/simd/x86/pclmulqdq.odin | 1 + core/simd/x86/sha.odin | 7 ++ core/simd/x86/sse.odin | 102 ++++++++++++++++ core/simd/x86/sse2.odin | 223 +++++++++++++++++++++++++++++++++++ core/simd/x86/sse3.odin | 11 ++ core/simd/x86/ssse3.odin | 16 +++ src/build_settings.cpp | 109 ++++++++++++++++- src/check_decl.cpp | 12 ++ src/checker.cpp | 16 +++ src/checker.hpp | 3 + src/entity.cpp | 8 +- src/llvm_backend.cpp | 4 +- src/llvm_backend_proc.cpp | 13 ++ src/main.cpp | 4 +- src/string.cpp | 9 ++ 16 files changed, 533 insertions(+), 9 deletions(-) diff --git a/core/simd/x86/fxsr.odin b/core/simd/x86/fxsr.odin index 847678d29..cd78de7d4 100644 --- a/core/simd/x86/fxsr.odin +++ b/core/simd/x86/fxsr.odin @@ -1,17 +1,21 @@ //+build i386, amd64 package simd_x86 +@(enable_target_feature="fxsr") _fxsave :: #force_inline proc "c" (mem_addr: rawptr) { fxsave(mem_addr) } +@(enable_target_feature="fxsr") _fxrstor :: #force_inline proc "c" (mem_addr: rawptr) { fxrstor(mem_addr) } when ODIN_ARCH == .amd64 { + @(enable_target_feature="fxsr") _fxsave64 :: #force_inline proc "c" (mem_addr: rawptr) { fxsave64(mem_addr) } + @(enable_target_feature="fxsr") _fxrstor64 :: #force_inline proc "c" (mem_addr: rawptr) { fxrstor64(mem_addr) } diff --git a/core/simd/x86/pclmulqdq.odin b/core/simd/x86/pclmulqdq.odin index ba4ecf35f..8a665db03 100644 --- a/core/simd/x86/pclmulqdq.odin +++ b/core/simd/x86/pclmulqdq.odin @@ -1,6 +1,7 @@ //+build i386, amd64 package simd_x86 +@(enable_target_feature="pclmulqdq") _mm_clmulepi64_si128 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i { return pclmulqdq(a, b, u8(IMM8)) } diff --git a/core/simd/x86/sha.odin b/core/simd/x86/sha.odin index d907ce6a6..90f1d72ce 100644 --- a/core/simd/x86/sha.odin +++ b/core/simd/x86/sha.odin @@ -1,24 +1,31 @@ //+build i386, amd64 package simd_x86 +@(enable_target_feature="sha") _mm_sha1msg1_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha1msg1(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sha") _mm_sha1msg2_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha1msg2(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sha") _mm_sha1nexte_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha1nexte(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sha") _mm_sha1rnds4_epu32 :: #force_inline proc "c" (a, b: __m128i, $FUNC: u32) -> __m128i where 0 <= FUNC, FUNC <= 3 { return transmute(__m128i)sha1rnds4(transmute(i32x4)a, transmute(i32x4)b, u8(FUNC & 0xff)) } +@(enable_target_feature="sha") _mm_sha256msg1_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha256msg1(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sha") _mm_sha256msg2_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha256msg2(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sha") _mm_sha256rnds2_epu32 :: #force_inline proc "c" (a, b, k: __m128i) -> __m128i { return transmute(__m128i)sha256rnds2(transmute(i32x4)a, transmute(i32x4)b, transmute(i32x4)k) } diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index a564f243a..6d8939b1b 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -43,232 +43,299 @@ _MM_FLUSH_ZERO_ON :: 0x8000 _MM_FLUSH_ZERO_OFF :: 0x0000 +@(enable_target_feature="sse") _mm_add_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return addss(a, b) } +@(enable_target_feature="sse") _mm_add_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.add(a, b) } +@(enable_target_feature="sse") _mm_sub_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return subss(a, b) } +@(enable_target_feature="sse") _mm_sub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.sub(a, b) } +@(enable_target_feature="sse") _mm_mul_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return mulss(a, b) } +@(enable_target_feature="sse") _mm_mul_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.mul(a, b) } +@(enable_target_feature="sse") _mm_div_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return divss(a, b) } +@(enable_target_feature="sse") _mm_div_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.div(a, b) } +@(enable_target_feature="sse") _mm_sqrt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return sqrtss(a) } +@(enable_target_feature="sse") _mm_sqrt_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return sqrtps(a) } +@(enable_target_feature="sse") _mm_rcp_ss :: #force_inline proc "c" (a: __m128) -> __m128 { return rcpss(a) } +@(enable_target_feature="sse") _mm_rcp_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return rcpps(a) } +@(enable_target_feature="sse") _mm_rsqrt_ss :: #force_inline proc "c" (a: __m128) -> __m128 { return rsqrtss(a) } +@(enable_target_feature="sse") _mm_rsqrt_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return rsqrtps(a) } +@(enable_target_feature="sse") _mm_min_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return minss(a, b) } +@(enable_target_feature="sse") _mm_min_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return minps(a, b) } +@(enable_target_feature="sse") _mm_max_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return maxss(a, b) } +@(enable_target_feature="sse") _mm_max_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return maxps(a, b) } +@(enable_target_feature="sse") _mm_and_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return transmute(__m128)simd.and(transmute(__m128i)a, transmute(__m128i)b) } +@(enable_target_feature="sse") _mm_andnot_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return transmute(__m128)simd.and_not(transmute(__m128i)a, transmute(__m128i)b) } +@(enable_target_feature="sse") _mm_or_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return transmute(__m128)simd.or(transmute(__m128i)a, transmute(__m128i)b) } +@(enable_target_feature="sse") _mm_xor_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return transmute(__m128)simd.xor(transmute(__m128i)a, transmute(__m128i)b) } +@(enable_target_feature="sse") _mm_cmpeq_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 0) } +@(enable_target_feature="sse") _mm_cmplt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 1) } +@(enable_target_feature="sse") _mm_cmple_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 2) } +@(enable_target_feature="sse") _mm_cmpgt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, cmpss(b, a, 1), 4, 1, 2, 3) } +@(enable_target_feature="sse") _mm_cmpge_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, cmpss(b, a, 2), 4, 1, 2, 3) } +@(enable_target_feature="sse") _mm_cmpneq_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 4) } +@(enable_target_feature="sse") _mm_cmpnlt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 5) } +@(enable_target_feature="sse") _mm_cmpnle_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 6) } +@(enable_target_feature="sse") _mm_cmpngt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, cmpss(b, a, 5), 4, 1, 2, 3) } +@(enable_target_feature="sse") _mm_cmpnge_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, cmpss(b, a, 6), 4, 1, 2, 3) } +@(enable_target_feature="sse") _mm_cmpord_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 7) } +@(enable_target_feature="sse") _mm_cmpunord_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 3) } +@(enable_target_feature="sse") _mm_cmpeq_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 0) } +@(enable_target_feature="sse") _mm_cmplt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 1) } +@(enable_target_feature="sse") _mm_cmple_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 2) } +@(enable_target_feature="sse") _mm_cmpgt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 1) } +@(enable_target_feature="sse") _mm_cmpge_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 2) } +@(enable_target_feature="sse") _mm_cmpneq_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 4) } +@(enable_target_feature="sse") _mm_cmpnlt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 5) } +@(enable_target_feature="sse") _mm_cmpnle_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 6) } +@(enable_target_feature="sse") _mm_cmpngt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 5) } +@(enable_target_feature="sse") _mm_cmpnge_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 6) } +@(enable_target_feature="sse") _mm_cmpord_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 7) } +@(enable_target_feature="sse") _mm_cmpunord_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 3) } +@(enable_target_feature="sse") _mm_comieq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comieq_ss(a, b) } +@(enable_target_feature="sse") _mm_comilt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comilt_ss(a, b) } +@(enable_target_feature="sse") _mm_comile_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comile_ss(a, b) } +@(enable_target_feature="sse") _mm_comigt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comigt_ss(a, b) } +@(enable_target_feature="sse") _mm_comige_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comige_ss(a, b) } +@(enable_target_feature="sse") _mm_comineq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comineq_ss(a, b) } +@(enable_target_feature="sse") _mm_ucomieq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomieq_ss(a, b) } +@(enable_target_feature="sse") _mm_ucomilt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomilt_ss(a, b) } +@(enable_target_feature="sse") _mm_ucomile_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomile_ss(a, b) } +@(enable_target_feature="sse") _mm_ucomigt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomigt_ss(a, b) } +@(enable_target_feature="sse") _mm_ucomige_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomige_ss(a, b) } +@(enable_target_feature="sse") _mm_ucomineq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomineq_ss(a, b) } +@(enable_target_feature="sse") _mm_cvtss_si32 :: #force_inline proc "c" (a: __m128) -> i32 { return cvtss2si(a) } _mm_cvt_ss2si :: _mm_cvtss_si32 _mm_cvttss_si32 :: _mm_cvtss_si32 +@(enable_target_feature="sse") _mm_cvtss_f32 :: #force_inline proc "c" (a: __m128) -> f32 { return simd.extract(a, 0) } +@(enable_target_feature="sse") _mm_cvtsi32_ss :: #force_inline proc "c" (a: __m128, b: i32) -> __m128 { return cvtsi2ss(a, b) } _mm_cvt_si2ss :: _mm_cvtsi32_ss +@(enable_target_feature="sse") _mm_set_ss :: #force_inline proc "c" (a: f32) -> __m128 { return __m128{a, 0, 0, 0} } +@(enable_target_feature="sse") _mm_set1_ps :: #force_inline proc "c" (a: f32) -> __m128 { return __m128(a) } _mm_set_ps1 :: _mm_set1_ps +@(enable_target_feature="sse") _mm_set_ps :: #force_inline proc "c" (a, b, c, d: f32) -> __m128 { return __m128{d, c, b, a} } +@(enable_target_feature="sse") _mm_setr_ps :: #force_inline proc "c" (a, b, c, d: f32) -> __m128 { return __m128{a, b, c, d} } +@(enable_target_feature="sse") _mm_setzero_ps :: #force_inline proc "c" () -> __m128 { return __m128{0, 0, 0, 0} } +@(enable_target_feature="sse") _mm_shuffle_ps :: #force_inline proc "c" (a, b: __m128, $MASK: u32) -> __m128 { return simd.shuffle( a, b, @@ -279,56 +346,69 @@ _mm_shuffle_ps :: #force_inline proc "c" (a, b: __m128, $MASK: u32) -> __m128 { } +@(enable_target_feature="sse") _mm_unpackhi_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 2, 6, 3, 7) } +@(enable_target_feature="sse") _mm_unpacklo_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 0, 4, 1, 5) } +@(enable_target_feature="sse") _mm_movehl_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 6, 7, 2, 3) } +@(enable_target_feature="sse") _mm_movelh_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 0, 1, 4, 5) } +@(enable_target_feature="sse") _mm_movemask_ps :: #force_inline proc "c" (a: __m128) -> u32 { return movmskps(a) } +@(enable_target_feature="sse") _mm_load_ss :: #force_inline proc "c" (p: ^f32) -> __m128 { return __m128{p^, 0, 0, 0} } +@(enable_target_feature="sse") _mm_load1_ps :: #force_inline proc "c" (p: ^f32) -> __m128 { a := p^ return __m128(a) } _mm_load_ps1 :: _mm_load1_ps +@(enable_target_feature="sse") _mm_load_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { return (^__m128)(p)^ } +@(enable_target_feature="sse") _mm_loadu_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { dst := _mm_undefined_ps() intrinsics.mem_copy_non_overlapping(&dst, p, size_of(__m128)) return dst } +@(enable_target_feature="sse") _mm_loadr_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { return simd.lanes_reverse(_mm_load_ps(p)) } +@(enable_target_feature="sse") _mm_loadu_si64 :: #force_inline proc "c" (mem_addr: rawptr) -> __m128i { a := intrinsics.unaligned_load((^i64)(mem_addr)) return __m128i{a, 0} } +@(enable_target_feature="sse") _mm_store_ss :: #force_inline proc "c" (p: ^f32, a: __m128) { p^ = simd.extract(a, 0) } +@(enable_target_feature="sse") _mm_store1_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { b := simd.swizzle(a, 0, 0, 0, 0) (^__m128)(p)^ = b @@ -336,71 +416,89 @@ _mm_store1_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { _mm_store_ps1 :: _mm_store1_ps +@(enable_target_feature="sse") _mm_store_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { (^__m128)(p)^ = a } +@(enable_target_feature="sse") _mm_storeu_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { b := a intrinsics.mem_copy_non_overlapping(p, &b, size_of(__m128)) } +@(enable_target_feature="sse") _mm_storer_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { (^__m128)(p)^ = simd.lanes_reverse(a) } +@(enable_target_feature="sse") _mm_move_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 4, 1, 2, 3) } +@(enable_target_feature="sse") _mm_sfence :: #force_inline proc "c" () { sfence() } +@(enable_target_feature="sse") _mm_getcsr :: #force_inline proc "c" () -> (result: u32) { stmxcsr(&result) return result } +@(enable_target_feature="sse") _mm_setcsr :: #force_inline proc "c" (val: u32) { val := val ldmxcsr(&val) } +@(enable_target_feature="sse") _MM_GET_EXCEPTION_MASK :: #force_inline proc "c" () -> u32 { return _mm_getcsr() & _MM_MASK_MASK } +@(enable_target_feature="sse") _MM_GET_EXCEPTION_STATE :: #force_inline proc "c" () -> u32 { return _mm_getcsr() & _MM_EXCEPT_MASK } +@(enable_target_feature="sse") _MM_GET_FLUSH_ZERO_MODE :: #force_inline proc "c" () -> u32 { return _mm_getcsr() & _MM_FLUSH_ZERO_MASK } +@(enable_target_feature="sse") _MM_GET_ROUNDING_MODE :: #force_inline proc "c" () -> u32 { return _mm_getcsr() & _MM_ROUND_MASK } +@(enable_target_feature="sse") _MM_SET_EXCEPTION_MASK :: #force_inline proc "c" (x: u32) { _mm_setcsr((_mm_getcsr() &~ _MM_MASK_MASK) | x) } +@(enable_target_feature="sse") _MM_SET_EXCEPTION_STATE :: #force_inline proc "c" (x: u32) { _mm_setcsr((_mm_getcsr() &~ _MM_EXCEPT_MASK) | x) } +@(enable_target_feature="sse") _MM_SET_FLUSH_ZERO_MODE :: #force_inline proc "c" (x: u32) { _mm_setcsr((_mm_getcsr() &~ _MM_FLUSH_ZERO_MASK) | x) } +@(enable_target_feature="sse") _MM_SET_ROUNDING_MODE :: #force_inline proc "c" (x: u32) { _mm_setcsr((_mm_getcsr() &~ _MM_ROUND_MASK) | x) } +@(enable_target_feature="sse") _mm_prefetch :: #force_inline proc "c" (p: rawptr, $STRATEGY: u32) { prefetch(p, (STRATEGY>>2)&1, STRATEGY&3, 1) } +@(enable_target_feature="sse") _mm_undefined_ps :: #force_inline proc "c" () -> __m128 { return _mm_set1_ps(0) } +@(enable_target_feature="sse") _MM_TRANSPOSE4_PS :: #force_inline proc "c" (row0, row1, row2, row3: ^__m128) { tmp0 := _mm_unpacklo_ps(row0^, row1^) tmp1 := _mm_unpacklo_ps(row2^, row3^) @@ -413,17 +511,21 @@ _MM_TRANSPOSE4_PS :: #force_inline proc "c" (row0, row1, row2, row3: ^__m128) { row3^ = _mm_movelh_ps(tmp3, tmp1) } +@(enable_target_feature="sse") _mm_stream_ps :: #force_inline proc "c" (addr: [^]f32, a: __m128) { intrinsics.non_temporal_store((^__m128)(addr), a) } when ODIN_ARCH == .amd64 { + @(enable_target_feature="sse") _mm_cvtss_si64 :: #force_inline proc "c"(a: __m128) -> i64 { return cvtss2si64(a) } + @(enable_target_feature="sse") _mm_cvttss_si64 :: #force_inline proc "c"(a: __m128) -> i64 { return cvttss2si64(a) } + @(enable_target_feature="sse") _mm_cvtsi64_ss :: #force_inline proc "c"(a: __m128, b: i64) -> __m128 { return cvtsi642ss(a, b) } diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index cb2e61f46..d15df8120 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -4,103 +4,135 @@ package simd_x86 import "core:intrinsics" import "core:simd" +@(enable_target_feature="sse2") _mm_pause :: #force_inline proc "c" () { pause() } +@(enable_target_feature="sse2") _mm_clflush :: #force_inline proc "c" (p: rawptr) { clflush(p) } +@(enable_target_feature="sse2") _mm_lfence :: #force_inline proc "c" () { lfence() } +@(enable_target_feature="sse2") _mm_mfence :: #force_inline proc "c" () { mfence() } +@(enable_target_feature="sse2") _mm_add_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add(transmute(i8x16)a, transmute(i8x16)b) } +@(enable_target_feature="sse2") _mm_add_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_add_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sse2") _mm_add_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add(transmute(i64x2)a, transmute(i64x2)b) } +@(enable_target_feature="sse2") _mm_adds_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add_sat(transmute(i8x16)a, transmute(i8x16)b) } +@(enable_target_feature="sse2") _mm_adds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add_sat(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_adds_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add_sat(transmute(u8x16)a, transmute(u8x16)b) } +@(enable_target_feature="sse2") _mm_adds_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add_sat(transmute(u16x8)a, transmute(u16x8)b) } +@(enable_target_feature="sse2") _mm_avg_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pavgb(transmute(u8x16)a, transmute(u8x16)b) } +@(enable_target_feature="sse2") _mm_avg_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pavgw(transmute(u16x8)a, transmute(u16x8)b) } +@(enable_target_feature="sse2") _mm_madd_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaddwd(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_max_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaxsw(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_max_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaxub(transmute(u8x16)a, transmute(u8x16)b) } +@(enable_target_feature="sse2") _mm_min_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pminsw(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_min_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pminub(transmute(u8x16)a, transmute(u8x16)b) } +@(enable_target_feature="sse2") _mm_mulhi_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmulhw(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_mulhi_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmulhuw(transmute(u16x8)a, transmute(u16x8)b) } +@(enable_target_feature="sse2") _mm_mullo_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.mul(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_mul_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmuludq(transmute(u32x4)a, transmute(u32x4)b) } +@(enable_target_feature="sse2") _mm_sad_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psadbw(transmute(u8x16)a, transmute(u8x16)b) } +@(enable_target_feature="sse2") _mm_sub_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i8x16)a, transmute(i8x16)b) } +@(enable_target_feature="sse2") _mm_sub_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_sub_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sse2") _mm_sub_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i64x2)a, transmute(i64x2)b) } +@(enable_target_feature="sse2") _mm_subs_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(i8x16)a, transmute(i8x16)b) } +@(enable_target_feature="sse2") _mm_subs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_subs_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(u8x16)a, transmute(u8x16)b) } +@(enable_target_feature="sse2") _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(u16x8)a, transmute(u16x8)b) } @@ -108,6 +140,7 @@ _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @(private) +@(enable_target_feature="sse2") _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 & 0xff @@ -134,6 +167,7 @@ _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128 } @(private) +@(enable_target_feature="sse2") _mm_srli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 return transmute(__m128i)simd.shuffle( @@ -159,203 +193,264 @@ _mm_srli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128 } +@(enable_target_feature="sse2") _mm_slli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_slli_si128_impl(a, IMM8) } +@(enable_target_feature="sse2") _mm_bslli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_slli_si128_impl(a, IMM8) } +@(enable_target_feature="sse2") _mm_bsrli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_srli_si128_impl(a, IMM8) } +@(enable_target_feature="sse2") _mm_slli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)pslliw(transmute(i16x8)a, IMM8) } +@(enable_target_feature="sse2") _mm_sll_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psllw(transmute(i16x8)a, transmute(i16x8)count) } +@(enable_target_feature="sse2") _mm_slli_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psllid(transmute(i32x4)a, IMM8) } +@(enable_target_feature="sse2") _mm_sll_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)pslld(transmute(i32x4)a, transmute(i32x4)count) } +@(enable_target_feature="sse2") _mm_slli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)pslliq(transmute(i64x2)a, IMM8) } +@(enable_target_feature="sse2") _mm_sll_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psllq(transmute(i64x2)a, transmute(i64x2)count) } +@(enable_target_feature="sse2") _mm_srai_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psraiw(transmute(i16x8)a. IMM8) } +@(enable_target_feature="sse2") _mm_sra_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psraw(transmute(i16x8)a, transmute(i16x8)count) } +@(enable_target_feature="sse2") _mm_srai_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psraid(transmute(i32x4)a, IMM8) } +@(enable_target_feature="sse2") _mm_sra_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrad(transmute(i32x4)a, transmute(i32x4)count) } +@(enable_target_feature="sse2") _mm_srli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_srli_si128_impl(a, IMM8) } +@(enable_target_feature="sse2") _mm_srli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrliw(transmute(i16x8)a. IMM8) } +@(enable_target_feature="sse2") _mm_srl_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrlw(transmute(i16x8)a, transmute(i16x8)count) } +@(enable_target_feature="sse2") _mm_srli_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrlid(transmute(i32x4)a, IMM8) } +@(enable_target_feature="sse2") _mm_srl_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrld(transmute(i32x4)a, transmute(i32x4)count) } +@(enable_target_feature="sse2") _mm_srli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrliq(transmute(i64x2)a, IMM8) } +@(enable_target_feature="sse2") _mm_srl_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrlq(transmute(i64x2)a, transmute(i64x2)count) } +@(enable_target_feature="sse2") _mm_and_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return simd.and(a, b) } +@(enable_target_feature="sse2") _mm_andnot_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return simd.and_not(b, a) } +@(enable_target_feature="sse2") _mm_or_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return simd.or(a, b) } +@(enable_target_feature="sse2") _mm_xor_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return simd.xor(a, b) } +@(enable_target_feature="sse2") _mm_cmpeq_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_eq(transmute(i8x16)a, transmute(i8x16)b) } +@(enable_target_feature="sse2") _mm_cmpeq_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_eq(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_cmpeq_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_eq(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sse2") _mm_cmpgt_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_gt(transmute(i8x16)a, transmute(i8x16)b) } +@(enable_target_feature="sse2") _mm_cmpgt_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_gt(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_cmpgt_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_gt(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sse2") _mm_cmplt_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_lt(transmute(i8x16)a, transmute(i8x16)b) } +@(enable_target_feature="sse2") _mm_cmplt_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_lt(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_cmplt_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_lt(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sse2") _mm_cvtepi32_pd :: #force_inline proc "c" (a: __m128i) -> __m128d { v := transmute(i32x4)a return cast(__m128d)simd.shuffle(v, v, 0, 1) } +@(enable_target_feature="sse2") _mm_cvtsi32_sd :: #force_inline proc "c" (a: __m128d, b: i32) -> __m128d { return simd.replace(a, 0, f64(b)) } +@(enable_target_feature="sse2") _mm_cvtepi32_ps :: #force_inline proc "c" (a: __m128i) -> __m128 { return cvtdq2ps(transmute(i32x4)a) } +@(enable_target_feature="sse2") _mm_cvtps_epi32 :: #force_inline proc "c" (a: __m128) -> __m128i { return transmute(__m128i)cvtps2dq(a) } +@(enable_target_feature="sse2") _mm_cvtsi32_si128 :: #force_inline proc "c" (a: i32) -> __m128i { return transmute(__m128i)i32x4{a, 0, 0, 0} } +@(enable_target_feature="sse2") _mm_cvtsi128_si32 :: #force_inline proc "c" (a: __m128i) -> i32 { return simd.extract(transmute(i32x4)a, 0) } +@(enable_target_feature="sse2") _mm_set_epi64x :: #force_inline proc "c" (e1, e0: i64) -> __m128i { return transmute(__m128i)i64x2{e0, e1} } +@(enable_target_feature="sse2") _mm_set_epi32 :: #force_inline proc "c" (e3, e2, e1, e0: i32) -> __m128i { return transmute(__m128i)i32x4{e0, e1, e2, e3} } +@(enable_target_feature="sse2") _mm_set_epi16 :: #force_inline proc "c" (e7, e6, e5, e4, e3, e2, e1, e0: i16) -> __m128i { return transmute(__m128i)i16x8{e0, e1, e2, e3, e4, e5, e6, e7} } +@(enable_target_feature="sse2") _mm_set_epi8 :: #force_inline proc "c" (e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0: i8) -> __m128i { return transmute(__m128i)i8x16{e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15} } +@(enable_target_feature="sse2") _mm_set1_epi64x :: #force_inline proc "c" (a: i64) -> __m128i { return _mm_set_epi64x(a, a) } +@(enable_target_feature="sse2") _mm_set1_epi32 :: #force_inline proc "c" (a: i32) -> __m128i { return _mm_set_epi32(a, a, a, a) } +@(enable_target_feature="sse2") _mm_set1_epi16 :: #force_inline proc "c" (a: i16) -> __m128i { return _mm_set_epi16(a, a, a, a, a, a, a, a) } +@(enable_target_feature="sse2") _mm_set1_epi8 :: #force_inline proc "c" (a: i8) -> __m128i { return _mm_set_epi8(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a) } +@(enable_target_feature="sse2") _mm_setr_epi32 :: #force_inline proc "c" (e3, e2, e1, e0: i32) -> __m128i { return _mm_set_epi32(e0, e1, e2, e3) } +@(enable_target_feature="sse2") _mm_setr_epi16 :: #force_inline proc "c" (e7, e6, e5, e4, e3, e2, e1, e0: i16) -> __m128i { return _mm_set_epi16(e0, e1, e2, e3, e4, e5, e6, e7) } +@(enable_target_feature="sse2") _mm_setr_epi8 :: #force_inline proc "c" (e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0: i8) -> __m128i { return _mm_set_epi8(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15) } +@(enable_target_feature="sse2") _mm_setzero_si128 :: #force_inline proc "c" () -> __m128i { return _mm_set1_epi64x(0) } +@(enable_target_feature="sse2") _mm_loadl_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { return _mm_set_epi64x(0, intrinsics.unaligned_load((^i64)(mem_addr))) } +@(enable_target_feature="sse2") _mm_load_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { return mem_addr^ } +@(enable_target_feature="sse2") _mm_loadu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { dst := _mm_undefined_si128() intrinsics.mem_copy_non_overlapping(&dst, mem_addr, size_of(__m128i)) return dst } +@(enable_target_feature="sse2") _mm_maskmoveu_si128 :: #force_inline proc "c" (a, mask: __m128i, mem_addr: rawptr) { maskmovdqu(transmute(i8x16)a, transmute(i8x16)mask, mem_addr) } +@(enable_target_feature="sse2") _mm_store_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { mem_addr^ = a } +@(enable_target_feature="sse2") _mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { storeudq(mem_addr, a) } +@(enable_target_feature="sse2") _mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { a := a intrinsics.mem_copy_non_overlapping(mem_addr, &a, 8) } +@(enable_target_feature="sse2") _mm_stream_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { intrinsics.non_temporal_store(mem_addr, a) } +@(enable_target_feature="sse2") _mm_stream_si32 :: #force_inline proc "c" (mem_addr: ^i32, a: i32) { intrinsics.non_temporal_store(mem_addr, a) } +@(enable_target_feature="sse2") _mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { zero := _mm_setzero_si128() return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)zero, 0, 2) @@ -364,24 +459,31 @@ _mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { +@(enable_target_feature="sse2") _mm_packs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)packsswb(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_packs_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)packssdw(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="sse2") _mm_packus_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)packuswb(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="sse2") _mm_extract_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> i32 { return i32(simd.extract(transmute(u16x8)a, IMM8)) } +@(enable_target_feature="sse2") _mm_insert_epi16 :: #force_inline proc "c" (a: __m128i, i: i32, $IMM8: u32) -> __m128i { return i32(simd.replace(transmute(u16x8)a, IMM8, i16(i))) } +@(enable_target_feature="sse2") _mm_movemask_epi8 :: #force_inline proc "c" (a: __m128i) -> i32 { return pmovmskb(transmute(i8x16)a) } +@(enable_target_feature="sse2") _mm_shuffle_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { v := transmute(i32x4)a return transmute(__m128i)simd.shuffle( @@ -393,6 +495,7 @@ _mm_shuffle_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i (IMM8 >> 6) & 0b11, ) } +@(enable_target_feature="sse2") _mm_shufflehi_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { v := transmute(i16x8)a return transmute(__m128i)simd.shuffle( @@ -408,6 +511,7 @@ _mm_shufflehi_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128 ((IMM8 >> 6) & 0b11) + 4, ) } +@(enable_target_feature="sse2") _mm_shufflelo_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { v := transmute(i16x8)a return transmute(__m128i)simd.shuffle( @@ -423,6 +527,7 @@ _mm_shufflelo_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128 7, ) } +@(enable_target_feature="sse2") _mm_unpackhi_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle( transmute(i8x16)a, @@ -430,15 +535,19 @@ _mm_unpackhi_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31, ) } +@(enable_target_feature="sse2") _mm_unpackhi_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i16x8)a, transmute(i16x8)b, 4, 12, 5, 13, 6, 14, 7, 15) } +@(enable_target_feature="sse2") _mm_unpackhi_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i32x4)a, transmute(i32x4)b, 2, 6, 3, 7) } +@(enable_target_feature="sse2") _mm_unpackhi_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 1, 3) } +@(enable_target_feature="sse2") _mm_unpacklo_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle( transmute(i8x16)a, @@ -446,12 +555,15 @@ _mm_unpacklo_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23, ) } +@(enable_target_feature="sse2") _mm_unpacklo_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i16x8)a, transmute(i16x8)b, 0, 8, 1, 9, 2, 10, 3, 11) } +@(enable_target_feature="sse2") _mm_unpacklo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i32x4)a, transmute(i32x4)b, 0, 4, 1, 5) } +@(enable_target_feature="sse2") _mm_unpacklo_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 0, 2) } @@ -459,57 +571,75 @@ _mm_unpacklo_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +@(enable_target_feature="sse2") _mm_add_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(a) + _mm_cvtsd_f64(b)) } +@(enable_target_feature="sse2") _mm_add_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.add(a, b) } +@(enable_target_feature="sse2") _mm_div_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(a) / _mm_cvtsd_f64(b)) } +@(enable_target_feature="sse2") _mm_div_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.div(a, b) } +@(enable_target_feature="sse2") _mm_max_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return maxsd(a, b) } +@(enable_target_feature="sse2") _mm_max_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return maxpd(a, b) } +@(enable_target_feature="sse2") _mm_min_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return minsd(a, b) } +@(enable_target_feature="sse2") _mm_min_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return minpd(a, b) } +@(enable_target_feature="sse2") _mm_mul_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(a) * _mm_cvtsd_f64(b)) } +@(enable_target_feature="sse2") _mm_mul_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.mul(a, b) } +@(enable_target_feature="sse2") _mm_sqrt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(sqrtsd(b))) } +@(enable_target_feature="sse2") _mm_sqrt_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { return simd.sqrt(a) } +@(enable_target_feature="sse2") _mm_sub_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(a) - _mm_cvtsd_f64(b)) } +@(enable_target_feature="sse2") _mm_sub_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.sub(a, b) } +@(enable_target_feature="sse2") _mm_and_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return transmute(__m128d)_mm_and_si128(transmute(__m128i)a, transmute(__m128i)b) } +@(enable_target_feature="sse2") _mm_andnot_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return transmute(__m128d)_mm_andnot_si128(transmute(__m128i)a, transmute(__m128i)b) } +@(enable_target_feature="sse2") _mm_or_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return transmute(__m128d)_mm_or_si128(transmute(__m128i)a, transmute(__m128i)b) } +@(enable_target_feature="sse2") _mm_xor_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return transmute(__m128d)_mm_xor_si128(transmute(__m128i)a, transmute(__m128i)b) } @@ -517,111 +647,147 @@ _mm_xor_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { +@(enable_target_feature="sse2") _mm_cmpeq_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 0) } +@(enable_target_feature="sse2") _mm_cmplt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 1) } +@(enable_target_feature="sse2") _mm_cmple_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 2) } +@(enable_target_feature="sse2") _mm_cmpgt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(_mm_cmplt_sd(b, a), 1, simd.extract(a, 1)) } +@(enable_target_feature="sse2") _mm_cmpge_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(_mm_cmple_sd(b, a), 1, simd.extract(a, 1)) } +@(enable_target_feature="sse2") _mm_cmpord_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 7) } +@(enable_target_feature="sse2") _mm_cmpunord_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 3) } +@(enable_target_feature="sse2") _mm_cmpneq_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 4) } +@(enable_target_feature="sse2") _mm_cmpnlt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 5) } +@(enable_target_feature="sse2") _mm_cmpnle_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 6) } +@(enable_target_feature="sse2") _mm_cmpngt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(_mm_cmpnlt_sd(b, a), 1, simd.extract(a, 1)) } +@(enable_target_feature="sse2") _mm_cmpnge_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(_mm_cmpnle_sd(b, a), 1, simd.extract(a, 1)) } +@(enable_target_feature="sse2") _mm_cmpeq_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 0) } +@(enable_target_feature="sse2") _mm_cmplt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 1) } +@(enable_target_feature="sse2") _mm_cmple_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 2) } +@(enable_target_feature="sse2") _mm_cmpgt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_cmplt_pd(b, a) } +@(enable_target_feature="sse2") _mm_cmpge_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_cmple_pd(b, a) } +@(enable_target_feature="sse2") _mm_cmpord_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 7) } +@(enable_target_feature="sse2") _mm_cmpunord_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 3) } +@(enable_target_feature="sse2") _mm_cmpneq_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 4) } +@(enable_target_feature="sse2") _mm_cmpnlt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 5) } +@(enable_target_feature="sse2") _mm_cmpnle_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 6) } +@(enable_target_feature="sse2") _mm_cmpngt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_cmpnlt_pd(b, a) } +@(enable_target_feature="sse2") _mm_cmpnge_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_cmpnle_pd(b, a) } +@(enable_target_feature="sse2") _mm_comieq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comieqsd(a, b) } +@(enable_target_feature="sse2") _mm_comilt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comiltsd(a, b) } +@(enable_target_feature="sse2") _mm_comile_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comilesd(a, b) } +@(enable_target_feature="sse2") _mm_comigt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comigtsd(a, b) } +@(enable_target_feature="sse2") _mm_comige_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comigesd(a, b) } +@(enable_target_feature="sse2") _mm_comineq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comineqsd(a, b) } +@(enable_target_feature="sse2") _mm_ucomieq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomieqsd(a, b) } +@(enable_target_feature="sse2") _mm_ucomilt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomiltsd(a, b) } +@(enable_target_feature="sse2") _mm_ucomile_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomilesd(a, b) } +@(enable_target_feature="sse2") _mm_ucomigt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomigtsd(a, b) } +@(enable_target_feature="sse2") _mm_ucomige_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomigesd(a, b) } +@(enable_target_feature="sse2") _mm_ucomineq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomineqsd(a, b) } @@ -630,115 +796,151 @@ _mm_ucomineq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { +@(enable_target_feature="sse2") _mm_cvtpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { return cvtpd2ps(a) } +@(enable_target_feature="sse2") _mm_cvtps_pd :: #force_inline proc "c" (a: __m128) -> __m128d { return cvtps2pd(a) } +@(enable_target_feature="sse2") _mm_cvtpd_epi32 :: #force_inline proc "c" (a: __m128d) -> __m128i { return transmute(__m128i)cvtpd2dq(a) } +@(enable_target_feature="sse2") _mm_cvtsd_si32 :: #force_inline proc "c" (a: __m128d) -> i32 { return cvtsd2si(a) } +@(enable_target_feature="sse2") _mm_cvtsd_ss :: #force_inline proc "c" (a, b: __m128d) -> __m128 { return cvtsd2ss(a, b) } +@(enable_target_feature="sse2") _mm_cvtsd_f64 :: #force_inline proc "c" (a: __m128d) -> f64 { return simd.extract(a, 0) } +@(enable_target_feature="sse2") _mm_cvtss_sd :: #force_inline proc "c" (a, b: __m128) -> __m128d { return cvtss2sd(a, b) } +@(enable_target_feature="sse2") _mm_cvttpd_epi32 :: #force_inline proc "c" (a: __m128d) -> __m128i { return transmute(__m128i)cvttpd2dq(a) } +@(enable_target_feature="sse2") _mm_cvttsd_si32 :: #force_inline proc "c" (a: __m128d) -> i32 { return cvttsd2si(a) } +@(enable_target_feature="sse2") _mm_cvttps_epi32 :: #force_inline proc "c" (a: __m128) -> __m128i { return transmute(__m128i)cvttps2dq(a) } +@(enable_target_feature="sse2") _mm_set_sd :: #force_inline proc "c" (a: f64) -> __m128d { return _mm_set_pd(0.0, a) } +@(enable_target_feature="sse2") _mm_set1_pd :: #force_inline proc "c" (a: f64) -> __m128d { return _mm_set_pd(a, a) } +@(enable_target_feature="sse2") _mm_set_pd1 :: #force_inline proc "c" (a: f64) -> __m128d { return _mm_set_pd(a, a) } +@(enable_target_feature="sse2") _mm_set_pd :: #force_inline proc "c" (a: f64, b: f64) -> __m128d { return __m128d{b, a} } +@(enable_target_feature="sse2") _mm_setr_pd :: #force_inline proc "c" (a: f64, b: f64) -> __m128d { return _mm_set_pd(b, a) } +@(enable_target_feature="sse2") _mm_setzero_pd :: #force_inline proc "c" () -> __m128d { return _mm_set_pd(0.0, 0.0) } +@(enable_target_feature="sse2") _mm_movemask_pd :: #force_inline proc "c" (a: __m128d) -> i32 { return movmskpd(a) } +@(enable_target_feature="sse2") _mm_load_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { return (^__m128d)(mem_addr)^ } +@(enable_target_feature="sse2") _mm_load_sd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { return _mm_setr_pd(mem_addr^, 0.) } +@(enable_target_feature="sse2") _mm_loadh_pd :: #force_inline proc "c" (a: __m128d, mem_addr: ^f64) -> __m128d { return _mm_setr_pd(simd.extract(a, 0), mem_addr^) } +@(enable_target_feature="sse2") _mm_loadl_pd :: #force_inline proc "c" (a: __m128d, mem_addr: ^f64) -> __m128d { return _mm_setr_pd(mem_addr^, simd.extract(a, 1)) } +@(enable_target_feature="sse2") _mm_stream_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { intrinsics.non_temporal_store((^__m128d)(mem_addr), a) } +@(enable_target_feature="sse2") _mm_store_sd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { mem_addr^ = simd.extract(a, 0) } +@(enable_target_feature="sse2") _mm_store_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { (^__m128d)(mem_addr)^ = a } +@(enable_target_feature="sse2") _mm_storeu_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { storeupd(mem_addr, a) } +@(enable_target_feature="sse2") _mm_store1_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { (^__m128d)(mem_addr)^ = simd.shuffle(a, a, 0, 0) } +@(enable_target_feature="sse2") _mm_store_pd1 :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { (^__m128d)(mem_addr)^ = simd.shuffle(a, a, 0, 0) } +@(enable_target_feature="sse2") _mm_storer_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { (^__m128d)(mem_addr)^ = simd.shuffle(a, a, 1, 0) } +@(enable_target_feature="sse2") _mm_storeh_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { mem_addr^ = simd.extract(a, 1) } +@(enable_target_feature="sse2") _mm_storel_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { mem_addr^ = simd.extract(a, 0) } +@(enable_target_feature="sse2") _mm_load1_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { d := mem_addr^ return _mm_setr_pd(d, d) } +@(enable_target_feature="sse2") _mm_load_pd1 :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { return _mm_load1_pd(mem_addr) } +@(enable_target_feature="sse2") _mm_loadr_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { a := _mm_load_pd(mem_addr) return simd.shuffle(a, a, 1, 0) } +@(enable_target_feature="sse2") _mm_loadu_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { dst := _mm_undefined_pd() intrinsics.mem_copy_non_overlapping(&dst, mem_addr, size_of(__m128d)) return dst } +@(enable_target_feature="sse2") _mm_shuffle_pd :: #force_inline proc "c" (a, b: __m128d, $MASK: u32) -> __m128d { return simd.shuffle(a, b, MASK&0b1, ((MASK>>1)&0b1) + 2) } +@(enable_target_feature="sse2") _mm_move_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_setr_pd(simd.extract(b, 0), simd.extract(a, 1)) } @@ -746,71 +948,92 @@ _mm_move_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { +@(enable_target_feature="sse2") _mm_castpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { return transmute(__m128)a } +@(enable_target_feature="sse2") _mm_castpd_si128 :: #force_inline proc "c" (a: __m128d) -> __m128i { return transmute(__m128i)a } +@(enable_target_feature="sse2") _mm_castps_pd :: #force_inline proc "c" (a: __m128) -> __m128d { return transmute(__m128d)a } +@(enable_target_feature="sse2") _mm_castps_si128 :: #force_inline proc "c" (a: __m128) -> __m128i { return transmute(__m128i)a } +@(enable_target_feature="sse2") _mm_castsi128_pd :: #force_inline proc "c" (a: __m128i) -> __m128d { return transmute(__m128d)a } +@(enable_target_feature="sse2") _mm_castsi128_ps :: #force_inline proc "c" (a: __m128i) -> __m128 { return transmute(__m128)a } +@(enable_target_feature="sse2") _mm_undefined_pd :: #force_inline proc "c" () -> __m128d { return __m128d{0, 0} } +@(enable_target_feature="sse2") _mm_undefined_si128 :: #force_inline proc "c" () -> __m128i { return __m128i{0, 0} } +@(enable_target_feature="sse2") _mm_unpackhi_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.shuffle(a, b, 1, 3) } +@(enable_target_feature="sse2") _mm_unpacklo_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.shuffle(a, b, 0, 2) } when ODIN_ARCH == .amd64 { + @(enable_target_feature="sse2") _mm_cvtsd_si64 :: #force_inline proc "c" (a: __m128d) -> i64 { return cvtsd2si64(a) } + @(enable_target_feature="sse2") _mm_cvtsd_si64x :: #force_inline proc "c" (a: __m128d) -> i64 { return _mm_cvtsd_si64(a) } + @(enable_target_feature="sse2") _mm_cvttsd_si64 :: #force_inline proc "c" (a: __m128d) -> i64 { return cvttsd2si64(a) } + @(enable_target_feature="sse2") _mm_cvttsd_si64x :: #force_inline proc "c" (a: __m128d) -> i64 { return _mm_cvttsd_si64(a) } + @(enable_target_feature="sse2") _mm_stream_si64 :: #force_inline proc "c" (mem_addr: ^i64, a: i64) { intrinsics.non_temporal_store(mem_addr, a) } + @(enable_target_feature="sse2") _mm_cvtsi64_si128 :: #force_inline proc "c" (a: i64) -> __m128i { return _mm_set_epi64x(0, a) } + @(enable_target_feature="sse2") _mm_cvtsi64x_si128 :: #force_inline proc "c" (a: i64) -> __m128i { return _mm_cvtsi64_si128(a) } + @(enable_target_feature="sse2") _mm_cvtsi128_si64 :: #force_inline proc "c" (a: __m128i) -> i64 { return simd.extract(transmute(i64x2)a, 0) } + @(enable_target_feature="sse2") _mm_cvtsi128_si64x :: #force_inline proc "c" (a: __m128i) -> i64 { return _mm_cvtsi128_si64(a) } + @(enable_target_feature="sse2") _mm_cvtsi64_sd :: #force_inline proc "c" (a: __m128d, b: i64) -> __m128d { return simd.replace(a, 0, f64(b)) } + @(enable_target_feature="sse2") _mm_cvtsi64x_sd :: #force_inline proc "c" (a: __m128d, b: i64) -> __m128d { return _mm_cvtsi64_sd(a, b) } diff --git a/core/simd/x86/sse3.odin b/core/simd/x86/sse3.odin index 9766a43e6..370bfa952 100644 --- a/core/simd/x86/sse3.odin +++ b/core/simd/x86/sse3.odin @@ -4,36 +4,47 @@ package simd_x86 import "core:intrinsics" import "core:simd" +@(enable_target_feature="sse3") _mm_addsub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return addsubps(a, b) } +@(enable_target_feature="sse3") _mm_addsub_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { return addsubpd(a, b) } +@(enable_target_feature="sse3") _mm_hadd_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { return haddpd(a, b) } +@(enable_target_feature="sse3") _mm_hadd_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return haddps(a, b) } +@(enable_target_feature="sse3") _mm_hsub_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { return hsubpd(a, b) } +@(enable_target_feature="sse3") _mm_hsub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return hsubps(a, b) } +@(enable_target_feature="sse3") _mm_lddqu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { return transmute(__m128i)lddqu(mem_addr) } +@(enable_target_feature="sse3") _mm_movedup_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { return simd.shuffle(a, a, 0, 0) } +@(enable_target_feature="sse3") _mm_loaddup_pd :: #force_inline proc "c" (mem_addr: [^]f64) -> __m128d { return _mm_load1_pd(mem_addr) } +@(enable_target_feature="sse3") _mm_movehdup_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return simd.shuffle(a, a, 1, 1, 3, 3) } +@(enable_target_feature="sse3") _mm_moveldup_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return simd.shuffle(a, a, 0, 0, 2, 2) } diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index 6c6f28008..8c677aed4 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -5,18 +5,23 @@ import "core:intrinsics" import "core:simd" _ :: simd +@(enable_target_feature="ssse3") _mm_abs_epi8 :: #force_inline proc "c" (a: __m128i) -> __m128i { return transmute(__m128i)pabsb128(transmute(i8x16)a) } +@(enable_target_feature="ssse3") _mm_abs_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { return transmute(__m128i)pabsw128(transmute(i16x8)a) } +@(enable_target_feature="ssse3") _mm_abs_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { return transmute(__m128i)pabsd128(transmute(i32x4)a) } +@(enable_target_feature="ssse3") _mm_shuffle_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pshufb128(transmute(u8x16)a, transmute(u8x16)b) } +@(enable_target_feature="ssse3") _mm_alignr_epi8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 @@ -53,36 +58,47 @@ _mm_alignr_epi8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u32) -> __m128i } +@(enable_target_feature="ssse3") _mm_hadd_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phaddw128(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="ssse3") _mm_hadds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phaddsw128(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="ssse3") _mm_hadd_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phaddd128(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="ssse3") _mm_hsub_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phsubw128(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="ssse3") _mm_hsubs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phsubsw128(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="ssse3") _mm_hsub_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phsubd128(transmute(i32x4)a, transmute(i32x4)b) } +@(enable_target_feature="ssse3") _mm_maddubs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaddubsw128(transmute(u8x16)a, transmute(i8x16)b) } +@(enable_target_feature="ssse3") _mm_mulhrs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmulhrsw128(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="ssse3") _mm_sign_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psignb128(transmute(i8x16)a, transmute(i8x16)b) } +@(enable_target_feature="ssse3") _mm_sign_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psignw128(transmute(i16x8)a, transmute(i16x8)b) } +@(enable_target_feature="ssse3") _mm_sign_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psignd128(transmute(i32x4)a, transmute(i32x4)b) } diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 27e09a0db..65470749f 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -256,7 +256,6 @@ struct BuildContext { String extra_linker_flags; String extra_assembler_flags; String microarch; - String target_features; BuildModeKind build_mode; bool generate_docs; i32 optimization_level; @@ -320,6 +319,10 @@ struct BuildContext { PtrMap defined_values; + BlockingMutex target_features_mutex; + StringSet target_features_set; + String target_features_string; + }; gb_global BuildContext build_context = {0}; @@ -1197,6 +1200,100 @@ void init_build_context(TargetMetrics *cross_target) { #include "microsoft_craziness.h" #endif + +Array split_by_comma(String const &list) { + isize n = 1; + for (isize i = 0; i < list.len; i++) { + if (list.text[i] == ',') { + n++; + } + } + auto res = array_make(heap_allocator(), n); + + String s = list; + for (isize i = 0; i < n; i++) { + isize m = string_index_byte(s, ','); + if (m < 0) { + res[i] = s; + break; + } + res[i] = substring(s, 0, m); + s = substring(s, m+1, s.len); + } + return res; +} + +bool check_target_feature_is_valid(TokenPos pos, String const &feature) { + // TODO(bill): check_target_feature_is_valid + return true; +} + +bool check_target_feature_is_enabled(TokenPos pos, String const &target_feature_list) { + BuildContext *bc = &build_context; + mutex_lock(&bc->target_features_mutex); + defer (mutex_unlock(&bc->target_features_mutex)); + + auto items = split_by_comma(target_feature_list); + array_free(&items); + for_array(i, items) { + String const &item = items.data[i]; + if (!check_target_feature_is_valid(pos, item)) { + error(pos, "Target feature '%.*s' is not valid", LIT(item)); + return false; + } + if (!string_set_exists(&bc->target_features_set, item)) { + error(pos, "Target feature '%.*s' is not enabled", LIT(item)); + return false; + } + } + + return true; +} + +void enable_target_feature(TokenPos pos, String const &target_feature_list) { + BuildContext *bc = &build_context; + mutex_lock(&bc->target_features_mutex); + defer (mutex_unlock(&bc->target_features_mutex)); + + auto items = split_by_comma(target_feature_list); + array_free(&items); + for_array(i, items) { + String const &item = items.data[i]; + if (!check_target_feature_is_valid(pos, item)) { + error(pos, "Target feature '%.*s' is not valid", LIT(item)); + } + } +} + + +char const *target_features_set_to_cstring(gbAllocator allocator, bool with_quotes) { + isize len = 0; + for_array(i, build_context.target_features_set.entries) { + if (i != 0) { + len += 1; + } + String feature = build_context.target_features_set.entries[i].value; + len += feature.len; + if (with_quotes) len += 2; + } + char *features = gb_alloc_array(allocator, char, len+1); + len = 0; + for_array(i, build_context.target_features_set.entries) { + if (i != 0) { + features[len++] = ','; + } + + if (with_quotes) features[len++] = '"'; + String feature = build_context.target_features_set.entries[i].value; + gb_memmove(features, feature.text, feature.len); + len += feature.len; + if (with_quotes) features[len++] = '"'; + } + features[len++] = 0; + + return features; +} + // NOTE(Jeroen): Set/create the output and other paths and report an error as appropriate. // We've previously called `parse_build_flags`, so `out_filepath` should be set. bool init_build_paths(String init_filename) { @@ -1206,6 +1303,9 @@ bool init_build_paths(String init_filename) { // NOTE(Jeroen): We're pre-allocating BuildPathCOUNT slots so that certain paths are always at the same enumerated index. array_init(&bc->build_paths, permanent_allocator(), BuildPathCOUNT); + string_set_init(&bc->target_features_set, heap_allocator(), 1024); + mutex_init(&bc->target_features_mutex); + // [BuildPathMainPackage] Turn given init path into a `Path`, which includes normalizing it into a full path. bc->build_paths[BuildPath_Main_Package] = path_from_string(ha, init_filename); @@ -1382,5 +1482,10 @@ bool init_build_paths(String init_filename) { return false; } + if (bc->target_features_string.len != 0) { + enable_target_feature({}, bc->target_features_string); + } + return true; -} \ No newline at end of file +} + diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 62a1e2555..d4818892b 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -899,6 +899,18 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } } + if (ac.require_target_feature.len != 0 && ac.enable_target_feature.len != 0) { + error(e->token, "Attributes @(require_target_feature=...) and @(enable_target_feature=...) cannot be used together"); + } else if (ac.require_target_feature.len != 0) { + if (check_target_feature_is_enabled(e->token.pos, ac.require_target_feature)) { + e->Procedure.target_feature = ac.require_target_feature; + } else { + e->Procedure.target_feature_disabled = true; + } + } else if (ac.enable_target_feature.len != 0) { + enable_target_feature(e->token.pos, ac.enable_target_feature); + e->Procedure.target_feature = ac.enable_target_feature; + } switch (e->Procedure.optimization_mode) { case ProcedureOptimizationMode_None: diff --git a/src/checker.cpp b/src/checker.cpp index 8afc6eb14..874839ece 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3207,6 +3207,22 @@ DECL_ATTRIBUTE_PROC(proc_decl_attribute) { } } return true; + } else if (name == "require_target_feature") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + ac->require_target_feature = ev.value_string; + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; + } else if (name == "enable_target_feature") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + ac->enable_target_feature = ev.value_string; + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } return false; } diff --git a/src/checker.hpp b/src/checker.hpp index 1c9ffd8c7..8fc9e54c8 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -124,6 +124,9 @@ struct AttributeContext { String objc_name; bool objc_is_class_method; Type * objc_type; + + String require_target_feature; // required by the target micro-architecture + String enable_target_feature; // will be enabled for the procedure only }; AttributeContext make_attribute_context(String link_prefix) { diff --git a/src/entity.cpp b/src/entity.cpp index 904a630fb..76e6912b9 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -233,10 +233,12 @@ struct Entity { String link_name; String link_prefix; DeferredProcedure deferred_procedure; - bool is_foreign; - bool is_export; - bool generated_from_polymorphic; ProcedureOptimizationMode optimization_mode; + bool is_foreign : 1; + bool is_export : 1; + bool generated_from_polymorphic : 1; + bool target_feature_disabled : 1; + String target_feature; } Procedure; struct { Array entities; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 7cf588853..cf7389ec1 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1332,8 +1332,8 @@ void lb_generate_code(lbGenerator *gen) { } } - if (build_context.target_features.len != 0) { - llvm_features = alloc_cstring(permanent_allocator(), build_context.target_features); + if (build_context.target_features_set.entries.count != 0) { + llvm_features = target_features_set_to_cstring(permanent_allocator(), false); } // GB_ASSERT_MSG(LLVMTargetHasAsmBackend(target)); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 1e3591bf1..296a7fa6b 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -169,6 +169,19 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) } } + if (!entity->Procedure.target_feature_disabled && + entity->Procedure.target_feature.len != 0) { + auto features = split_by_comma(entity->Procedure.target_feature); + for_array(i, features) { + String feature = features[i]; + LLVMAttributeRef ref = LLVMCreateStringAttribute( + m->ctx, + cast(char const *)feature.text, cast(unsigned)feature.len, + "", 0); + LLVMAddAttributeAtIndex(p->value, LLVMAttributeIndex_FunctionIndex, ref); + } + } + if (entity->flags & EntityFlag_Cold) { lb_add_attribute_to_proc(m, p->value, "cold"); } diff --git a/src/main.cpp b/src/main.cpp index 13c8bd74d..ee71b91df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1376,8 +1376,8 @@ bool parse_build_flags(Array args) { } case BuildFlag_TargetFeatures: { GB_ASSERT(value.kind == ExactValue_String); - build_context.target_features = value.value_string; - string_to_lower(&build_context.target_features); + build_context.target_features_string = value.value_string; + string_to_lower(&build_context.target_features_string); break; } case BuildFlag_RelocMode: { diff --git a/src/string.cpp b/src/string.cpp index 616761265..44eccd2d2 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -157,6 +157,15 @@ int string_compare(String const &x, String const &y) { return 0; } +isize string_index_byte(String const &s, u8 x) { + for (isize i = 0; i < s.len; i++) { + if (s.text[i] == x) { + return i; + } + } + return -1; +} + GB_COMPARE_PROC(string_cmp_proc) { String x = *(String *)a; String y = *(String *)b; From a0babefe55568c400835a5984db676d9f650a8b4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 15:13:45 +0100 Subject: [PATCH 238/254] Fix lb_build_builtin_simd_proc --- src/llvm_backend_proc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 296a7fa6b..e3ffcaef2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1021,8 +1021,8 @@ lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAndValue const res.type = tv.type; lbValue arg0 = {}; if (ce->args.count > 0) arg0 = lb_build_expr(p, ce->args[0]); - lbValue arg1 = {}; if (ce->args.count > 1) arg0 = lb_build_expr(p, ce->args[1]); - lbValue arg2 = {}; if (ce->args.count > 2) arg0 = lb_build_expr(p, ce->args[2]); + lbValue arg1 = {}; if (ce->args.count > 1) arg1 = lb_build_expr(p, ce->args[1]); + lbValue arg2 = {}; if (ce->args.count > 2) arg2 = lb_build_expr(p, ce->args[2]); Type *elem = base_array_type(arg0.type); From 51707032d10d166094959d49e597b322a8d7c55d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 15:17:02 +0100 Subject: [PATCH 239/254] Add SSE4.1 --- core/simd/x86/sse41.odin | 352 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 core/simd/x86/sse41.odin diff --git a/core/simd/x86/sse41.odin b/core/simd/x86/sse41.odin new file mode 100644 index 000000000..516e0bdc2 --- /dev/null +++ b/core/simd/x86/sse41.odin @@ -0,0 +1,352 @@ +//+build i386, amd64 +package simd_x86 + +import "core:simd" + +// SSE4 rounding constants +_MM_FROUND_TO_NEAREST_INT :: 0x00 +_MM_FROUND_TO_NEG_INF :: 0x01 +_MM_FROUND_TO_POS_INF :: 0x02 +_MM_FROUND_TO_ZERO :: 0x03 +_MM_FROUND_CUR_DIRECTION :: 0x04 +_MM_FROUND_RAISE_EXC :: 0x00 +_MM_FROUND_NO_EXC :: 0x08 +_MM_FROUND_NINT :: 0x00 +_MM_FROUND_FLOOR :: _MM_FROUND_RAISE_EXC | _MM_FROUND_TO_NEG_INF +_MM_FROUND_CEIL :: _MM_FROUND_RAISE_EXC | _MM_FROUND_TO_POS_INF +_MM_FROUND_TRUNC :: _MM_FROUND_RAISE_EXC | _MM_FROUND_TO_ZERO +_MM_FROUND_RINT :: _MM_FROUND_RAISE_EXC | _MM_FROUND_CUR_DIRECTION +_MM_FROUND_NEARBYINT :: _MM_FROUND_NO_EXC | _MM_FROUND_CUR_DIRECTION + + + +@(enable_target_feature="sse4.1") +_mm_blendv_epi8 :: #force_inline proc "c" (a, b, mask: __m128i) -> __m128i { + return transmute(__m128i)pblendvb(transmute(i8x16)a, transmute(i8x16)b, transmute(i8x16)mask) +} +@(enable_target_feature="sse4.1") +_mm_blend_epi16 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i { + return transmute(__m128i)pblendw(transmute(i16x8)a, transmute(i16x8)b, IMM8) +} +@(enable_target_feature="sse4.1") +_mm_blendv_pd :: #force_inline proc "c" (a, b, mask: __m128d) -> __m128d { + return blendvpd(a, b, mask) +} +@(enable_target_feature="sse4.1") +_mm_blendv_ps :: #force_inline proc "c" (a, b, mask: __m128) -> __m128 { + return blendvps(a, b, mask) +} +@(enable_target_feature="sse4.1") +_mm_blend_pd :: #force_inline proc "c" (a, b: __m128d, $IMM2: u8) -> __m128d { + return blendpd(a, b, IMM2) +} +@(enable_target_feature="sse4.1") +_mm_blend_ps :: #force_inline proc "c" (a, b: __m128, $IMM4: u8) -> __m128 { + return blendps(a, b, IMM4) +} +@(enable_target_feature="sse4.1") +_mm_extract_ps :: #force_inline proc "c" (a: __m128, $IMM8: u32) -> i32 { + return transmute(i32)simd.extract(a, IMM8) +} +@(enable_target_feature="sse4.1") +_mm_extract_epi8 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> i32 { + return i32(simd.extract(transmute(u8x16)a, IMM8)) +} +@(enable_target_feature="sse4.1") +_mm_extract_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> i32 { + return simd.extract(transmute(i32x4)a, IMM8) +} +@(enable_target_feature="sse4.1") +_mm_insert_ps :: #force_inline proc "c" (a, b: __m128, $IMM8: u8) -> __m128 { + return insertps(a, b, IMM8) +} +@(enable_target_feature="sse4.1") +_mm_insert_epi8 :: #force_inline proc "c" (a: __m128i, i: i32, $IMM8: u32) -> __m128i { + return transmute(__m128i)simd.replace(transmute(i8x16)a, IMM8, i8(i)) +} +@(enable_target_feature="sse4.1") +_mm_insert_epi32 :: #force_inline proc "c" (a: __m128i, i: i32, $IMM8: u32) -> __m128i { + return transmute(__m128i)simd.replace(transmute(i32x4)a, IMM8, i) +} +@(enable_target_feature="sse4.1") +_mm_max_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pmaxsb(transmute(i8x16)a, transmute(i8x16)b) +} +@(enable_target_feature="sse4.1") +_mm_max_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pmaxuw(transmute(u16x8)a, transmute(u16x8)b) +} +@(enable_target_feature="sse4.1") +_mm_max_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pmaxsd(transmute(i32x4)a, transmute(i32x4)b) +} +@(enable_target_feature="sse4.1") +_mm_max_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pmaxud(transmute(u32x4)a, transmute(u32x4)b) +} +@(enable_target_feature="sse4.1") +_mm_min_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pminsb(transmute(i8x16)a, transmute(i8x16)b) +} +@(enable_target_feature="sse4.1") +_mm_min_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pminuw(transmute(u16x8)a, transmute(u16x8)b) +} +@(enable_target_feature="sse4.1") +_mm_min_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pminsd(transmute(i32x4)a, transmute(i32x4)b) +} +@(enable_target_feature="sse4.1") +_mm_min_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pminud(transmute(u32x4)a, transmute(u32x4)b) +} +@(enable_target_feature="sse4.1") +_mm_packus_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)packusdw(transmute(i32x4)a, transmute(i32x4)b) +} +@(enable_target_feature="sse4.1") +_mm_cmpeq_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_eq(transmute(i64x2)a, transmute(i64x2)b) +} +@(enable_target_feature="sse4.1") +_mm_cvtepi8_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(i8x16)a + y := simd.shuffle(x, x, 0, 1, 2, 3, 4, 5, 6, 7) + return transmute(__m128i)i16x8(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepi8_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(i8x16)a + y := simd.shuffle(x, x, 0, 1, 2, 3) + return transmute(__m128i)i32x4(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepi8_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(i8x16)a + y := simd.shuffle(x, x, 0, 1) + return transmute(__m128i)i64x2(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepi16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(i16x8)a + y := simd.shuffle(x, x, 0, 1, 2, 3) + return transmute(__m128i)i32x4(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepi16_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(i16x8)a + y := simd.shuffle(x, x, 0, 1) + return transmute(__m128i)i64x2(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepi32_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(i32x4)a + y := simd.shuffle(x, x, 0, 1) + return transmute(__m128i)i64x2(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepu8_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(u8x16)a + y := simd.shuffle(x, x, 0, 1, 2, 3, 4, 5, 6, 7) + return transmute(__m128i)i16x8(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepu8_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(u8x16)a + y := simd.shuffle(x, x, 0, 1, 2, 3) + return transmute(__m128i)i32x4(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepu8_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(u8x16)a + y := simd.shuffle(x, x, 0, 1) + return transmute(__m128i)i64x2(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepu16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(u16x8)a + y := simd.shuffle(x, x, 0, 1, 2, 3) + return transmute(__m128i)i32x4(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepu16_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(u16x8)a + y := simd.shuffle(x, x, 0, 1) + return transmute(__m128i)i64x2(y) +} +@(enable_target_feature="sse4.1") +_mm_cvtepu32_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { + x := transmute(u32x4)a + y := simd.shuffle(x, x, 0, 1) + return transmute(__m128i)i64x2(y) +} +@(enable_target_feature="sse4.1") +_mm_dp_pd :: #force_inline proc "c" (a, b: __m128d, $IMM8: u8) -> __m128d { + return dppd(a, b, IMM8) +} +@(enable_target_feature="sse4.1") +_mm_dp_ps :: #force_inline proc "c" (a, b: __m128, $IMM8: u8) -> __m128 { + return dpps(a, b, IMM8) +} +@(enable_target_feature="sse4.1") +_mm_floor_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { + return simd.floor(a) +} +@(enable_target_feature="sse4.1") +_mm_floor_ps :: #force_inline proc "c" (a: __m128) -> __m128 { + return simd.floor(a) +} +@(enable_target_feature="sse4.1") +_mm_floor_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return roundsd(a, b, _MM_FROUND_FLOOR) +} +@(enable_target_feature="sse4.1") +_mm_floor_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return roundss(a, b, _MM_FROUND_FLOOR) +} +@(enable_target_feature="sse4.1") +_mm_ceil_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { + return simd.ceil(a) +} +@(enable_target_feature="sse4.1") +_mm_ceil_ps :: #force_inline proc "c" (a: __m128) -> __m128 { + return simd.ceil(a) +} +@(enable_target_feature="sse4.1") +_mm_ceil_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { + return roundsd(a, b, _MM_FROUND_CEIL) +} +@(enable_target_feature="sse4.1") +_mm_ceil_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { + return roundss(a, b, _MM_FROUND_CEIL) +} +@(enable_target_feature="sse4.1") +_mm_round_pd :: #force_inline proc "c" (a: __m128d, $ROUNDING: i32) -> __m128d { + return roundpd(a, ROUNDING) +} +@(enable_target_feature="sse4.1") +_mm_round_ps :: #force_inline proc "c" (a: __m128, $ROUNDING: i32) -> __m128 { + return roundps(a, ROUNDING) +} +@(enable_target_feature="sse4.1") +_mm_round_sd :: #force_inline proc "c" (a, b: __m128d, $ROUNDING: i32) -> __m128d { + return roundsd(a, b, ROUNDING) +} +@(enable_target_feature="sse4.1") +_mm_round_ss :: #force_inline proc "c" (a, b: __m128, $ROUNDING: i32) -> __m128 { + return roundss(a, b, ROUNDING) +} +@(enable_target_feature="sse4.1") +_mm_minpos_epu16 :: #force_inline proc "c" (a: __m128i) -> __m128i { + return transmute(__m128i)phminposuw(transmute(u16x8)a) +} +@(enable_target_feature="sse4.1") +_mm_mul_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)pmuldq(transmute(i32x4)a, transmute(i32x4)b) +} +@(enable_target_feature="sse4.1") +_mm_mullo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return transmute(__m128i)simd.mul(transmute(i32x4)a, transmute(i32x4)b) +} +@(enable_target_feature="sse4.1") +_mm_mpsadbw_epu8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i { + return transmute(__m128i)mpsadbw(transmute(u8x16)a, transmute(u8x16)b, IMM8) +} +@(enable_target_feature="sse4.1") +_mm_testz_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { + return ptestz(transmute(i64x2)a, transmute(i64x2)mask) +} +@(enable_target_feature="sse4.1") +_mm_testc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { + return ptestc(transmute(i64x2)a, transmute(i64x2)mask) +} +@(enable_target_feature="sse4.1") +_mm_testnzc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { + return ptestnzc(transmute(i64x2)a, transmute(i64x2)mask) +} +@(enable_target_feature="sse4.1") +_mm_test_all_zeros :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { + return _mm_testz_si128(a, mask) +} +@(enable_target_feature="sse4.1") +_mm_test_all_ones :: #force_inline proc "c" (a: __m128i) -> i32 { + return _mm_testc_si128(a, _mm_cmpeq_epi32(a, a)) +} +@(enable_target_feature="sse4.1") +_mm_test_mix_ones_zeros :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { + return _mm_testnzc_si128(a, mask) +} + + +when ODIN_ARCH == .amd64 { + @(enable_target_feature="sse4.1") + _mm_extract_epi64 :: #force_inline proc "c" (a: __m128i, $IMM1: u32) -> i64 { + return simd.extract(transmute(i64x2)a, IMM1) + } + + @(enable_target_feature="sse4.1") + _mm_insert_epi64 :: #force_inline proc "c" (a: __m128i, i: i64, $IMM1: u32) -> __m128i { + return transmute(__m128i)simd.replace(transmute(i64x2)a, IMM1, i) + } +} + + +@(private, default_calling_convention="c") +foreign _ { + @(link_name = "llvm.x86.sse41.pblendvb") + pblendvb :: proc(a, b: i8x16, mask: i8x16) -> i8x16 --- + @(link_name = "llvm.x86.sse41.blendvpd") + blendvpd :: proc(a, b, mask: __m128d) -> __m128d --- + @(link_name = "llvm.x86.sse41.blendvps") + blendvps :: proc(a, b, mask: __m128) -> __m128 --- + @(link_name = "llvm.x86.sse41.blendpd") + blendpd :: proc(a, b: __m128d, #const imm2: u8) -> __m128d --- + @(link_name = "llvm.x86.sse41.blendps") + blendps :: proc(a, b: __m128, #const imm4: u8) -> __m128 --- + @(link_name = "llvm.x86.sse41.pblendw") + pblendw :: proc(a: i16x8, b: i16x8, #const imm8: u8) -> i16x8 --- + @(link_name = "llvm.x86.sse41.insertps") + insertps :: proc(a, b: __m128, #const imm8: u8) -> __m128 --- + @(link_name = "llvm.x86.sse41.pmaxsb") + pmaxsb :: proc(a, b: i8x16) -> i8x16 --- + @(link_name = "llvm.x86.sse41.pmaxuw") + pmaxuw :: proc(a, b: u16x8) -> u16x8 --- + @(link_name = "llvm.x86.sse41.pmaxsd") + pmaxsd :: proc(a, b: i32x4) -> i32x4 --- + @(link_name = "llvm.x86.sse41.pmaxud") + pmaxud :: proc(a, b: u32x4) -> u32x4 --- + @(link_name = "llvm.x86.sse41.pminsb") + pminsb :: proc(a, b: i8x16) -> i8x16 --- + @(link_name = "llvm.x86.sse41.pminuw") + pminuw :: proc(a, b: u16x8) -> u16x8 --- + @(link_name = "llvm.x86.sse41.pminsd") + pminsd :: proc(a, b: i32x4) -> i32x4 --- + @(link_name = "llvm.x86.sse41.pminud") + pminud :: proc(a, b: u32x4) -> u32x4 --- + @(link_name = "llvm.x86.sse41.packusdw") + packusdw :: proc(a, b: i32x4) -> u16x8 --- + @(link_name = "llvm.x86.sse41.dppd") + dppd :: proc(a, b: __m128d, #const imm8: u8) -> __m128d --- + @(link_name = "llvm.x86.sse41.dpps") + dpps :: proc(a, b: __m128, #const imm8: u8) -> __m128 --- + @(link_name = "llvm.x86.sse41.round.pd") + roundpd :: proc(a: __m128d, rounding: i32) -> __m128d --- + @(link_name = "llvm.x86.sse41.round.ps") + roundps :: proc(a: __m128, rounding: i32) -> __m128 --- + @(link_name = "llvm.x86.sse41.round.sd") + roundsd :: proc(a, b: __m128d, rounding: i32) -> __m128d --- + @(link_name = "llvm.x86.sse41.round.ss") + roundss :: proc(a, b: __m128, rounding: i32) -> __m128 --- + @(link_name = "llvm.x86.sse41.phminposuw") + phminposuw :: proc(a: u16x8) -> u16x8 --- + @(link_name = "llvm.x86.sse41.pmuldq") + pmuldq :: proc(a, b: i32x4) -> i64x2 --- + @(link_name = "llvm.x86.sse41.mpsadbw") + mpsadbw :: proc(a, b: u8x16, #const imm8: u8) -> u16x8 --- + @(link_name = "llvm.x86.sse41.ptestz") + ptestz :: proc(a, mask: i64x2) -> i32 --- + @(link_name = "llvm.x86.sse41.ptestc") + ptestc :: proc(a, mask: i64x2) -> i32 --- + @(link_name = "llvm.x86.sse41.ptestnzc") + ptestnzc :: proc(a, mask: i64x2) -> i32 --- +} \ No newline at end of file From 5b42dd7707a8921321958934cf5d75ada1d8a006 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 15:27:09 +0100 Subject: [PATCH 240/254] Correct `@(require_results)` on parapoly procedures --- src/check_decl.cpp | 10 ++++++---- src/check_expr.cpp | 8 ++++++++ src/check_stmt.cpp | 10 +++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index d4818892b..86280b6cb 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1014,10 +1014,12 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } } - if (pt->result_count == 0 && ac.require_results) { - error(pl->type, "'require_results' is not needed on a procedure with no results"); - } else { - pt->require_results = ac.require_results; + if (ac.require_results) { + if (pt->result_count == 0) { + error(pl->type, "'require_results' is not needed on a procedure with no results"); + } else { + pt->require_results = true; + } } if (ac.link_name.len > 0) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 7fe9b8acf..f954f1583 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -442,6 +442,14 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, Entity *base_ final_proc_type->Proc.is_poly_specialized = true; final_proc_type->Proc.is_polymorphic = true; + final_proc_type->Proc.variadic = src->Proc.variadic; + final_proc_type->Proc.require_results = src->Proc.require_results; + final_proc_type->Proc.c_vararg = src->Proc.c_vararg; + final_proc_type->Proc.has_named_results = src->Proc.has_named_results; + final_proc_type->Proc.diverging = src->Proc.diverging; + final_proc_type->Proc.return_by_pointer = src->Proc.return_by_pointer; + final_proc_type->Proc.optional_ok = src->Proc.optional_ok; + for (isize i = 0; i < operands.count; i++) { Operand o = operands[i]; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f2c830c1b..b316f940f 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1405,12 +1405,12 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) { if (kind == Expr_Stmt) { return; } - Ast *expr = strip_or_return_expr(operand.expr); + Ast *expr = strip_or_return_expr(operand.expr); if (expr->kind == Ast_CallExpr) { AstCallExpr *ce = &expr->CallExpr; - Type *t = type_of_expr(ce->proc); - if (is_type_proc(t)) { + Type *t = base_type(type_of_expr(ce->proc)); + if (t->kind == Type_Proc) { if (t->Proc.require_results) { gbString expr_str = expr_to_string(ce->proc); error(node, "'%s' requires that its results must be handled", expr_str); @@ -1421,8 +1421,8 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) { } else if (expr->kind == Ast_SelectorCallExpr) { AstSelectorCallExpr *se = &expr->SelectorCallExpr; ast_node(ce, CallExpr, se->call); - Type *t = type_of_expr(ce->proc); - if (is_type_proc(t)) { + Type *t = base_type(type_of_expr(ce->proc)); + if (t->kind == Type_Proc) { if (t->Proc.require_results) { gbString expr_str = expr_to_string(ce->proc); error(node, "'%s' requires that its results must be handled", expr_str); From f3868ac932fcdae54e1009a6b39da37044add07c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 15:57:26 +0100 Subject: [PATCH 241/254] Improve missing handled results for built in procedures --- src/check_stmt.cpp | 46 +++++++++++++++++++++++++-------- src/checker.hpp | 1 + src/checker_builtin_procs.hpp | 48 +++++++++++++++++------------------ src/parser.hpp | 1 - 4 files changed, 61 insertions(+), 35 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index b316f940f..f061b4961 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1381,6 +1381,18 @@ bool all_operands_valid(Array const &operands) { return true; } +bool check_stmt_internal_builtin_proc_id(Ast *expr, BuiltinProcId *id_) { + BuiltinProcId id = BuiltinProc_Invalid; + Entity *e = entity_of_node(expr); + if (e != nullptr && e->kind == Entity_Builtin) { + if (e->Builtin.id && e->Builtin.id != BuiltinProc_DIRECTIVE) { + id = cast(BuiltinProcId)e->Builtin.id; + } + } + if (id_) *id_ = id; + return id != BuiltinProc_Invalid; +} + void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) { u32 mod_flags = flags & (~Stmt_FallthroughAllowed); switch (node->kind) { @@ -1408,26 +1420,40 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) { Ast *expr = strip_or_return_expr(operand.expr); if (expr->kind == Ast_CallExpr) { + BuiltinProcId builtin_id = BuiltinProc_Invalid; + bool do_require = false; + AstCallExpr *ce = &expr->CallExpr; Type *t = base_type(type_of_expr(ce->proc)); if (t->kind == Type_Proc) { - if (t->Proc.require_results) { - gbString expr_str = expr_to_string(ce->proc); - error(node, "'%s' requires that its results must be handled", expr_str); - gb_string_free(expr_str); - } + do_require = t->Proc.require_results; + } else if (check_stmt_internal_builtin_proc_id(ce->proc, &builtin_id)) { + auto const &bp = builtin_procs[builtin_id]; + do_require = bp.kind == Expr_Expr && !bp.ignore_results; + } + if (do_require) { + gbString expr_str = expr_to_string(ce->proc); + error(node, "'%s' requires that its results must be handled", expr_str); + gb_string_free(expr_str); } return; } else if (expr->kind == Ast_SelectorCallExpr) { + BuiltinProcId builtin_id = BuiltinProc_Invalid; + bool do_require = false; + AstSelectorCallExpr *se = &expr->SelectorCallExpr; ast_node(ce, CallExpr, se->call); Type *t = base_type(type_of_expr(ce->proc)); if (t->kind == Type_Proc) { - if (t->Proc.require_results) { - gbString expr_str = expr_to_string(ce->proc); - error(node, "'%s' requires that its results must be handled", expr_str); - gb_string_free(expr_str); - } + do_require = t->Proc.require_results; + } else if (check_stmt_internal_builtin_proc_id(ce->proc, &builtin_id)) { + auto const &bp = builtin_procs[builtin_id]; + do_require = bp.kind == Expr_Expr && !bp.ignore_results; + } + if (do_require) { + gbString expr_str = expr_to_string(ce->proc); + error(node, "'%s' requires that its results must be handled", expr_str); + gb_string_free(expr_str); } return; } diff --git a/src/checker.hpp b/src/checker.hpp index 8fc9e54c8..f11a00532 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -60,6 +60,7 @@ struct BuiltinProc { ExprKind kind; BuiltinProcPkg pkg; bool diverging; + bool ignore_results; // ignores require results handling }; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 2e27cc026..05f256775 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -385,26 +385,26 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("atomic_signal_fence"), 1, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("atomic_store"), 2, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("atomic_store_explicit"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_load_explicit"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_add_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_sub_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_and_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_nand_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_or_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_xor_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_exchange"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_exchange_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_compare_exchange_strong"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_compare_exchange_strong_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_compare_exchange_weak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("atomic_compare_exchange_weak_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("atomic_load"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_load_explicit"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_add"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_add_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_sub_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_and"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_and_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_nand"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_nand_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_or"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_or_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_xor"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_xor_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_exchange"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_exchange_explicit"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_compare_exchange_strong"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_compare_exchange_strong_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_compare_exchange_weak"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("atomic_compare_exchange_weak_explicit"), 5, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("fixed_point_mul"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fixed_point_div"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -477,7 +477,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("syscall"), 1, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("x86_cpuid"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("x86_xgetbv"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -565,12 +565,12 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("__entry_point"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, - {STR_LIT("objc_send"), 3, true, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("objc_send"), 3, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("objc_find_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, - {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, + {STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, {STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/parser.hpp b/src/parser.hpp index dc294b6ce..a648828fb 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -411,7 +411,6 @@ AST_KIND(_ExprBegin, "", bool) \ Token ellipsis; \ ProcInlining inlining; \ bool optional_ok_one; \ - i32 builtin_id; \ void *sce_temp_data; \ }) \ AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \ From 912d29af8321bb25d40916c2ec0b1709f6265a56 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 15:59:48 +0100 Subject: [PATCH 242/254] Add `@(require_results)` to all appropriate procedures --- core/simd/x86/abm.odin | 4 + core/simd/x86/adx.odin | 6 + core/simd/x86/pclmulqdq.odin | 2 +- core/simd/x86/rdtsc.odin | 2 + core/simd/x86/sha.odin | 14 +- core/simd/x86/sse.odin | 176 +++++++-------- core/simd/x86/sse2.odin | 406 +++++++++++++++++------------------ core/simd/x86/sse3.odin | 22 +- core/simd/x86/sse41.odin | 120 +++++------ core/simd/x86/ssse3.odin | 32 +-- 10 files changed, 398 insertions(+), 386 deletions(-) diff --git a/core/simd/x86/abm.odin b/core/simd/x86/abm.odin index f1898811f..5d7549ab3 100644 --- a/core/simd/x86/abm.odin +++ b/core/simd/x86/abm.odin @@ -3,17 +3,21 @@ package simd_x86 import "core:intrinsics" +@(require_results) _lzcnt_u32 :: #force_inline proc "c" (x: u32) -> u32 { return intrinsics.count_leading_zeros(x) } +@(require_results) _popcnt32 :: #force_inline proc "c" (x: u32) -> i32 { return i32(intrinsics.count_ones(x)) } when ODIN_ARCH == .amd64 { + @(require_results) _lzcnt_u64 :: #force_inline proc "c" (x: u64) -> u64 { return intrinsics.count_leading_zeros(x) } + @(require_results) _popcnt64 :: #force_inline proc "c" (x: u64) -> i32 { return i32(intrinsics.count_ones(x)) } diff --git a/core/simd/x86/adx.odin b/core/simd/x86/adx.odin index e73aa03a6..d03cffcff 100644 --- a/core/simd/x86/adx.odin +++ b/core/simd/x86/adx.odin @@ -1,14 +1,17 @@ //+build i386, amd64 package simd_x86 +@(require_results) _addcarry_u32 :: #force_inline proc "c" (c_in: u8, a: u32, b: u32, out: ^u32) -> u8 { x, y := llvm_addcarry_u32(c_in, a, b) out^ = y return x } +@(require_results) _addcarryx_u32 :: #force_inline proc "c" (c_in: u8, a: u32, b: u32, out: ^u32) -> u8 { return llvm_addcarryx_u32(c_in, a, b, out) } +@(require_results) _subborrow_u32 :: #force_inline proc "c" (c_in: u8, a: u32, b: u32, out: ^u32) -> u8 { x, y := llvm_subborrow_u32(c_in, a, b) out^ = y @@ -16,14 +19,17 @@ _subborrow_u32 :: #force_inline proc "c" (c_in: u8, a: u32, b: u32, out: ^u32) - } when ODIN_ARCH == .amd64 { + @(require_results) _addcarry_u64 :: #force_inline proc "c" (c_in: u8, a: u64, b: u64, out: ^u64) -> u8 { x, y := llvm_addcarry_u64(c_in, a, b) out^ = y return x } + @(require_results) _addcarryx_u64 :: #force_inline proc "c" (c_in: u8, a: u64, b: u64, out: ^u64) -> u8 { return llvm_addcarryx_u64(c_in, a, b, out) } + @(require_results) _subborrow_u64 :: #force_inline proc "c" (c_in: u8, a: u64, b: u64, out: ^u64) -> u8 { x, y := llvm_subborrow_u64(c_in, a, b) out^ = y diff --git a/core/simd/x86/pclmulqdq.odin b/core/simd/x86/pclmulqdq.odin index 8a665db03..692fb7ce1 100644 --- a/core/simd/x86/pclmulqdq.odin +++ b/core/simd/x86/pclmulqdq.odin @@ -1,7 +1,7 @@ //+build i386, amd64 package simd_x86 -@(enable_target_feature="pclmulqdq") +@(require_results, enable_target_feature="pclmulqdq") _mm_clmulepi64_si128 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i { return pclmulqdq(a, b, u8(IMM8)) } diff --git a/core/simd/x86/rdtsc.odin b/core/simd/x86/rdtsc.odin index 91dcc4ec9..54024c3f2 100644 --- a/core/simd/x86/rdtsc.odin +++ b/core/simd/x86/rdtsc.odin @@ -1,10 +1,12 @@ //+build i386, amd64 package simd_x86 +@(require_results) _rdtsc :: #force_inline proc "c" () -> u64 { return rdtsc() } +@(require_results) __rdtscp :: #force_inline proc "c" (aux: ^u32) -> u64 { return rdtscp(aux) } diff --git a/core/simd/x86/sha.odin b/core/simd/x86/sha.odin index 90f1d72ce..f015f4b8a 100644 --- a/core/simd/x86/sha.odin +++ b/core/simd/x86/sha.odin @@ -1,31 +1,31 @@ //+build i386, amd64 package simd_x86 -@(enable_target_feature="sha") +@(require_results, enable_target_feature="sha") _mm_sha1msg1_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha1msg1(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sha") +@(require_results, enable_target_feature="sha") _mm_sha1msg2_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha1msg2(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sha") +@(require_results, enable_target_feature="sha") _mm_sha1nexte_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha1nexte(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sha") +@(require_results, enable_target_feature="sha") _mm_sha1rnds4_epu32 :: #force_inline proc "c" (a, b: __m128i, $FUNC: u32) -> __m128i where 0 <= FUNC, FUNC <= 3 { return transmute(__m128i)sha1rnds4(transmute(i32x4)a, transmute(i32x4)b, u8(FUNC & 0xff)) } -@(enable_target_feature="sha") +@(require_results, enable_target_feature="sha") _mm_sha256msg1_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha256msg1(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sha") +@(require_results, enable_target_feature="sha") _mm_sha256msg2_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)sha256msg2(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sha") +@(require_results, enable_target_feature="sha") _mm_sha256rnds2_epu32 :: #force_inline proc "c" (a, b, k: __m128i) -> __m128i { return transmute(__m128i)sha256rnds2(transmute(i32x4)a, transmute(i32x4)b, transmute(i32x4)k) } diff --git a/core/simd/x86/sse.odin b/core/simd/x86/sse.odin index 6d8939b1b..3efdeccba 100644 --- a/core/simd/x86/sse.odin +++ b/core/simd/x86/sse.odin @@ -43,299 +43,299 @@ _MM_FLUSH_ZERO_ON :: 0x8000 _MM_FLUSH_ZERO_OFF :: 0x0000 -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_add_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return addss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_add_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.add(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_sub_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return subss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_sub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.sub(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_mul_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return mulss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_mul_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.mul(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_div_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return divss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_div_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.div(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_sqrt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return sqrtss(a) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_sqrt_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return sqrtps(a) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_rcp_ss :: #force_inline proc "c" (a: __m128) -> __m128 { return rcpss(a) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_rcp_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return rcpps(a) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_rsqrt_ss :: #force_inline proc "c" (a: __m128) -> __m128 { return rsqrtss(a) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_rsqrt_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return rsqrtps(a) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_min_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return minss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_min_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return minps(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_max_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return maxss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_max_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return maxps(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_and_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return transmute(__m128)simd.and(transmute(__m128i)a, transmute(__m128i)b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_andnot_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return transmute(__m128)simd.and_not(transmute(__m128i)a, transmute(__m128i)b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_or_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return transmute(__m128)simd.or(transmute(__m128i)a, transmute(__m128i)b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_xor_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return transmute(__m128)simd.xor(transmute(__m128i)a, transmute(__m128i)b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpeq_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 0) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmplt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 1) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmple_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 2) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpgt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, cmpss(b, a, 1), 4, 1, 2, 3) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpge_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, cmpss(b, a, 2), 4, 1, 2, 3) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpneq_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 4) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpnlt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 5) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpnle_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 6) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpngt_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, cmpss(b, a, 5), 4, 1, 2, 3) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpnge_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, cmpss(b, a, 6), 4, 1, 2, 3) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpord_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 7) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpunord_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpss(a, b, 3) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpeq_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 0) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmplt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 1) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmple_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 2) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpgt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 1) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpge_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 2) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpneq_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 4) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpnlt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 5) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpnle_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(a, b, 6) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpngt_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 5) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpnge_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 6) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpord_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 7) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cmpunord_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return cmpps(b, a, 3) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_comieq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comieq_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_comilt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comilt_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_comile_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comile_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_comigt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comigt_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_comige_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comige_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_comineq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return comineq_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_ucomieq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomieq_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_ucomilt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomilt_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_ucomile_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomile_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_ucomigt_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomigt_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_ucomige_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomige_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_ucomineq_ss :: #force_inline proc "c" (a, b: __m128) -> b32 { return ucomineq_ss(a, b) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cvtss_si32 :: #force_inline proc "c" (a: __m128) -> i32 { return cvtss2si(a) } _mm_cvt_ss2si :: _mm_cvtss_si32 _mm_cvttss_si32 :: _mm_cvtss_si32 -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cvtss_f32 :: #force_inline proc "c" (a: __m128) -> f32 { return simd.extract(a, 0) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_cvtsi32_ss :: #force_inline proc "c" (a: __m128, b: i32) -> __m128 { return cvtsi2ss(a, b) } _mm_cvt_si2ss :: _mm_cvtsi32_ss -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_set_ss :: #force_inline proc "c" (a: f32) -> __m128 { return __m128{a, 0, 0, 0} } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_set1_ps :: #force_inline proc "c" (a: f32) -> __m128 { return __m128(a) } _mm_set_ps1 :: _mm_set1_ps -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_set_ps :: #force_inline proc "c" (a, b, c, d: f32) -> __m128 { return __m128{d, c, b, a} } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_setr_ps :: #force_inline proc "c" (a, b, c, d: f32) -> __m128 { return __m128{a, b, c, d} } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_setzero_ps :: #force_inline proc "c" () -> __m128 { return __m128{0, 0, 0, 0} } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_shuffle_ps :: #force_inline proc "c" (a, b: __m128, $MASK: u32) -> __m128 { return simd.shuffle( a, b, @@ -346,58 +346,58 @@ _mm_shuffle_ps :: #force_inline proc "c" (a, b: __m128, $MASK: u32) -> __m128 { } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_unpackhi_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 2, 6, 3, 7) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_unpacklo_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 0, 4, 1, 5) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_movehl_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 6, 7, 2, 3) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_movelh_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 0, 1, 4, 5) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_movemask_ps :: #force_inline proc "c" (a: __m128) -> u32 { return movmskps(a) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_load_ss :: #force_inline proc "c" (p: ^f32) -> __m128 { return __m128{p^, 0, 0, 0} } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_load1_ps :: #force_inline proc "c" (p: ^f32) -> __m128 { a := p^ return __m128(a) } _mm_load_ps1 :: _mm_load1_ps -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_load_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { return (^__m128)(p)^ } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_loadu_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { dst := _mm_undefined_ps() intrinsics.mem_copy_non_overlapping(&dst, p, size_of(__m128)) return dst } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_loadr_ps :: #force_inline proc "c" (p: [^]f32) -> __m128 { return simd.lanes_reverse(_mm_load_ps(p)) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_loadu_si64 :: #force_inline proc "c" (mem_addr: rawptr) -> __m128i { a := intrinsics.unaligned_load((^i64)(mem_addr)) return __m128i{a, 0} @@ -431,7 +431,7 @@ _mm_storer_ps :: #force_inline proc "c" (p: [^]f32, a: __m128) { } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_move_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return simd.shuffle(a, b, 4, 1, 2, 3) } @@ -441,7 +441,7 @@ _mm_sfence :: #force_inline proc "c" () { sfence() } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_getcsr :: #force_inline proc "c" () -> (result: u32) { stmxcsr(&result) return result @@ -453,19 +453,19 @@ _mm_setcsr :: #force_inline proc "c" (val: u32) { ldmxcsr(&val) } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _MM_GET_EXCEPTION_MASK :: #force_inline proc "c" () -> u32 { return _mm_getcsr() & _MM_MASK_MASK } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _MM_GET_EXCEPTION_STATE :: #force_inline proc "c" () -> u32 { return _mm_getcsr() & _MM_EXCEPT_MASK } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _MM_GET_FLUSH_ZERO_MODE :: #force_inline proc "c" () -> u32 { return _mm_getcsr() & _MM_FLUSH_ZERO_MASK } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _MM_GET_ROUNDING_MODE :: #force_inline proc "c" () -> u32 { return _mm_getcsr() & _MM_ROUND_MASK } @@ -493,7 +493,7 @@ _mm_prefetch :: #force_inline proc "c" (p: rawptr, $STRATEGY: u32) { } -@(enable_target_feature="sse") +@(require_results, enable_target_feature="sse") _mm_undefined_ps :: #force_inline proc "c" () -> __m128 { return _mm_set1_ps(0) } @@ -517,15 +517,15 @@ _mm_stream_ps :: #force_inline proc "c" (addr: [^]f32, a: __m128) { } when ODIN_ARCH == .amd64 { - @(enable_target_feature="sse") + @(require_results, enable_target_feature="sse") _mm_cvtss_si64 :: #force_inline proc "c"(a: __m128) -> i64 { return cvtss2si64(a) } - @(enable_target_feature="sse") + @(require_results, enable_target_feature="sse") _mm_cvttss_si64 :: #force_inline proc "c"(a: __m128) -> i64 { return cvttss2si64(a) } - @(enable_target_feature="sse") + @(require_results, enable_target_feature="sse") _mm_cvtsi64_ss :: #force_inline proc "c"(a: __m128, b: i64) -> __m128 { return cvtsi642ss(a, b) } diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index d15df8120..f33bd2195 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -21,118 +21,118 @@ _mm_mfence :: #force_inline proc "c" () { mfence() } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_add_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_add_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_add_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_add_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add(transmute(i64x2)a, transmute(i64x2)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_adds_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add_sat(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_adds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add_sat(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_adds_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add_sat(transmute(u8x16)a, transmute(u8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_adds_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.add_sat(transmute(u16x8)a, transmute(u16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_avg_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pavgb(transmute(u8x16)a, transmute(u8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_avg_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pavgw(transmute(u16x8)a, transmute(u16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_madd_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaddwd(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_max_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaxsw(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_max_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaxub(transmute(u8x16)a, transmute(u8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_min_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pminsw(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_min_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pminub(transmute(u8x16)a, transmute(u8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_mulhi_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmulhw(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_mulhi_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmulhuw(transmute(u16x8)a, transmute(u16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_mullo_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.mul(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_mul_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmuludq(transmute(u32x4)a, transmute(u32x4)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sad_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psadbw(transmute(u8x16)a, transmute(u8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sub_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sub_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sub_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sub_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub(transmute(i64x2)a, transmute(i64x2)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_subs_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_subs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_subs_epu8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(u8x16)a, transmute(u8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.sub_sat(transmute(u16x8)a, transmute(u16x8)b) } @@ -140,7 +140,7 @@ _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { @(private) -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 & 0xff @@ -167,7 +167,7 @@ _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128 } @(private) -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 return transmute(__m128i)simd.shuffle( @@ -193,233 +193,233 @@ _mm_srli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128 } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_slli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_slli_si128_impl(a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_bslli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_slli_si128_impl(a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_bsrli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_srli_si128_impl(a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_slli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)pslliw(transmute(i16x8)a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sll_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psllw(transmute(i16x8)a, transmute(i16x8)count) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_slli_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psllid(transmute(i32x4)a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sll_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)pslld(transmute(i32x4)a, transmute(i32x4)count) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_slli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)pslliq(transmute(i64x2)a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sll_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psllq(transmute(i64x2)a, transmute(i64x2)count) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srai_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psraiw(transmute(i16x8)a. IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sra_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psraw(transmute(i16x8)a, transmute(i16x8)count) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srai_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psraid(transmute(i32x4)a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sra_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrad(transmute(i32x4)a, transmute(i32x4)count) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srli_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return _mm_srli_si128_impl(a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srli_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrliw(transmute(i16x8)a. IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srl_epi16 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrlw(transmute(i16x8)a, transmute(i16x8)count) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srli_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrlid(transmute(i32x4)a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srl_epi32 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrld(transmute(i32x4)a, transmute(i32x4)count) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srli_epi64 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { return transmute(__m128i)psrliq(transmute(i64x2)a, IMM8) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_srl_epi64 :: #force_inline proc "c" (a, count: __m128i) -> __m128i { return transmute(__m128i)psrlq(transmute(i64x2)a, transmute(i64x2)count) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_and_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return simd.and(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_andnot_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return simd.and_not(b, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_or_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return simd.or(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_xor_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return simd.xor(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpeq_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_eq(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpeq_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_eq(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpeq_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_eq(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpgt_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_gt(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpgt_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_gt(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpgt_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_gt(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmplt_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_lt(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmplt_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_lt(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmplt_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_lt(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtepi32_pd :: #force_inline proc "c" (a: __m128i) -> __m128d { v := transmute(i32x4)a return cast(__m128d)simd.shuffle(v, v, 0, 1) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtsi32_sd :: #force_inline proc "c" (a: __m128d, b: i32) -> __m128d { return simd.replace(a, 0, f64(b)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtepi32_ps :: #force_inline proc "c" (a: __m128i) -> __m128 { return cvtdq2ps(transmute(i32x4)a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtps_epi32 :: #force_inline proc "c" (a: __m128) -> __m128i { return transmute(__m128i)cvtps2dq(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtsi32_si128 :: #force_inline proc "c" (a: i32) -> __m128i { return transmute(__m128i)i32x4{a, 0, 0, 0} } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtsi128_si32 :: #force_inline proc "c" (a: __m128i) -> i32 { return simd.extract(transmute(i32x4)a, 0) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set_epi64x :: #force_inline proc "c" (e1, e0: i64) -> __m128i { return transmute(__m128i)i64x2{e0, e1} } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set_epi32 :: #force_inline proc "c" (e3, e2, e1, e0: i32) -> __m128i { return transmute(__m128i)i32x4{e0, e1, e2, e3} } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set_epi16 :: #force_inline proc "c" (e7, e6, e5, e4, e3, e2, e1, e0: i16) -> __m128i { return transmute(__m128i)i16x8{e0, e1, e2, e3, e4, e5, e6, e7} } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set_epi8 :: #force_inline proc "c" (e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0: i8) -> __m128i { return transmute(__m128i)i8x16{e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15} } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set1_epi64x :: #force_inline proc "c" (a: i64) -> __m128i { return _mm_set_epi64x(a, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set1_epi32 :: #force_inline proc "c" (a: i32) -> __m128i { return _mm_set_epi32(a, a, a, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set1_epi16 :: #force_inline proc "c" (a: i16) -> __m128i { return _mm_set_epi16(a, a, a, a, a, a, a, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set1_epi8 :: #force_inline proc "c" (a: i8) -> __m128i { return _mm_set_epi8(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_setr_epi32 :: #force_inline proc "c" (e3, e2, e1, e0: i32) -> __m128i { return _mm_set_epi32(e0, e1, e2, e3) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_setr_epi16 :: #force_inline proc "c" (e7, e6, e5, e4, e3, e2, e1, e0: i16) -> __m128i { return _mm_set_epi16(e0, e1, e2, e3, e4, e5, e6, e7) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_setr_epi8 :: #force_inline proc "c" (e15, e14, e13, e12, e11, e10, e9, e8, e7, e6, e5, e4, e3, e2, e1, e0: i8) -> __m128i { return _mm_set_epi8(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_setzero_si128 :: #force_inline proc "c" () -> __m128i { return _mm_set1_epi64x(0) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_loadl_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { return _mm_set_epi64x(0, intrinsics.unaligned_load((^i64)(mem_addr))) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_load_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { return mem_addr^ } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_loadu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { dst := _mm_undefined_si128() intrinsics.mem_copy_non_overlapping(&dst, mem_addr, size_of(__m128i)) @@ -450,7 +450,7 @@ _mm_stream_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { _mm_stream_si32 :: #force_inline proc "c" (mem_addr: ^i32, a: i32) { intrinsics.non_temporal_store(mem_addr, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { zero := _mm_setzero_si128() return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)zero, 0, 2) @@ -459,31 +459,31 @@ _mm_move_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_packs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)packsswb(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_packs_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)packssdw(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_packus_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)packuswb(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_extract_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> i32 { return i32(simd.extract(transmute(u16x8)a, IMM8)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_insert_epi16 :: #force_inline proc "c" (a: __m128i, i: i32, $IMM8: u32) -> __m128i { return i32(simd.replace(transmute(u16x8)a, IMM8, i16(i))) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_movemask_epi8 :: #force_inline proc "c" (a: __m128i) -> i32 { return pmovmskb(transmute(i8x16)a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_shuffle_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { v := transmute(i32x4)a return transmute(__m128i)simd.shuffle( @@ -495,7 +495,7 @@ _mm_shuffle_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i (IMM8 >> 6) & 0b11, ) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_shufflehi_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { v := transmute(i16x8)a return transmute(__m128i)simd.shuffle( @@ -511,7 +511,7 @@ _mm_shufflehi_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128 ((IMM8 >> 6) & 0b11) + 4, ) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_shufflelo_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { v := transmute(i16x8)a return transmute(__m128i)simd.shuffle( @@ -527,7 +527,7 @@ _mm_shufflelo_epi16 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128 7, ) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpackhi_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle( transmute(i8x16)a, @@ -535,19 +535,19 @@ _mm_unpackhi_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31, ) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpackhi_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i16x8)a, transmute(i16x8)b, 4, 12, 5, 13, 6, 14, 7, 15) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpackhi_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i32x4)a, transmute(i32x4)b, 2, 6, 3, 7) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpackhi_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 1, 3) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpacklo_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle( transmute(i8x16)a, @@ -555,15 +555,15 @@ _mm_unpacklo_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23, ) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpacklo_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i16x8)a, transmute(i16x8)b, 0, 8, 1, 9, 2, 10, 3, 11) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpacklo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i32x4)a, transmute(i32x4)b, 0, 4, 1, 5) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpacklo_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.shuffle(transmute(i64x2)a, transmute(i64x2)b, 0, 2) } @@ -571,75 +571,75 @@ _mm_unpacklo_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_add_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(a) + _mm_cvtsd_f64(b)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_add_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.add(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_div_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(a) / _mm_cvtsd_f64(b)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_div_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.div(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_max_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return maxsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_max_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return maxpd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_min_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return minsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_min_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return minpd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_mul_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(a) * _mm_cvtsd_f64(b)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_mul_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.mul(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sqrt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(sqrtsd(b))) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sqrt_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { return simd.sqrt(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sub_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(a, 0, _mm_cvtsd_f64(a) - _mm_cvtsd_f64(b)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_sub_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.sub(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_and_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return transmute(__m128d)_mm_and_si128(transmute(__m128i)a, transmute(__m128i)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_andnot_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return transmute(__m128d)_mm_andnot_si128(transmute(__m128i)a, transmute(__m128i)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_or_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return transmute(__m128d)_mm_or_si128(transmute(__m128i)a, transmute(__m128i)b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_xor_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return transmute(__m128d)_mm_xor_si128(transmute(__m128i)a, transmute(__m128i)b) } @@ -647,147 +647,147 @@ _mm_xor_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpeq_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 0) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmplt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 1) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmple_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 2) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpgt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(_mm_cmplt_sd(b, a), 1, simd.extract(a, 1)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpge_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(_mm_cmple_sd(b, a), 1, simd.extract(a, 1)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpord_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 7) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpunord_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 3) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpneq_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 4) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpnlt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 5) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpnle_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmpsd(a, b, 6) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpngt_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(_mm_cmpnlt_sd(b, a), 1, simd.extract(a, 1)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpnge_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.replace(_mm_cmpnle_sd(b, a), 1, simd.extract(a, 1)) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpeq_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 0) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmplt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 1) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmple_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 2) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpgt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_cmplt_pd(b, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpge_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_cmple_pd(b, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpord_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 7) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpunord_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 3) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpneq_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 4) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpnlt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 5) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpnle_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return cmppd(a, b, 6) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpngt_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_cmpnlt_pd(b, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cmpnge_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_cmpnle_pd(b, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_comieq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comieqsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_comilt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comiltsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_comile_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comilesd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_comigt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comigtsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_comige_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comigesd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_comineq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return comineqsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_ucomieq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomieqsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_ucomilt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomiltsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_ucomile_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomilesd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_ucomigt_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomigtsd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_ucomige_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomigesd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_ucomineq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { return ucomineqsd(a, b) } @@ -796,87 +796,87 @@ _mm_ucomineq_sd :: #force_inline proc "c" (a, b: __m128d) -> i32 { -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { return cvtpd2ps(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtps_pd :: #force_inline proc "c" (a: __m128) -> __m128d { return cvtps2pd(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtpd_epi32 :: #force_inline proc "c" (a: __m128d) -> __m128i { return transmute(__m128i)cvtpd2dq(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtsd_si32 :: #force_inline proc "c" (a: __m128d) -> i32 { return cvtsd2si(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtsd_ss :: #force_inline proc "c" (a, b: __m128d) -> __m128 { return cvtsd2ss(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtsd_f64 :: #force_inline proc "c" (a: __m128d) -> f64 { return simd.extract(a, 0) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvtss_sd :: #force_inline proc "c" (a, b: __m128) -> __m128d { return cvtss2sd(a, b) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvttpd_epi32 :: #force_inline proc "c" (a: __m128d) -> __m128i { return transmute(__m128i)cvttpd2dq(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvttsd_si32 :: #force_inline proc "c" (a: __m128d) -> i32 { return cvttsd2si(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_cvttps_epi32 :: #force_inline proc "c" (a: __m128) -> __m128i { return transmute(__m128i)cvttps2dq(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set_sd :: #force_inline proc "c" (a: f64) -> __m128d { return _mm_set_pd(0.0, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set1_pd :: #force_inline proc "c" (a: f64) -> __m128d { return _mm_set_pd(a, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set_pd1 :: #force_inline proc "c" (a: f64) -> __m128d { return _mm_set_pd(a, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_set_pd :: #force_inline proc "c" (a: f64, b: f64) -> __m128d { return __m128d{b, a} } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_setr_pd :: #force_inline proc "c" (a: f64, b: f64) -> __m128d { return _mm_set_pd(b, a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_setzero_pd :: #force_inline proc "c" () -> __m128d { return _mm_set_pd(0.0, 0.0) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_movemask_pd :: #force_inline proc "c" (a: __m128d) -> i32 { return movmskpd(a) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_load_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { return (^__m128d)(mem_addr)^ } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_load_sd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { return _mm_setr_pd(mem_addr^, 0.) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_loadh_pd :: #force_inline proc "c" (a: __m128d, mem_addr: ^f64) -> __m128d { return _mm_setr_pd(simd.extract(a, 0), mem_addr^) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_loadl_pd :: #force_inline proc "c" (a: __m128d, mem_addr: ^f64) -> __m128d { return _mm_setr_pd(mem_addr^, simd.extract(a, 1)) } @@ -916,31 +916,31 @@ _mm_storeh_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { _mm_storel_pd :: #force_inline proc "c" (mem_addr: ^f64, a: __m128d) { mem_addr^ = simd.extract(a, 0) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_load1_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { d := mem_addr^ return _mm_setr_pd(d, d) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_load_pd1 :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { return _mm_load1_pd(mem_addr) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_loadr_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { a := _mm_load_pd(mem_addr) return simd.shuffle(a, a, 1, 0) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_loadu_pd :: #force_inline proc "c" (mem_addr: ^f64) -> __m128d { dst := _mm_undefined_pd() intrinsics.mem_copy_non_overlapping(&dst, mem_addr, size_of(__m128d)) return dst } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_shuffle_pd :: #force_inline proc "c" (a, b: __m128d, $MASK: u32) -> __m128d { return simd.shuffle(a, b, MASK&0b1, ((MASK>>1)&0b1) + 2) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_move_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return _mm_setr_pd(simd.extract(b, 0), simd.extract(a, 1)) } @@ -948,64 +948,64 @@ _mm_move_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_castpd_ps :: #force_inline proc "c" (a: __m128d) -> __m128 { return transmute(__m128)a } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_castpd_si128 :: #force_inline proc "c" (a: __m128d) -> __m128i { return transmute(__m128i)a } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_castps_pd :: #force_inline proc "c" (a: __m128) -> __m128d { return transmute(__m128d)a } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_castps_si128 :: #force_inline proc "c" (a: __m128) -> __m128i { return transmute(__m128i)a } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_castsi128_pd :: #force_inline proc "c" (a: __m128i) -> __m128d { return transmute(__m128d)a } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_castsi128_ps :: #force_inline proc "c" (a: __m128i) -> __m128 { return transmute(__m128)a } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_undefined_pd :: #force_inline proc "c" () -> __m128d { return __m128d{0, 0} } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_undefined_si128 :: #force_inline proc "c" () -> __m128i { return __m128i{0, 0} } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpackhi_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.shuffle(a, b, 1, 3) } -@(enable_target_feature="sse2") +@(require_results, enable_target_feature="sse2") _mm_unpacklo_pd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return simd.shuffle(a, b, 0, 2) } when ODIN_ARCH == .amd64 { - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvtsd_si64 :: #force_inline proc "c" (a: __m128d) -> i64 { return cvtsd2si64(a) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvtsd_si64x :: #force_inline proc "c" (a: __m128d) -> i64 { return _mm_cvtsd_si64(a) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvttsd_si64 :: #force_inline proc "c" (a: __m128d) -> i64 { return cvttsd2si64(a) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvttsd_si64x :: #force_inline proc "c" (a: __m128d) -> i64 { return _mm_cvttsd_si64(a) } @@ -1013,27 +1013,27 @@ when ODIN_ARCH == .amd64 { _mm_stream_si64 :: #force_inline proc "c" (mem_addr: ^i64, a: i64) { intrinsics.non_temporal_store(mem_addr, a) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvtsi64_si128 :: #force_inline proc "c" (a: i64) -> __m128i { return _mm_set_epi64x(0, a) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvtsi64x_si128 :: #force_inline proc "c" (a: i64) -> __m128i { return _mm_cvtsi64_si128(a) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvtsi128_si64 :: #force_inline proc "c" (a: __m128i) -> i64 { return simd.extract(transmute(i64x2)a, 0) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvtsi128_si64x :: #force_inline proc "c" (a: __m128i) -> i64 { return _mm_cvtsi128_si64(a) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvtsi64_sd :: #force_inline proc "c" (a: __m128d, b: i64) -> __m128d { return simd.replace(a, 0, f64(b)) } - @(enable_target_feature="sse2") + @(require_results, enable_target_feature="sse2") _mm_cvtsi64x_sd :: #force_inline proc "c" (a: __m128d, b: i64) -> __m128d { return _mm_cvtsi64_sd(a, b) } diff --git a/core/simd/x86/sse3.odin b/core/simd/x86/sse3.odin index 370bfa952..7a3073c18 100644 --- a/core/simd/x86/sse3.odin +++ b/core/simd/x86/sse3.odin @@ -4,47 +4,47 @@ package simd_x86 import "core:intrinsics" import "core:simd" -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_addsub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return addsubps(a, b) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_addsub_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { return addsubpd(a, b) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_hadd_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { return haddpd(a, b) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_hadd_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return haddps(a, b) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_hsub_pd :: #force_inline proc "c" (a: __m128d, b: __m128d) -> __m128d { return hsubpd(a, b) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_hsub_ps :: #force_inline proc "c" (a, b: __m128) -> __m128 { return hsubps(a, b) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_lddqu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i) -> __m128i { return transmute(__m128i)lddqu(mem_addr) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_movedup_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { return simd.shuffle(a, a, 0, 0) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_loaddup_pd :: #force_inline proc "c" (mem_addr: [^]f64) -> __m128d { return _mm_load1_pd(mem_addr) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_movehdup_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return simd.shuffle(a, a, 1, 1, 3, 3) } -@(enable_target_feature="sse3") +@(require_results, enable_target_feature="sse3") _mm_moveldup_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return simd.shuffle(a, a, 0, 0, 2, 2) } diff --git a/core/simd/x86/sse41.odin b/core/simd/x86/sse41.odin index 516e0bdc2..b35be33f2 100644 --- a/core/simd/x86/sse41.odin +++ b/core/simd/x86/sse41.odin @@ -20,271 +20,271 @@ _MM_FROUND_NEARBYINT :: _MM_FROUND_NO_EXC | _MM_FROUND_CUR_DIRECTION -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_blendv_epi8 :: #force_inline proc "c" (a, b, mask: __m128i) -> __m128i { return transmute(__m128i)pblendvb(transmute(i8x16)a, transmute(i8x16)b, transmute(i8x16)mask) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_blend_epi16 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i { return transmute(__m128i)pblendw(transmute(i16x8)a, transmute(i16x8)b, IMM8) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_blendv_pd :: #force_inline proc "c" (a, b, mask: __m128d) -> __m128d { return blendvpd(a, b, mask) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_blendv_ps :: #force_inline proc "c" (a, b, mask: __m128) -> __m128 { return blendvps(a, b, mask) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_blend_pd :: #force_inline proc "c" (a, b: __m128d, $IMM2: u8) -> __m128d { return blendpd(a, b, IMM2) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_blend_ps :: #force_inline proc "c" (a, b: __m128, $IMM4: u8) -> __m128 { return blendps(a, b, IMM4) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_extract_ps :: #force_inline proc "c" (a: __m128, $IMM8: u32) -> i32 { return transmute(i32)simd.extract(a, IMM8) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_extract_epi8 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> i32 { return i32(simd.extract(transmute(u8x16)a, IMM8)) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_extract_epi32 :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> i32 { return simd.extract(transmute(i32x4)a, IMM8) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_insert_ps :: #force_inline proc "c" (a, b: __m128, $IMM8: u8) -> __m128 { return insertps(a, b, IMM8) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_insert_epi8 :: #force_inline proc "c" (a: __m128i, i: i32, $IMM8: u32) -> __m128i { return transmute(__m128i)simd.replace(transmute(i8x16)a, IMM8, i8(i)) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_insert_epi32 :: #force_inline proc "c" (a: __m128i, i: i32, $IMM8: u32) -> __m128i { return transmute(__m128i)simd.replace(transmute(i32x4)a, IMM8, i) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_max_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaxsb(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_max_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaxuw(transmute(u16x8)a, transmute(u16x8)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_max_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaxsd(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_max_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaxud(transmute(u32x4)a, transmute(u32x4)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_min_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pminsb(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_min_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pminuw(transmute(u16x8)a, transmute(u16x8)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_min_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pminsd(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_min_epu32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pminud(transmute(u32x4)a, transmute(u32x4)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_packus_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)packusdw(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cmpeq_epi64 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.lanes_eq(transmute(i64x2)a, transmute(i64x2)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepi8_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i8x16)a y := simd.shuffle(x, x, 0, 1, 2, 3, 4, 5, 6, 7) return transmute(__m128i)i16x8(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepi8_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i8x16)a y := simd.shuffle(x, x, 0, 1, 2, 3) return transmute(__m128i)i32x4(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepi8_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i8x16)a y := simd.shuffle(x, x, 0, 1) return transmute(__m128i)i64x2(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepi16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i16x8)a y := simd.shuffle(x, x, 0, 1, 2, 3) return transmute(__m128i)i32x4(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepi16_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i16x8)a y := simd.shuffle(x, x, 0, 1) return transmute(__m128i)i64x2(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepi32_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(i32x4)a y := simd.shuffle(x, x, 0, 1) return transmute(__m128i)i64x2(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepu8_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u8x16)a y := simd.shuffle(x, x, 0, 1, 2, 3, 4, 5, 6, 7) return transmute(__m128i)i16x8(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepu8_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u8x16)a y := simd.shuffle(x, x, 0, 1, 2, 3) return transmute(__m128i)i32x4(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepu8_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u8x16)a y := simd.shuffle(x, x, 0, 1) return transmute(__m128i)i64x2(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepu16_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u16x8)a y := simd.shuffle(x, x, 0, 1, 2, 3) return transmute(__m128i)i32x4(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepu16_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u16x8)a y := simd.shuffle(x, x, 0, 1) return transmute(__m128i)i64x2(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_cvtepu32_epi64 :: #force_inline proc "c" (a: __m128i) -> __m128i { x := transmute(u32x4)a y := simd.shuffle(x, x, 0, 1) return transmute(__m128i)i64x2(y) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_dp_pd :: #force_inline proc "c" (a, b: __m128d, $IMM8: u8) -> __m128d { return dppd(a, b, IMM8) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_dp_ps :: #force_inline proc "c" (a, b: __m128, $IMM8: u8) -> __m128 { return dpps(a, b, IMM8) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_floor_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { return simd.floor(a) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_floor_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return simd.floor(a) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_floor_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return roundsd(a, b, _MM_FROUND_FLOOR) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_floor_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return roundss(a, b, _MM_FROUND_FLOOR) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_ceil_pd :: #force_inline proc "c" (a: __m128d) -> __m128d { return simd.ceil(a) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_ceil_ps :: #force_inline proc "c" (a: __m128) -> __m128 { return simd.ceil(a) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_ceil_sd :: #force_inline proc "c" (a, b: __m128d) -> __m128d { return roundsd(a, b, _MM_FROUND_CEIL) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_ceil_ss :: #force_inline proc "c" (a, b: __m128) -> __m128 { return roundss(a, b, _MM_FROUND_CEIL) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_round_pd :: #force_inline proc "c" (a: __m128d, $ROUNDING: i32) -> __m128d { return roundpd(a, ROUNDING) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_round_ps :: #force_inline proc "c" (a: __m128, $ROUNDING: i32) -> __m128 { return roundps(a, ROUNDING) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_round_sd :: #force_inline proc "c" (a, b: __m128d, $ROUNDING: i32) -> __m128d { return roundsd(a, b, ROUNDING) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_round_ss :: #force_inline proc "c" (a, b: __m128, $ROUNDING: i32) -> __m128 { return roundss(a, b, ROUNDING) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_minpos_epu16 :: #force_inline proc "c" (a: __m128i) -> __m128i { return transmute(__m128i)phminposuw(transmute(u16x8)a) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_mul_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmuldq(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_mullo_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)simd.mul(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_mpsadbw_epu8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u8) -> __m128i { return transmute(__m128i)mpsadbw(transmute(u8x16)a, transmute(u8x16)b, IMM8) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_testz_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { return ptestz(transmute(i64x2)a, transmute(i64x2)mask) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_testc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { return ptestc(transmute(i64x2)a, transmute(i64x2)mask) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_testnzc_si128 :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { return ptestnzc(transmute(i64x2)a, transmute(i64x2)mask) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_test_all_zeros :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { return _mm_testz_si128(a, mask) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_test_all_ones :: #force_inline proc "c" (a: __m128i) -> i32 { return _mm_testc_si128(a, _mm_cmpeq_epi32(a, a)) } -@(enable_target_feature="sse4.1") +@(require_results, enable_target_feature="sse4.1") _mm_test_mix_ones_zeros :: #force_inline proc "c" (a: __m128i, mask: __m128i) -> i32 { return _mm_testnzc_si128(a, mask) } when ODIN_ARCH == .amd64 { - @(enable_target_feature="sse4.1") + @(require_results, enable_target_feature="sse4.1") _mm_extract_epi64 :: #force_inline proc "c" (a: __m128i, $IMM1: u32) -> i64 { return simd.extract(transmute(i64x2)a, IMM1) } - @(enable_target_feature="sse4.1") + @(require_results, enable_target_feature="sse4.1") _mm_insert_epi64 :: #force_inline proc "c" (a: __m128i, i: i64, $IMM1: u32) -> __m128i { return transmute(__m128i)simd.replace(transmute(i64x2)a, IMM1, i) } diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index 8c677aed4..f11ef6774 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -5,23 +5,23 @@ import "core:intrinsics" import "core:simd" _ :: simd -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_abs_epi8 :: #force_inline proc "c" (a: __m128i) -> __m128i { return transmute(__m128i)pabsb128(transmute(i8x16)a) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_abs_epi16 :: #force_inline proc "c" (a: __m128i) -> __m128i { return transmute(__m128i)pabsw128(transmute(i16x8)a) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_abs_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { return transmute(__m128i)pabsd128(transmute(i32x4)a) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_shuffle_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pshufb128(transmute(u8x16)a, transmute(u8x16)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_alignr_epi8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 @@ -58,47 +58,47 @@ _mm_alignr_epi8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u32) -> __m128i } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_hadd_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phaddw128(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_hadds_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phaddsw128(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_hadd_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phaddd128(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_hsub_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phsubw128(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_hsubs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phsubsw128(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_hsub_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)phsubd128(transmute(i32x4)a, transmute(i32x4)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_maddubs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmaddubsw128(transmute(u8x16)a, transmute(i8x16)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_mulhrs_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pmulhrsw128(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_sign_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psignb128(transmute(i8x16)a, transmute(i8x16)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_sign_epi16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psignw128(transmute(i16x8)a, transmute(i16x8)b) } -@(enable_target_feature="ssse3") +@(require_results, enable_target_feature="ssse3") _mm_sign_epi32 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)psignd128(transmute(i32x4)a, transmute(i32x4)b) } From 68222cb8ab1dc4a8581677cfeb872690d885f3ea Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 16:06:31 +0100 Subject: [PATCH 243/254] Add SSE4.2 --- core/simd/x86/sse42.odin | 149 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 core/simd/x86/sse42.odin diff --git a/core/simd/x86/sse42.odin b/core/simd/x86/sse42.odin new file mode 100644 index 000000000..62b4f0478 --- /dev/null +++ b/core/simd/x86/sse42.odin @@ -0,0 +1,149 @@ +//+build i386, amd64 +package simd_x86 + +import "core:simd" + +_SIDD_UBYTE_OPS :: 0b0000_0000 +_SIDD_UWORD_OPS :: 0b0000_0001 +_SIDD_SBYTE_OPS :: 0b0000_0010 +_SIDD_SWORD_OPS :: 0b0000_0011 + +_SIDD_CMP_EQUAL_ANY :: 0b0000_0000 +_SIDD_CMP_RANGES :: 0b0000_0100 +_SIDD_CMP_EQUAL_EACH :: 0b0000_1000 +_SIDD_CMP_EQUAL_ORDERED :: 0b0000_1100 + +_SIDD_POSITIVE_POLARITY :: 0b0000_0000 +_SIDD_NEGATIVE_POLARITY :: 0b0001_0000 +_SIDD_MASKED_POSITIVE_POLARITY :: 0b0010_0000 +_SIDD_MASKED_NEGATIVE_POLARITY :: 0b0011_0000 + +_SIDD_LEAST_SIGNIFICANT :: 0b0000_0000 +_SIDD_MOST_SIGNIFICANT :: 0b0100_0000 + +_SIDD_BIT_MASK :: 0b0000_0000 +_SIDD_UNIT_MASK :: 0b0100_0000 + +@(require_results, enable_target_feature="sse4.2") +_mm_cmpistrm :: #force_inline proc "c" (a: __m128i, b: __m128i, $IMM8: i8) -> __m128i { + return transmute(__m128i)pcmpistrm128(transmute(i8x16)a, transmute(i8x16)b, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpistri :: #force_inline proc "c" (a: __m128i, b: __m128i, $IMM8: i8) -> i32 { + return pcmpistri128(transmute(i8x16)a, transmute(i8x16)b, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpistrz :: #force_inline proc "c" (a: __m128i, b: __m128i, $IMM8: i8) -> i32 { + return pcmpistriz128(transmute(i8x16)a, transmute(i8x16)b, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpistrc :: #force_inline proc "c" (a: __m128i, b: __m128i, $IMM8: i8) -> i32 { + return pcmpistric128(transmute(i8x16)a, transmute(i8x16)b, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpistrs :: #force_inline proc "c" (a: __m128i, b: __m128i, $IMM8: i8) -> i32 { + return pcmpistris128(transmute(i8x16)a, transmute(i8x16)b, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpistro :: #force_inline proc "c" (a: __m128i, b: __m128i, $IMM8: i8) -> i32 { + return pcmpistrio128(transmute(i8x16)a, transmute(i8x16)b, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpistra :: #force_inline proc "c" (a: __m128i, b: __m128i, $IMM8: i8) -> i32 { + return pcmpistria128(transmute(i8x16)a, transmute(i8x16)b, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpestrm :: #force_inline proc "c" (a: __m128i, la: i32, b: __m128i, lb: i32, $IMM8: i8) -> __m128i { + return transmute(__m128i)pcmpestrm128(transmute(i8x16)a, la, transmute(i8x16)b, lb, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpestri :: #force_inline proc "c" (a: __m128i, la: i32, b: __m128i, lb: i32, $IMM8: i8) -> i32 { + return pcmpestri128(transmute(i8x16)a, la, transmute(i8x16)b, lb, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpestrz :: #force_inline proc "c" (a: __m128i, la: i32, b: __m128i, lb: i32, $IMM8: i8) -> i32 { + return pcmpestriz128(transmute(i8x16)a, la, transmute(i8x16)b, lb, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpestrc :: #force_inline proc "c" (a: __m128i, la: i32, b: __m128i, lb: i32, $IMM8: i8) -> i32 { + return pcmpestric128(transmute(i8x16)a, la, transmute(i8x16)b, lb, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpestrs :: #force_inline proc "c" (a: __m128i, la: i32, b: __m128i, lb: i32, $IMM8: i8) -> i32 { + return pcmpestris128(transmute(i8x16)a, la, transmute(i8x16)b, lb, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpestro :: #force_inline proc "c" (a: __m128i, la: i32, b: __m128i, lb: i32, $IMM8: i8) -> i32 { + return pcmpestrio128(transmute(i8x16)a, la, transmute(i8x16)b, lb, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpestra :: #force_inline proc "c" (a: __m128i, la: i32, b: __m128i, lb: i32, $IMM8: i8) -> i32 { + return pcmpestria128(transmute(i8x16)a, la, transmute(i8x16)b, lb, IMM8) +} +@(require_results, enable_target_feature="sse4.2") +_mm_crc32_u8 :: #force_inline proc "c" (crc: u32, v: u8) -> u32 { + return crc32_32_8(crc, v) +} +@(require_results, enable_target_feature="sse4.2") +_mm_crc32_u16 :: #force_inline proc "c" (crc: u32, v: u16) -> u32 { + return crc32_32_16(crc, v) +} +@(require_results, enable_target_feature="sse4.2") +_mm_crc32_u32 :: #force_inline proc "c" (crc: u32, v: u32) -> u32 { + return crc32_32_32(crc, v) +} +@(require_results, enable_target_feature="sse4.2") +_mm_cmpgt_epi64 :: #force_inline proc "c" (a: __m128i, b: __m128i) -> __m128i { + return transmute(__m128i)simd.lanes_gt(transmute(i64x2)a, transmute(i64x2)b) +} + +when ODIN_ARCH == .amd64 { + @(require_results, enable_target_feature="sse4.2") + _mm_crc32_u64 :: #force_inline proc "c" (crc: u64, v: u64) -> u64 { + return crc32_64_64(crc, v) + } +} + +@(private, default_calling_convention="c") +foreign _ { + // SSE 4.2 string and text comparison ops + @(link_name="llvm.x86.sse42.pcmpestrm128") + pcmpestrm128 :: proc(a: i8x16, la: i32, b: i8x16, lb: i32, #const imm8: i8) -> u8x16 --- + @(link_name="llvm.x86.sse42.pcmpestri128") + pcmpestri128 :: proc(a: i8x16, la: i32, b: i8x16, lb: i32, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpestriz128") + pcmpestriz128 :: proc(a: i8x16, la: i32, b: i8x16, lb: i32, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpestric128") + pcmpestric128 :: proc(a: i8x16, la: i32, b: i8x16, lb: i32, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpestris128") + pcmpestris128 :: proc(a: i8x16, la: i32, b: i8x16, lb: i32, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpestrio128") + pcmpestrio128 :: proc(a: i8x16, la: i32, b: i8x16, lb: i32, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpestria128") + pcmpestria128 :: proc(a: i8x16, la: i32, b: i8x16, lb: i32, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpistrm128") + pcmpistrm128 :: proc(a, b: i8x16, #const imm8: i8) -> i8x16 --- + @(link_name="llvm.x86.sse42.pcmpistri128") + pcmpistri128 :: proc(a, b: i8x16, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpistriz128") + pcmpistriz128 :: proc(a, b: i8x16, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpistric128") + pcmpistric128 :: proc(a, b: i8x16, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpistris128") + pcmpistris128 :: proc(a, b: i8x16, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpistrio128") + pcmpistrio128 :: proc(a, b: i8x16, #const imm8: i8) -> i32 --- + @(link_name="llvm.x86.sse42.pcmpistria128") + pcmpistria128 :: proc(a, b: i8x16, #const imm8: i8) -> i32 --- + // SSE 4.2 CRC instructions + @(link_name="llvm.x86.sse42.crc32.32.8") + crc32_32_8 :: proc(crc: u32, v: u8) -> u32 --- + @(link_name="llvm.x86.sse42.crc32.32.16") + crc32_32_16 :: proc(crc: u32, v: u16) -> u32 --- + @(link_name="llvm.x86.sse42.crc32.32.32") + crc32_32_32 :: proc(crc: u32, v: u32) -> u32 --- + + // AMD64 Only + @(link_name="llvm.x86.sse42.crc32.64.64") + crc32_64_64 :: proc(crc: u64, v: u64) -> u64 --- +} From 4e49d24df980e3f4b853d62af01ed68cb2c622d2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 16:08:06 +0100 Subject: [PATCH 244/254] Add enable_target_feature to ABM --- core/simd/x86/abm.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/simd/x86/abm.odin b/core/simd/x86/abm.odin index 5d7549ab3..79b806242 100644 --- a/core/simd/x86/abm.odin +++ b/core/simd/x86/abm.odin @@ -3,21 +3,21 @@ package simd_x86 import "core:intrinsics" -@(require_results) +@(require_results, enable_target_feature="lzcnt") _lzcnt_u32 :: #force_inline proc "c" (x: u32) -> u32 { return intrinsics.count_leading_zeros(x) } -@(require_results) +@(require_results, enable_target_feature="popcnt") _popcnt32 :: #force_inline proc "c" (x: u32) -> i32 { return i32(intrinsics.count_ones(x)) } when ODIN_ARCH == .amd64 { - @(require_results) + @(require_results, enable_target_feature="lzcnt") _lzcnt_u64 :: #force_inline proc "c" (x: u64) -> u64 { return intrinsics.count_leading_zeros(x) } - @(require_results) + @(require_results, enable_target_feature="popcnt") _popcnt64 :: #force_inline proc "c" (x: u64) -> i32 { return i32(intrinsics.count_ones(x)) } From cb10af08cb612e7d73abe3174e3e4ce0aa162ecc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 30 May 2022 16:42:32 +0100 Subject: [PATCH 245/254] Correct intrinsics.odin for documentation --- core/intrinsics/intrinsics.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 89f9a5f20..72effaca9 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -279,8 +279,8 @@ wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) - wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) --- // x86 Targets (i386, amd64) -cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- -xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- +x86_cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- +x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- // Darwin targets only From a7840d50e2a1f6683607ab2b0dae895952eda103 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 31 May 2022 00:01:23 +0100 Subject: [PATCH 246/254] Correct documentation --- core/intrinsics/intrinsics.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 72effaca9..9994a1914 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -10,10 +10,10 @@ soa_struct :: proc($N: int, $T: typeid) -> type/#soa[N]T // Volatile volatile_load :: proc(dst: ^$T) -> T --- -volatile_store :: proc(dst: ^$T, val: T) -> T --- +volatile_store :: proc(dst: ^$T, val: T) --- -nontemporal_load :: proc(dst: ^$T) -> T --- -nontemporal_store :: proc(dst: ^$T, val: T) -> T --- +non_temporal_load :: proc(dst: ^$T) -> T --- +non_temporal_store :: proc(dst: ^$T, val: T) --- // Trapping debug_trap :: proc() --- From 516f6647b46c69a67139154c02c74b436cd4b999 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 31 May 2022 11:00:41 +0100 Subject: [PATCH 247/254] Fix intrinsics.non_temporal_{load, store} --- src/llvm_backend_proc.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index e3ffcaef2..75ca77641 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2117,10 +2117,14 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMValueRef instr = LLVMBuildStore(p->builder, val.value, dst.value); switch (id) { - case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; case BuiltinProc_non_temporal_store: - // TODO(bill): BuiltinProc_non_temporal_store + { + unsigned kind_id = LLVMGetMDKindIDInContext(p->module->ctx, "nontemporal", 11); + LLVMMetadataRef node = LLVMValueAsMetadata(LLVMConstInt(lb_type(p->module, t_u32), 1, false)); + LLVMSetMetadata(instr, kind_id, LLVMMetadataAsValue(p->module->ctx, node)); + } break; + case BuiltinProc_volatile_store: LLVMSetVolatile(instr, true); break; case BuiltinProc_atomic_store: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; case BuiltinProc_atomic_store_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[2])); break; } @@ -2138,10 +2142,15 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, LLVMValueRef instr = LLVMBuildLoad(p->builder, dst.value, ""); switch (id) { - case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; case BuiltinProc_non_temporal_load: - // TODO(bill): BuiltinProc_non_temporal_load + { + unsigned kind_id = LLVMGetMDKindIDInContext(p->module->ctx, "nontemporal", 11); + LLVMMetadataRef node = LLVMValueAsMetadata(LLVMConstInt(lb_type(p->module, t_u32), 1, false)); + LLVMSetMetadata(instr, kind_id, LLVMMetadataAsValue(p->module->ctx, node)); + } break; + break; + case BuiltinProc_volatile_load: LLVMSetVolatile(instr, true); break; case BuiltinProc_atomic_load: LLVMSetOrdering(instr, LLVMAtomicOrderingSequentiallyConsistent); break; case BuiltinProc_atomic_load_explicit: LLVMSetOrdering(instr, llvm_atomic_ordering_from_odin(ce->args[1])); break; } From 25dae06b6ab357847898ee5635f11e73d326ee4e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 1 Jun 2022 10:32:24 +0100 Subject: [PATCH 248/254] Remove loader.mjs --- vendor/wasm/loader/loader.mjs | 57 ----------------------------------- 1 file changed, 57 deletions(-) delete mode 100644 vendor/wasm/loader/loader.mjs diff --git a/vendor/wasm/loader/loader.mjs b/vendor/wasm/loader/loader.mjs deleted file mode 100644 index be434cf36..000000000 --- a/vendor/wasm/loader/loader.mjs +++ /dev/null @@ -1,57 +0,0 @@ -import {WasmMemoryInterface, odinSetupDefaultImports, WebGLInterface} from "./runtime.mjs"; - -export async function runWasmCanvas(wasmPath, consoleElement, extraForeignImports) { - let wasmMemoryInterface = new WasmMemoryInterface(); - - let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); - let exports = {}; - - let gl_context = new WebGLInterface( - wasmMemoryInterface, - null, - {antialias: false}, - ); - imports["webgl"] = gl_context.getWebGL1Interface(); - imports["webgl2"] = gl_context.getWebGL2Interface(); - - if (extraForeignImports !== undefined) { - imports = { - ...imports, - ...extraForeignImports, - }; - } - - const response = await fetch(wasmPath); - const file = await response.arrayBuffer(); - const wasm = await WebAssembly.instantiate(file, imports); - exports = wasm.instance.exports; - wasmMemoryInterface.setExports(exports); - wasmMemoryInterface.setMemory(exports.memory); - - exports._start(); - - if (exports.step) { - const odin_ctx = exports.default_context_ptr(); - - let prevTimeStamp = undefined; - const step = (currTimeStamp) => { - if (prevTimeStamp == undefined) { - prevTimeStamp = currTimeStamp; - } - - const dt = (currTimeStamp - prevTimeStamp)*0.001; - prevTimeStamp = currTimeStamp; - exports.step(dt, odin_ctx); - window.requestAnimationFrame(step); - }; - - window.requestAnimationFrame(step); - } - - exports._end(); - - return; -}; - - -export {runWasmCanvas}; \ No newline at end of file From 4fac7a8f2794bbea08a455677b61296241617982 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 1 Jun 2022 10:40:59 +0100 Subject: [PATCH 249/254] Update wasm/README.md --- vendor/wasm/README.md | 57 +++++-------------------------------------- 1 file changed, 6 insertions(+), 51 deletions(-) diff --git a/vendor/wasm/README.md b/vendor/wasm/README.md index 4a9345504..55cbd1b7a 100644 --- a/vendor/wasm/README.md +++ b/vendor/wasm/README.md @@ -4,57 +4,12 @@ This directory is for use when targeting the `js_wasm32` target and the packages The `js_wasm32` target assumes that the WASM output will be ran within a web browser rather than a standalone VM. In the VM cases, either `wasi_wasm32` or `freestanding_wasm32` should be used accordingly. -## Example +## Example for `js_wasm32` ```js -import {WasmMemoryInterface, odinSetupDefaultImports} from "./js/runtime.js"; -import {WebGLInterface} from "./WebGL/runtime.js"; - -const runWasm = async (wasm_path, webglCanvasElement, consoleElement) => { - let wasmMemoryInterface = new WasmMemoryInterface(); - - let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); - - if (webglCanvasElement !== undefined) { - let gl_context = new WebGLInterface( - wasmMemoryInterface, - webglCanvasElement, - {antialias: false}, - ); - if (!gl_context.ctx) { - return "WebGL is not available."; - } - imports["webgl"] = gl_context.getWebGL1Interface() - imports["webgl2"] = gl_context.getWebGL2Interface() - } - - const response = await fetch(wasm_path); - const file = await response.arrayBuffer(); - const wasm = await WebAssembly.instantiate(file, imports); - const exports = wasm.instance.exports; - wasmMemoryInterface.setExports(exports); - wasmMemoryInterface.setMemory(exports.memory); - - exports._start(); - - if (exports.step) { - const odin_ctx = exports.default_context_ptr(); - - let prevTimeStamp = undefined; - const step = (currTimeStamp) => { - if (prevTimeStamp == undefined) { - prevTimeStamp = currTimeStamp; - } - - const dt = (currTimeStamp - prevTimeStamp)*0.001; - prevTimeStamp = currTimeStamp; - exports.step(dt, odin_ctx); - window.requestAnimationFrame(step); - }; - - window.requestAnimationFrame(step); - } - - return; -}; + + + ``` \ No newline at end of file From 487bd3d9424479ffe3fb2638077232dbccafa0cf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 1 Jun 2022 11:07:58 +0100 Subject: [PATCH 250/254] Keep compiler happy --- src/microsoft_craziness.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/microsoft_craziness.h b/src/microsoft_craziness.h index 49936e6f5..812513875 100644 --- a/src/microsoft_craziness.h +++ b/src/microsoft_craziness.h @@ -126,7 +126,7 @@ HANDLE mc_find_first(String wildcard, MC_Find_Data *find_data) { bool mc_find_next(HANDLE handle, MC_Find_Data *find_data) { WIN32_FIND_DATAW _find_data; - bool success = FindNextFileW(handle, &_find_data); + bool success = !!FindNextFileW(handle, &_find_data); find_data->file_attributes = _find_data.dwFileAttributes; find_data->filename = mc_wstring_to_string(_find_data.cFileName); From ba5f7c4e2af5c82c220b7e1796fde2f026ce4208 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 1 Jun 2022 11:08:19 +0100 Subject: [PATCH 251/254] Deprecate `a..b` based ranges in favour of `..=` --- core/encoding/entity/entity.odin | 6 +++--- core/encoding/xml/tokenizer.odin | 2 +- src/parser.cpp | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index db1a5ad0b..e5831a75f 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -231,16 +231,16 @@ xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) { for len(entity) > 0 { r := entity[0] switch r { - case '0'..'9': + case '0'..='9': val *= base val += int(r - '0') - case 'a'..'f': + case 'a'..='f': if base == 10 { return -1, false } val *= base val += int(r - 'a' + 10) - case 'A'..'F': + case 'A'..='F': if base == 10 { return -1, false } val *= base val += int(r - 'A' + 10) diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index c3fece76e..d225c5d90 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -198,7 +198,7 @@ is_valid_identifier_rune :: proc(r: rune) -> bool { switch r { case '_', '-', ':': return true case 'A'..='Z', 'a'..='z': return true - case '0'..'9': return true + case '0'..='9': return true case -1: return false } } diff --git a/src/parser.cpp b/src/parser.cpp index 5280fd4b0..b58e3c320 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1428,6 +1428,7 @@ Token expect_operator(AstFile *f) { LIT(p)); } if (f->curr_token.kind == Token_Ellipsis) { + syntax_warning(f->curr_token, "'..' for ranges has now be deprecated, prefer '..='"); f->tokens[f->curr_token_index].flags |= TokenFlag_Replace; } From 66de1856e37ed7917ef7d647ec794ca7e8c53c52 Mon Sep 17 00:00:00 2001 From: Ian Lilley Date: Wed, 1 Jun 2022 21:37:05 -0400 Subject: [PATCH 252/254] tighter allocation for arena allocator --- core/mem/allocators.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index d006e4574..6bedbf691 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -52,15 +52,16 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, switch mode { case .Alloc: - total_size := size + alignment + #no_bounds_check end := &arena.data[arena.offset] + + ptr := align_forward(end, uintptr(alignment)) + + total_size := size + ptr_sub((^byte)(ptr), (^byte)(end)) if arena.offset + total_size > len(arena.data) { return nil, .Out_Of_Memory } - #no_bounds_check end := &arena.data[arena.offset] - - ptr := align_forward(end, uintptr(alignment)) arena.offset += total_size arena.peak_used = max(arena.peak_used, arena.offset) zero(ptr, size) From bb7f291f5fa9d412d5ff15b8de4a46b6ad2704e2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 2 Jun 2022 12:10:43 +0100 Subject: [PATCH 253/254] Remove `simd_rem`; Disallow `simd_div` for integers --- core/intrinsics/intrinsics.odin | 3 +-- core/simd/simd.odin | 3 +-- src/check_builtin.cpp | 9 +++++++-- src/check_expr.cpp | 6 ++++++ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/core/intrinsics/intrinsics.odin b/core/intrinsics/intrinsics.odin index 9994a1914..22b5d953d 100644 --- a/core/intrinsics/intrinsics.odin +++ b/core/intrinsics/intrinsics.odin @@ -194,8 +194,7 @@ constant_utf16_cstring :: proc($literal: string) -> [^]u16 --- simd_add :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_sub :: proc(a, b: #simd[N]T) -> #simd[N]T --- simd_mul :: proc(a, b: #simd[N]T) -> #simd[N]T --- -simd_div :: proc(a, b: #simd[N]T) -> #simd[N]T --- -simd_rem :: proc(a, b: #simd[N]T) -> #simd[N]T --- +simd_div :: proc(a, b: #simd[N]T) -> #simd[N]T where type_is_float(T) --- // Keeps Odin's Behaviour // (x << y) if y <= mask else 0 diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 390ff377a..a0a4df28d 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -61,8 +61,7 @@ b64x8 :: #simd[8]b64 add :: intrinsics.simd_add sub :: intrinsics.simd_sub mul :: intrinsics.simd_mul -div :: intrinsics.simd_div -rem :: intrinsics.simd_rem // integers only +div :: intrinsics.simd_div // floats only // Keeps Odin's Behaviour // (x << y) if y <= mask else 0 diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 92e3987a0..8108604ba 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -452,6 +452,13 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call return false; } + if (id == BuiltinProc_simd_div && is_type_integer(elem)) { + gbString xs = type_to_string(x.type); + error(x.expr, "'%.*s' is not supported for integer elements, got '%s'", LIT(builtin_name), xs); + gb_string_free(xs); + // don't return + } + operand->mode = Addressing_Value; operand->type = x.type; return true; @@ -460,7 +467,6 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call // Integer only case BuiltinProc_simd_add_sat: case BuiltinProc_simd_sub_sat: - case BuiltinProc_simd_rem: case BuiltinProc_simd_and: case BuiltinProc_simd_or: case BuiltinProc_simd_xor: @@ -492,7 +498,6 @@ bool check_builtin_simd_operation(CheckerContext *c, Operand *operand, Ast *call switch (id) { case BuiltinProc_simd_add_sat: case BuiltinProc_simd_sub_sat: - case BuiltinProc_simd_rem: if (!is_type_integer(elem)) { gbString xs = type_to_string(x.type); error(x.expr, "'%.*s' expected a #simd type with an integer element, got '%s'", LIT(builtin_name), xs); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index f954f1583..58972d2cf 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1618,6 +1618,9 @@ bool check_binary_op(CheckerContext *c, Operand *o, Token op) { if (is_type_matrix(main_type)) { error(op, "Operator '%.*s' is only allowed with matrix types", LIT(op.string)); return false; + } else if (is_type_simd_vector(main_type) && is_type_integer(type)) { + error(op, "Operator '%.*s' is only allowed with #simd types with integer elements", LIT(op.string)); + return false; } /*fallthrough*/ case Token_Mul: @@ -1669,6 +1672,9 @@ bool check_binary_op(CheckerContext *c, Operand *o, Token op) { if (!is_type_integer(type)) { error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string)); return false; + } else if (is_type_simd_vector(main_type)) { + error(op, "Operator '%.*s' is only allowed with #simd types with integer elements", LIT(op.string)); + return false; } break; From fb49841b1d8e84cb721f5f73200d4c8158cbb4fa Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 2 Jun 2022 13:02:16 +0100 Subject: [PATCH 254/254] Remove `strings` dependency from `core:sys/windows` --- core/mem/mem.odin | 2 +- core/slice/map.odin | 4 +--- core/strings/builder.odin | 12 ++++++------ core/strings/intern.odin | 7 ++++--- core/sys/windows/comdlg32.odin | 5 ++++- core/sys/windows/util.odin | 17 +++++++++++++++-- core/time/perf.odin | 8 ++++---- 7 files changed, 35 insertions(+), 20 deletions(-) diff --git a/core/mem/mem.odin b/core/mem/mem.odin index 46fed4289..fd91a6c97 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -152,7 +152,7 @@ slice_ptr :: proc "contextless" (ptr: ^$T, len: int) -> []T { return ([^]T)(ptr)[:len] } -byte_slice :: #force_inline proc "contextless" (data: rawptr, len: int) -> []byte { +byte_slice :: #force_inline proc "contextless" (data: rawptr, #any_int len: int) -> []byte { return ([^]u8)(data)[:max(len, 0)] } diff --git a/core/slice/map.odin b/core/slice/map.odin index 1c5512ceb..9de00b174 100644 --- a/core/slice/map.odin +++ b/core/slice/map.odin @@ -2,11 +2,9 @@ package slice import "core:intrinsics" import "core:runtime" -import "core:mem" _ :: intrinsics _ :: runtime -_ :: mem map_keys :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (keys: []K) { keys = make(type_of(keys), len(m), allocator) @@ -52,7 +50,7 @@ map_entries :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (entries map_entry_infos :: proc(m: $M/map[$K]$V, allocator := context.allocator) -> (entries: []Map_Entry_Info(K, V)) #no_bounds_check { m := m - rm := (^mem.Raw_Map)(&m) + rm := (^runtime.Raw_Map)(&m) info := runtime.type_info_base(type_info_of(M)).variant.(runtime.Type_Info_Map) gs := runtime.type_info_base(info.generated_struct).variant.(runtime.Type_Info_Struct) diff --git a/core/strings/builder.odin b/core/strings/builder.odin index d51e21827..a910b0988 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -1,6 +1,6 @@ package strings -import "core:mem" +import "core:runtime" import "core:unicode/utf8" import "core:strconv" import "core:io" @@ -129,12 +129,12 @@ reset_builder :: proc(b: ^Builder) { strings.write_byte(&builder, 'b') -> "ab" */ builder_from_bytes :: proc(backing: []byte) -> Builder { - s := transmute(mem.Raw_Slice)backing - d := mem.Raw_Dynamic_Array{ + s := transmute(runtime.Raw_Slice)backing + d := runtime.Raw_Dynamic_Array{ data = s.data, len = 0, cap = s.len, - allocator = mem.nil_allocator(), + allocator = runtime.nil_allocator(), } return Builder{ buf = transmute([dynamic]byte)d, @@ -276,7 +276,7 @@ pop_byte :: proc(b: ^Builder) -> (r: byte) { } r = b.buf[len(b.buf)-1] - d := cast(^mem.Raw_Dynamic_Array)&b.buf + d := cast(^runtime.Raw_Dynamic_Array)&b.buf d.len = max(d.len-1, 0) return } @@ -289,7 +289,7 @@ pop_rune :: proc(b: ^Builder) -> (r: rune, width: int) { } r, width = utf8.decode_last_rune(b.buf[:]) - d := cast(^mem.Raw_Dynamic_Array)&b.buf + d := cast(^runtime.Raw_Dynamic_Array)&b.buf d.len = max(d.len-width, 0) return } diff --git a/core/strings/intern.odin b/core/strings/intern.odin index 27c3db084..1e9577e61 100644 --- a/core/strings/intern.odin +++ b/core/strings/intern.odin @@ -1,6 +1,6 @@ package strings -import "core:mem" +import "core:runtime" // custom string entry struct Intern_Entry :: struct { @@ -11,7 +11,7 @@ Intern_Entry :: struct { // "intern" is a more memory efficient string map // `allocator` is used to allocate the actual `Intern_Entry` strings Intern :: struct { - allocator: mem.Allocator, + allocator: runtime.Allocator, entries: map[string]^Intern_Entry, } @@ -54,7 +54,8 @@ _intern_get_entry :: proc(m: ^Intern, text: string) -> ^Intern_Entry #no_bounds_ } entry_size := int(offset_of(Intern_Entry, str)) + len(text) + 1 - new_entry := (^Intern_Entry)(mem.alloc(entry_size, align_of(Intern_Entry), m.allocator)) + ptr, _ := runtime.mem_alloc(entry_size, align_of(Intern_Entry), m.allocator) + new_entry := (^Intern_Entry)(ptr) new_entry.len = len(text) copy(new_entry.str[:new_entry.len], text) diff --git a/core/sys/windows/comdlg32.odin b/core/sys/windows/comdlg32.odin index a3709cba7..42a1fd60f 100644 --- a/core/sys/windows/comdlg32.odin +++ b/core/sys/windows/comdlg32.odin @@ -2,7 +2,6 @@ package sys_windows foreign import "system:Comdlg32.lib" -import "core:strings" LPOFNHOOKPROC :: #type proc "stdcall" (hdlg: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> UINT_PTR @@ -49,6 +48,9 @@ SAVE_TITLE :: "Select file to save" SAVE_FLAGS :: u32(OFN_OVERWRITEPROMPT | OFN_EXPLORER) SAVE_EXT :: "txt" +/* +import "core:strings" + Open_Save_Mode :: enum { Open = 0, Save = 1, @@ -121,6 +123,7 @@ select_file_to_save :: proc(title := SAVE_TITLE, dir := ".", path, ok = _open_file_dialog(title, dir, filters, default_filter, flags, default_ext, Open_Save_Mode.Save, allocator) return } +*/ // TODO: Implement convenience function for select_file_to_open with ALLOW_MULTI_SELECT that takes // it output of the form "path\u0000\file1u\0000file2" and turns it into []string with the path + file pre-concatenated for you. diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 1c8b9175b..c350032f0 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -1,7 +1,6 @@ // +build windows package sys_windows -import "core:strings" import "core:runtime" import "core:intrinsics" @@ -100,6 +99,20 @@ utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> (res: st // AdvAPI32, NetAPI32 and UserENV helpers. allowed_username :: proc(username: string) -> bool { + contains_any :: proc(s, chars: string) -> bool { + if chars == "" { + return false + } + for c in transmute([]byte)s { + for b in transmute([]byte)chars { + if c == b { + return true + } + } + } + return false + } + /* User account names are limited to 20 characters and group names are limited to 256 characters. In addition, account names cannot be terminated by a period and they cannot include commas or any of the following printable characters: @@ -120,7 +133,7 @@ allowed_username :: proc(username: string) -> bool { return false } } - if strings.contains_any(username, _DISALLOWED) { + if contains_any(username, _DISALLOWED) { return false } diff --git a/core/time/perf.odin b/core/time/perf.odin index f49b57f5b..53406646f 100644 --- a/core/time/perf.odin +++ b/core/time/perf.odin @@ -1,6 +1,6 @@ package time -import "core:mem" +import "core:runtime" Tick :: struct { _nsec: i64, // relative amount @@ -50,9 +50,9 @@ Benchmark_Error :: enum { } Benchmark_Options :: struct { - setup: #type proc(options: ^Benchmark_Options, allocator: mem.Allocator) -> (err: Benchmark_Error), - bench: #type proc(options: ^Benchmark_Options, allocator: mem.Allocator) -> (err: Benchmark_Error), - teardown: #type proc(options: ^Benchmark_Options, allocator: mem.Allocator) -> (err: Benchmark_Error), + setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), rounds: int, bytes: int,