diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d1681e..7025c80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,14 +20,13 @@ jobs: config: - os: ubuntu-latest - os: windows-latest - - os: macos-latest steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Python 3.11 + - name: Install Python 3.12 uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.12" - name: Install poetry run: pip install poetry - name: Install dependencies diff --git a/README.md b/README.md index 0f18865..7300936 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,17 @@ Easy-to-use tool to create executable music using Sointu (https://github.com/vsa It downloads nasm, Crinkler and Sointu automatically and runs them to generate an executable from Sointu track YAML files. For this purpose, it contains a small x86 assembly player and wav writer. ## Prequisites + +### Windows You need a recent version of the Windows SDK installed. This can be achieved by installing either Visual Studio or MSVC build tools, and enabling the "Windows 10 SDK" workload in the installer. +### Linux +You need to install i386 libraries in `/usr/lib/i386-linux-gnu` for libc6, pipewire-alsa and libasound. Also make sure to install upx, ld and nasm using your package manager. Note: GM.DLS will not work. + ## Usage ``` -usage: sointu-executable-msx [-h] [-b,--brutal] [-n,--nfo NFO] [-d,--delay DELAY] [-s,--sointu-compile SOINTUCOMPILE] - [-4,--4klang FOURKLANG] [--sample-type {float,pcm}] [--channel-count CHANNELCOUNT] - [--sample-size SAMPLESIZE] +usage: sointu-executable-msx [-h] [-b,--brutal] [-n,--nfo NFO] [-d,--delay DELAY] [-s,--sointu-compile SOINTUCOMPILE] [-4,--4klang FOURKLANG] [--sample-type {float,pcm}] + [--channel-count CHANNELCOUNT] [--sample-size SAMPLESIZE] [--force-download] [--ld LD] [--build-folder BUILDFOLDER] [--disable-upx] input Easy-to-use tool to create executable music using Sointu. @@ -33,6 +37,11 @@ options: Enforce channel count for 4klang builds. --sample-size SAMPLESIZE Enforce sample size for 4klang builds. + --force-download Force-redownload the cached dependencies. + --ld LD Use this ld binary instead of the one in the PATH variable. + --build-folder BUILDFOLDER + Use a specific build folder instead of a temporary dir. + --disable-upx Disable UPX for drop-in replacement compressing linkers for ld. ``` ### Examples diff --git a/pyinstaller.spec b/pyinstaller.spec index b583007..3b2fbf4 100644 --- a/pyinstaller.spec +++ b/pyinstaller.spec @@ -17,8 +17,10 @@ a = Analysis([ pathex=[], binaries=[], datas=[ - (join(sourcePath, 'play.asm'), moduleName), - (join(sourcePath, 'wav.asm'), moduleName), + (join(sourcePath, 'play.win32.asm'), moduleName), + (join(sourcePath, 'wav.win32.asm'), moduleName), + (join(sourcePath, 'play.elf32.asm'), moduleName), + (join(sourcePath, 'wav.elf32.asm'), moduleName), ], hiddenimports=[], hookspath=[], diff --git a/sointuexemsx/__main__.py b/sointuexemsx/__main__.py index c6295b4..7e65ecd 100644 --- a/sointuexemsx/__main__.py +++ b/sointuexemsx/__main__.py @@ -10,13 +10,21 @@ get_cache_dir, ) from pathlib import Path -from winreg import ( - ConnectRegistry, - OpenKey, - HKEY_LOCAL_MACHINE, - HKEYType, - QueryValueEx, -) +from platform import system +if system() == 'Windows': + from winreg import ( + ConnectRegistry, + OpenKey, + HKEY_LOCAL_MACHINE, + HKEYType, + QueryValueEx, + ) +elif system() == 'Linux': + from stat import ( + S_IXUSR, + S_IXGRP, + S_IXOTH, + ) from subprocess import ( run, CompletedProcess, @@ -34,14 +42,19 @@ copyfile, rmtree, ) -from enum import StrEnum +from enum import ( + IntEnum, + auto, +) from json import loads -class DownloadUrls(StrEnum): - Crinkler = 'https://github.com/runestubbe/Crinkler/releases/download/v2.3/crinkler23.zip!crinkler23/Win64/Crinkler.exe' - Nasm = 'https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip!nasm-2.16.01/nasm.exe' - SointuCompile = 'https://github.com/vsariola/sointu/releases/latest/download/sointu-Windows.zip!sointu-windows/sointu-compile.exe' +class DependencyType(IntEnum): + Crinkler = auto() + Nasm = auto() + SointuCompile = auto() + Upx = auto() + Ld = auto() def clear_cached_path( @@ -86,6 +99,9 @@ def clear_cached_path( parser.add_argument('--channel-count', dest='channelCount', default=2, help='Enforce channel count for 4klang builds.') parser.add_argument('--sample-size', dest='sampleSize', default=4, help='Enforce sample size for 4klang builds.') parser.add_argument('--force-download', dest='forceDownload', action='store_true', help='Force-redownload the cached dependencies.') + parser.add_argument('--ld', dest='ld', default='ld', help='Use this ld binary instead of the one in the PATH variable.') + parser.add_argument('--build-folder', dest='buildFolder', default=None, help='Use a specific build folder instead of a temporary dir.') + parser.add_argument('--disable-upx', dest='disableUpx', action='store_true', help='Disable UPX for drop-in replacement compressing linkers for ld.') args: Namespace = parser.parse_args() # Check argument sanity @@ -109,80 +125,124 @@ def clear_cached_path( print("4klang assembly file does not exist:", args.fourKlang) exit(1) + # Download dependencies. + downloadUrls = {} + if system() == 'Windows': + downloadUrls.update({ + DependencyType.Crinkler: 'https://github.com/runestubbe/Crinkler/releases/download/v2.3/crinkler23.zip!crinkler23/Win64/Crinkler.exe', + DependencyType.Nasm: 'https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip!nasm-2.16.01/nasm.exe', + DependencyType.SointuCompile: 'https://github.com/vsariola/sointu/releases/latest/download/sointu-Windows.zip!sointu-windows/sointu-compile.exe', + }) + elif system() == 'Linux': + downloadUrls.update({ + DependencyType.SointuCompile: 'https://github.com/vsariola/sointu/releases/latest/download/sointu-Linux.zip!sointu-Linux/sointu-compile', + }) + if args.forceDownload: print("Clearing cache.") - for url in DownloadUrls: + for url in downloadUrls.values(): for deletedPath in clear_cached_path(url.value): print(f"Removing {deletedPath}") - # Download dependencies. - crinkler: Path = cached_path( - url_or_filename=DownloadUrls.Crinkler.value, - extract_archive=True, - ) - nasm: Path = cached_path( - url_or_filename=DownloadUrls.Nasm.value, - extract_archive=True, - ) - sointu: Path = cached_path( - url_or_filename=DownloadUrls.SointuCompile.value, - extract_archive=True, - ) if args.sointuCompile is None else Path(args.sointuCompile) - - # Find Windows SDK path. - registry: HKEYType = ConnectRegistry(None, HKEY_LOCAL_MACHINE) - windowsSdkKey: HKEYType = OpenKey(registry, r'SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0') - windowsSdkProductVersion, _ = QueryValueEx(windowsSdkKey, r'ProductVersion') - windowsSdkInstallFolder, _ = QueryValueEx(windowsSdkKey, r'InstallationFolder') - windowsSdkKey.Close() - registry.Close() - windowsSdkLibPath: Path = Path(windowsSdkInstallFolder) / 'Lib' / '{}.0'.format(windowsSdkProductVersion) / 'um' / 'x86' + programs = {} + for program in downloadUrls.keys(): + programs[program] = cached_path( + url_or_filename=downloadUrls[program], + extract_archive=True, + ) + if system() == 'Linux': + programs[program].chmod(programs[program].stat().st_mode | S_IXUSR | S_IXGRP | S_IXOTH) + + + if args.sointuCompile is not None: + programs[DependencyType.SointuCompile] = Path(args.sointuCompile) + + if system() == 'Linux': + programs.update({ + DependencyType.Nasm: Path('nasm'), + DependencyType.Upx: Path('upx'), + DependencyType.Ld: Path(args.ld), + }) + + # Find required library paths + libpaths = [] + if system() == 'Windows': + # Find Windows SDK path. + registry: HKEYType = ConnectRegistry(None, HKEY_LOCAL_MACHINE) + windowsSdkKey: HKEYType = OpenKey(registry, r'SOFTWARE\WOW6432Node\Microsoft\Microsoft SDKs\Windows\v10.0') + windowsSdkProductVersion, _ = QueryValueEx(windowsSdkKey, r'ProductVersion') + windowsSdkInstallFolder, _ = QueryValueEx(windowsSdkKey, r'InstallationFolder') + windowsSdkKey.Close() + registry.Close() + windowsSdkLibPath: Path = Path(windowsSdkInstallFolder) / 'Lib' / f'{windowsSdkProductVersion}.0' / 'um' / 'x86' + libpaths.append(windowsSdkLibPath) # Determine track base name without extension. base, _ = splitext(basename(args.input[0])) dir = dirname(args.input[0]) + if args.buildFolder: + Path(args.buildFolder).mkdir(exist_ok=True, parents=True) # Run sointu-compile on the track. - with TemporaryDirectory() as temporaryDirectory: + with TemporaryDirectory( + dir=args.buildFolder, + delete=args.buildFolder is None, + ) as temporaryDirectory: outputDirectory: Path = Path(temporaryDirectory) - print('Exporting to:', outputDirectory) - + print('Exporting to:', outputDirectory) + + objectFileExtension: str = "" + binaryFileExtension: str = "" + platformPrefix: str = "" + nasmAbi: str = "" + if system() == 'Windows': + objectFileExtension = '.obj' + binaryFileExtension = '.exe' + platformPrefix = 'win32' + nasmAbi = 'win32' + elif system() == 'Linux': + objectFileExtension = '.o' + binaryFileExtension = '' + platformPrefix = 'elf32' + nasmAbi = 'elf32' + + trackInclude: Path = outputDirectory / f'{base}.inc' nasmArgs: list[str] = [ - str(nasm), - '-f', 'win32', + str(programs[DependencyType.Nasm]), + '-f', nasmAbi, '-I', str(outputDirectory), - '-DFILENAME="{}.wav"'.format(base), - '-DTRACK_INCLUDE="{}"'.format(outputDirectory / '{}.inc'.format(base)), + f'-DFILENAME="{base}.wav"', + f'-DTRACK_INCLUDE="{trackInclude}"', ] if args.delay != 0: nasmArgs += [ '-DADD_DELAY', - '-DDELAY_MS={}'.format(args.delay), + f'-DDELAY_MS={args.delay}', ] if args.fourKlang is not None: nasmArgs += [ '-DUSE_4KLANG', - '-DCHANNEL_COUNT={}'.format(args.channelCount), - '-DSAMPLE_SIZE={}'.format(args.sampleSize), + f'-DCHANNEL_COUNT={args.channelCount}', + f'-DSAMPLE_SIZE={args.sampleSize}', ] if args.sampleType == 'float': nasmArgs.append('-DSAMPLE_FLOAT') # Copy the track files to the output directory. print("copying:") - print(args.fourKlang, "->", outputDirectory / '{}.asm'.format(base)) + print(args.fourKlang, "->", outputDirectory / f'{base}.asm') - copyfile(args.fourKlang, outputDirectory / '{}.asm'.format(base)) - copyfile(args.input[0], outputDirectory / '{}.inc'.format(base)) + copyfile(args.fourKlang, outputDirectory / f'{base}.asm') + copyfile(args.input[0], outputDirectory / f'{base}.inc') else: # Run sointu-compile to convert the track to assembly. result: CompletedProcess = run([ - sointu, + str(programs[DependencyType.SointuCompile]), '-arch', '386', '-e', 'asm,inc', - '-o', outputDirectory / '{}.asm'.format(base), + '-o', outputDirectory / f'{base}.asm', args.input[0], ]) @@ -193,8 +253,8 @@ def clear_cached_path( # Assemble the wav writer. result: CompletedProcess = run(nasmArgs + [ - str(files(sointuexemsx) / 'wav.asm'), - '-o', str(outputDirectory / 'wav.obj'), + str(files(sointuexemsx) / f'wav.{platformPrefix}.asm'), + '-o', str(outputDirectory / f'wav{objectFileExtension}'), ]) if result.returncode != 0: @@ -204,8 +264,8 @@ def clear_cached_path( # Assemble the player. result: CompletedProcess = run(nasmArgs + [ - str(files(sointuexemsx) / 'play.asm'), - '-o', str(outputDirectory / 'play.obj'), + str(files(sointuexemsx) / f'play.{platformPrefix}.asm'), + '-o', str(outputDirectory / f'play{objectFileExtension}'), ]) if result.returncode != 0: @@ -215,11 +275,11 @@ def clear_cached_path( # Assemble the track. result: CompletedProcess = run([ - nasm, - '-f', 'win32', + str(programs[DependencyType.Nasm]), + '-f', nasmAbi, '-I', outputDirectory, - outputDirectory / '{}.asm'.format(base), - '-o', outputDirectory / '{}.obj'.format(base), + outputDirectory / f'{base}.asm', + '-o', outputDirectory / f'{base}{objectFileExtension}', ]) if result.returncode != 0: @@ -227,38 +287,114 @@ def clear_cached_path( else: print("Assembled track.") - crinklerArgs: list[str] = [ - str(crinkler), - '/LIBPATH:"{}"'.format(outputDirectory), - '/LIBPATH:"{}"'.format(windowsSdkLibPath), - 'Winmm.lib', - 'Kernel32.lib', - 'User32.lib', - str(outputDirectory / '{}.obj'.format(base)), - ] + wavBinary = outputDirectory / f'{base}-wav{binaryFileExtension}' + playBinary = outputDirectory / f'{base}-play{binaryFileExtension}' + if system() == 'Windows': + crinklerArgs: list[str] = [ + str(programs[DependencyType.Crinkler]), + f'/LIBPATH:"{outputDirectory}"', + *map( + lambda libpath: f'/LIBPATH:"{libpath}"', + libpaths, + ), + 'Winmm.lib', + 'Kernel32.lib', + 'User32.lib', + str(outputDirectory / f'{base}{objectFileExtension}'), + '/COMPMODE:VERYSLOW' if args.brutal else '/COMPMODE:FAST', + ] + + # Link wav writer. + # Note: When using the list based api, quotes in arguments + # are not escaped properly. + result: CompletedProcess = run(' '.join(map(str, crinklerArgs + [ + outputDirectory / f'wav{objectFileExtension}', + f'/OUT:{wavBinary}', + ])), shell=True) + if result.returncode != 0: + print("Could not link wav writer.") + else: + print("Linked wav writer.") + + # Link player. + # Note: When using the list based api, quotes in arguments + # are not escaped properly. + result: CompletedProcess = run(' '.join(map(str, crinklerArgs + [ + outputDirectory / f'play{objectFileExtension}', + f'/OUT:{playBinary}', + ])), shell=True) + if result.returncode != 0: + print("Could not link player.") + else: + print("Linked player.") + elif system() == 'Linux': + ldArgs: list[str] = [ + str(programs[DependencyType.Ld]), + str(outputDirectory / f'{base}{objectFileExtension}'), + '-no-pie', + '-m', 'elf_i386', + '-lc', + '-e', 'main', + '-I', '/usr/lib/i386-linux-gnu', + '-dynamic-linker', '/lib/ld-linux.so.2', + ] - # Link wav writer. - # Note: When using the list based api, quotes in arguments - # are not escaped properly. - result: CompletedProcess = run(' '.join(map(str, crinklerArgs + [ - outputDirectory / 'wav.obj', - '/OUT:{}'.format(outputDirectory / '{}-wav.exe'.format(base)), - '/COMPMODE:VERYSLOW' if args.brutal else '/COMPMODE:FAST', - ])), shell=True) - - # Link player. - # Note: When using the list based api, quotes in arguments - # are not escaped properly. - result: CompletedProcess = run(' '.join(map(str, crinklerArgs + [ - outputDirectory / 'play.obj', - '/OUT:{}'.format(outputDirectory / '{}-play.exe'.format(base)), - '/COMPMODE:VERYSLOW' if args.brutal else '/COMPMODE:FAST', - ])), shell=True) + # Link wav writer. + # Note: When using the list based api, quotes in arguments + # are not escaped properly. + result: CompletedProcess = run(' '.join(map(str, ldArgs + [ + outputDirectory / f'wav{objectFileExtension}', + '-o', wavBinary, + ])), shell=True) + if result.returncode != 0: + print("Could not link wav writer.") + else: + print("Linked wav writer.") + + # Link player. + # Note: When using the list based api, quotes in arguments + # are not escaped properly. + result: CompletedProcess = run(' '.join(map(str, ldArgs + [ + outputDirectory / f'play{objectFileExtension}', + '-o', playBinary, + '-lasound', + ])), shell=True) + if result.returncode != 0: + print("Could not link player.") + else: + print("Linked player.") + + if not args.disableUpx: + # Compress wav writer using UPX. + # Note: When using the list based api, quotes in arguments + # are not escaped properly. + result: CompletedProcess = run(' '.join(map(str, [ + str(programs[DependencyType.Upx]), + '--best', + outputDirectory / f'{base}-wav{binaryFileExtension}', + ])), shell=True) + if result.returncode != 0: + print("Could not upx-compress wav writer.") + else: + print("upx-compressed wav writer.") + + # Compress player using UPX. + # Note: When using the list based api, quotes in arguments + # are not escaped properly. + result: CompletedProcess = run(' '.join(map(str, [ + str(programs[DependencyType.Upx]), + '--best', + outputDirectory / f'{base}-play{binaryFileExtension}', + ])), shell=True) + if result.returncode != 0: + print("Could not upx-compress player.") + else: + print("upx-compressed player.") # Create release archive. - zipFile: ZipFile = ZipFile('{}.zip'.format(base), 'w') - zipFile.write(filename=str(outputDirectory / '{}-wav.exe'.format(base)), arcname='{}/{}-wav.exe'.format(base, base)) - zipFile.write(filename=str(outputDirectory / '{}-play.exe'.format(base)), arcname='{}/{}-play.exe'.format(base, base)) + zipFile: ZipFile = ZipFile(f'{base}.zip', 'w') + zipFile.write(filename=str(outputDirectory / f'{base}-wav{binaryFileExtension}'), arcname=f'{base}/{base}-wav{binaryFileExtension}') + zipFile.write(filename=str(outputDirectory / f'{base}-play{binaryFileExtension}'), arcname=f'{base}/{base}-play{binaryFileExtension}') if args.nfo is not None: nfoBaseWithExt = basename(args.nfo) zipFile.write(filename=args.nfo, arcname='{}/{}'.format(base, nfoBaseWithExt)) diff --git a/sointuexemsx/play.elf32.asm b/sointuexemsx/play.elf32.asm new file mode 100644 index 0000000..a59bc22 --- /dev/null +++ b/sointuexemsx/play.elf32.asm @@ -0,0 +1,88 @@ +%include TRACK_INCLUDE + +%define SND_PCM_FORMAT_S16_LE 0x2 +%define SND_PCM_FORMAT_FLOAT 0xE +%define SND_PCM_ACCESS_RW_INTERLEAVED 0x3 +%define SND_PCM_STREAM_PLAYBACK 0x0 + +%ifdef SU_SYNC +section .bss + global _syncBuf +_syncBuf: + resb SU_SYNCBUFFER_LENGTH +%endif ; SU_SYNC + +section .bss +sound_buffer: + resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT + +render_thread: + resd 1 + +pcm_handle: + resd 1 + +section .data +default_device: + db "default", 0 + +section .text +symbols: + extern pthread_create + extern sleep + extern snd_pcm_open + extern snd_pcm_set_params + extern snd_pcm_writei + + global main +main: + ; elf32 uses the cdecl calling convention. This is more readable imo ;) + + ; Prologue + push ebp + mov ebp, esp + sub esp, 0x10 + + ; Unix does not have gm.dls, no need to ifdef and setup here. + + ; We render in the background while playing already. + push sound_buffer + lea eax, su_render_song + push eax + push 0 + push render_thread + call pthread_create + + ; We can't start playing too early or the missing samples will be audible. + push 0x2 + call sleep + + ; Play the track. + push 0x0 + push SND_PCM_STREAM_PLAYBACK + push default_device + push pcm_handle + call snd_pcm_open + + push SU_LENGTH_IN_SAMPLES + push 0 + push SU_SAMPLE_RATE + push SU_CHANNEL_COUNT + push SND_PCM_ACCESS_RW_INTERLEAVED +%ifdef SU_SAMPLE_FLOAT + push SND_PCM_FORMAT_FLOAT +%else ; SU_SAMPLE_FLOAT + push SND_PCM_FORMAT_S16_LE +%endif ; SU_SAMPLE_FLOAT + push dword [pcm_handle] + call snd_pcm_set_params + + push SU_LENGTH_IN_SAMPLES + push sound_buffer + push dword [pcm_handle] + call snd_pcm_writei + +exit: + ; At least we can skip the epilogue :) + leave + ret diff --git a/sointuexemsx/play.asm b/sointuexemsx/play.win32.asm similarity index 100% rename from sointuexemsx/play.asm rename to sointuexemsx/play.win32.asm diff --git a/sointuexemsx/wav.elf32.asm b/sointuexemsx/wav.elf32.asm new file mode 100644 index 0000000..9fc860c --- /dev/null +++ b/sointuexemsx/wav.elf32.asm @@ -0,0 +1,98 @@ +%include TRACK_INCLUDE + +%define WAVE_FORMAT_PCM 0x1 +%define WAVE_FORMAT_IEEE_FLOAT 0x3 + +%ifdef SU_SYNC +section .bss + global _syncBuf +_syncBuf: + resb SU_SYNCBUFFER_LENGTH +%endif ; SU_SYNC + +section .bss +sound_buffer: + resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT + +file: + resd 1 + +section .data +; Change the filename over -DFILENAME="yourfilename.wav" +filename: + db FILENAME, 0 + +format: + db "wb", 0 + +; This is the wave file header. +wave_file: + db "RIFF" + dd wave_file_end + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT - wave_file + db "WAVE" + db "fmt " +wave_format_end: + dd wave_format_end - wave_file +%ifdef SU_SAMPLE_FLOAT + dw WAVE_FORMAT_IEEE_FLOAT +%else ; SU_SAMPLE_FLOAT + dw WAVE_FORMAT_PCM +%endif ; SU_SAMPLE_FLOAT + dw SU_CHANNEL_COUNT + dd SU_SAMPLE_RATE + dd SU_SAMPLE_SIZE * SU_SAMPLE_RATE * SU_CHANNEL_COUNT + dw SU_SAMPLE_SIZE * SU_CHANNEL_COUNT + dw SU_SAMPLE_SIZE * 8 +wave_header_end: + db "data" + dd wave_file_end + SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT - wave_header_end +wave_file_end: + +section .text +symbols: + extern fopen + extern fwrite + extern fclose + + global main +main: + ; elf32 uses the cdecl calling convention. This is more readable imo ;) + + ; Prologue + push ebp + mov ebp, esp + sub esp, 0x10 + + ; Unix does not have gm.dls, no need to ifdef and setup here. + + ; We render the complete track here. + push sound_buffer + call su_render_song + + ; Now we open the file and save the track. + push format + push filename + call fopen + mov dword [file], eax + + ; Write header + push dword [file] + push 0x1 + push wave_file_end - wave_file + push wave_file + call fwrite + + ; write data + push dword [file] + push 0x1 + push SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT + push sound_buffer + call fwrite + + push dword [file] + call fclose + +exit: + ; At least we can skip the epilogue :) + leave + ret diff --git a/sointuexemsx/wav.asm b/sointuexemsx/wav.win32.asm similarity index 100% rename from sointuexemsx/wav.asm rename to sointuexemsx/wav.win32.asm