-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
1,291 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
# sointu-executable-msx | ||
Easy-to-use tool to create executable music using sointu. | ||
Easy-to-use tool to create executable music using Sointu. |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# -*- mode: python ; coding: utf-8 -*- | ||
from os.path import abspath, join | ||
from zipfile import ZipFile | ||
from platform import system | ||
|
||
moduleName = 'sointuexemsx' | ||
rootPath = abspath('.') | ||
buildPath = join(rootPath, 'build') | ||
distPath = join(rootPath, 'dist') | ||
sourcePath = join(rootPath, moduleName) | ||
|
||
block_cipher = None | ||
|
||
a = Analysis([ | ||
join(sourcePath, '__main__.py'), | ||
], | ||
pathex=[], | ||
binaries=[], | ||
datas=[ | ||
(join(sourcePath, 'play.asm'), moduleName), | ||
(join(sourcePath, 'wav.asm'), moduleName), | ||
], | ||
hiddenimports=[], | ||
hookspath=[], | ||
hooksconfig={}, | ||
runtime_hooks=[], | ||
excludes=[], | ||
win_no_prefer_redirects=False, | ||
win_private_assemblies=False, | ||
cipher=block_cipher, | ||
noarchive=False, | ||
) | ||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) | ||
|
||
exe = EXE( | ||
pyz, | ||
a.scripts, | ||
a.binaries, | ||
a.zipfiles, | ||
a.datas, | ||
[], | ||
name='{}'.format(moduleName), | ||
debug=False, | ||
bootloader_ignore_signals=False, | ||
strip=False, | ||
upx=True, | ||
upx_exclude=[], | ||
runtime_tmpdir=None, | ||
console=True, | ||
disable_windowed_traceback=False, | ||
argv_emulation=True, | ||
target_arch=None, | ||
codesign_identity=None, | ||
entitlements_file=None, | ||
icon=join(rootPath, 'team210.ico'), | ||
) | ||
|
||
exeFileName = '{}{}'.format(moduleName, '.exe' if system() == 'Windows' else '') | ||
zipFileName = '{}-{}.zip'.format(moduleName, system()) | ||
zipFile = ZipFile(join(distPath, zipFileName), mode='w') | ||
zipFile.write(join(distPath, exeFileName), arcname=join(moduleName, exeFileName)) | ||
zipFile.write(join(rootPath, 'LICENSE'), arcname=join(moduleName, 'LICENSE')) | ||
zipFile.write(join(rootPath, 'README.md'), arcname=join(moduleName, 'README.md')) | ||
zipFile.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
from argparse import ( | ||
ArgumentParser, | ||
Namespace, | ||
) | ||
from importlib.resources import files | ||
import sointuexemsx | ||
from cached_path import cached_path | ||
from pathlib import Path | ||
from winreg import ( | ||
ConnectRegistry, | ||
OpenKey, | ||
HKEY_LOCAL_MACHINE, | ||
HKEYType, | ||
QueryValueEx, | ||
) | ||
from subprocess import ( | ||
run, | ||
CompletedProcess, | ||
) | ||
from tempfile import TemporaryDirectory | ||
from os.path import ( | ||
basename, | ||
splitext, | ||
exists, | ||
) | ||
from zipfile import ZipFile | ||
from sys import exit | ||
|
||
if __name__ == '__main__': | ||
# Parse command line arguments. | ||
parser: ArgumentParser = ArgumentParser("sointu-executable-msx", description="Easy-to-use tool to create executable music using Sointu.") | ||
parser.add_argument(nargs=1, dest='input', help='Track file to compile.') | ||
parser.add_argument('-b,--brutal', dest='brutal', action='store_true', help='Use brutal Crinkler settings (takes much longer, but executable will be smaller).') | ||
parser.add_argument('-n,--nfo', dest='nfo', default=None, help='Add a NFO to the release archive.') | ||
parser.add_argument('-d,--delay', dest='delay', default=0, type=int, help='Add a delay in ms before starting to play the track (useful for computationally heavy tracks).') | ||
args: Namespace = parser.parse_args() | ||
|
||
# Check argument sanity | ||
if args.input is None or type(args.input) != list or len(args.input) != 1: | ||
print('Input argument missing or wrong format:', args.input) | ||
exit(1) | ||
|
||
if not exists(args.input[0]): | ||
print("Input file does not exist:", args.input[0]) | ||
exit(1) | ||
|
||
if args.nfo is not None and not exists(args.nfo): | ||
print("NFO file does not exist:", args.nfo) | ||
|
||
# Download dependencies. | ||
crinkler: Path = cached_path( | ||
url_or_filename='https://github.com/runestubbe/Crinkler/releases/download/v2.3/crinkler23.zip!crinkler23/Win64/Crinkler.exe', | ||
extract_archive=True, | ||
) | ||
nasm: Path = cached_path( | ||
url_or_filename='https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip!nasm-2.16.01/nasm.exe', | ||
extract_archive=True, | ||
) | ||
sointu: Path = cached_path( | ||
'https://github.com/vsariola/sointu/releases/download/v0.3.0/sointu-Windows.zip!sointu-windows/sointu-compile.exe', | ||
extract_archive=True, | ||
) | ||
|
||
# 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' | ||
|
||
# Determine track base name without extension. | ||
base, _ = splitext(basename(args.input[0])) | ||
|
||
# Run sointu-compile on the track. | ||
with Path(TemporaryDirectory().name) as outputDirectory: | ||
print('Exporting to:', outputDirectory) | ||
|
||
# Run sointu-compile to convert the track to assembly. | ||
result: CompletedProcess = run([ | ||
sointu, | ||
'-arch', '386', | ||
'-e', 'asm,inc', | ||
'-o', outputDirectory / 'music.asm', | ||
args.input[0], | ||
]) | ||
|
||
if result.returncode != 0: | ||
print("Could not compile track with Sointu.") | ||
else: | ||
print("Compiled sointu track.") | ||
|
||
# Assemble the wav writer. | ||
result: CompletedProcess = run([ | ||
nasm, | ||
'-f', 'win32', | ||
'-I', outputDirectory, | ||
files(sointuexemsx) / 'wav.asm', | ||
'-DTRACK_INCLUDE="{}"'.format(outputDirectory / 'music.inc'), | ||
'-DFILENAME="{}"'.format('music.wav'), | ||
'-o', outputDirectory / 'wav.obj', | ||
]) | ||
|
||
if result.returncode != 0: | ||
print("Could not assemble wav writer.") | ||
else: | ||
print("Assembled wav writer.") | ||
|
||
# Assemble the player. | ||
result: CompletedProcess = run([ | ||
nasm, | ||
'-f', 'win32', | ||
'-I', outputDirectory, | ||
files(sointuexemsx) / 'play.asm', | ||
'-DTRACK_INCLUDE="{}"'.format(outputDirectory / 'music.inc'), | ||
'-DDELAY' if args.delay != 0 else '', | ||
'-DDELAY_MS={}'.format(args.delay) if args.delay != 0 else '', | ||
'-o', outputDirectory / 'play.obj', | ||
]) | ||
|
||
if result.returncode != 0: | ||
print("Could not assemble player.") | ||
else: | ||
print("Assembled player.") | ||
|
||
# Assemble the track. | ||
result: CompletedProcess = run([ | ||
nasm, | ||
'-f', 'win32', | ||
'-I', outputDirectory, | ||
outputDirectory / 'music.asm', | ||
'-o', outputDirectory/ 'music.obj', | ||
]) | ||
|
||
if result.returncode != 0: | ||
print("Could not assemble track.") | ||
else: | ||
print("Assembled track.") | ||
|
||
# Link wav writer. | ||
# Note: When using the list based api, quotes in arguments | ||
# are not escaped properly. | ||
result: CompletedProcess = run(' '.join(map(str,[ | ||
crinkler, | ||
'/LIBPATH:"{}"'.format(outputDirectory), | ||
'/LIBPATH:"{}"'.format(windowsSdkLibPath), | ||
outputDirectory / 'wav.obj', | ||
outputDirectory / 'music.obj', | ||
'/OUT:{}'.format(outputDirectory / '{}-wav.exe'.format(base)), | ||
'Winmm.lib', | ||
'Kernel32.lib', | ||
'User32.lib', | ||
'/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,[ | ||
crinkler, | ||
'/LIBPATH:"{}"'.format(outputDirectory), | ||
'/LIBPATH:"{}"'.format(windowsSdkLibPath), | ||
outputDirectory / 'play.obj', | ||
outputDirectory / 'music.obj', | ||
'/OUT:{}'.format(outputDirectory / '{}-play.exe'.format(base)), | ||
'Winmm.lib', | ||
'Kernel32.lib', | ||
'User32.lib', | ||
'/COMPMODE:VERYSLOW' if args.brutal else '/COMPMODE:FAST', | ||
])), shell=True) | ||
|
||
# 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)) | ||
if args.nfo is not None: | ||
nfoBaseWithExt = basename(args.nfo) | ||
zipFile.write(filename=args.nfo, arcname='{}/{}'.format(base, nfoBaseWithExt)) | ||
zipFile.close() | ||
|
||
exit(0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
%define MANGLED | ||
%include TRACK_INCLUDE | ||
|
||
%define WAVE_FORMAT_PCM 0x1 | ||
%define WAVE_FORMAT_IEEE_FLOAT 0x3 | ||
%define WHDR_PREPARED 0x2 | ||
%define WAVE_MAPPER 0xFFFFFFFF | ||
%define TIME_SAMPLES 0x2 | ||
%define PM_REMOVE 0x1 | ||
|
||
section .bss | ||
sound_buffer: | ||
resb SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT | ||
|
||
wave_out_handle: | ||
resd 1 | ||
|
||
msg: | ||
resd 1 | ||
message: | ||
resd 7 | ||
|
||
section .data | ||
wave_format: | ||
%ifdef SU_SAMPLE_FLOAT | ||
dw WAVE_FORMAT_IEEE_FLOAT | ||
%else ; SU_SAMPLE_FLOAT | ||
dw WAVE_FORMAT_PCM | ||
%endif ; SU_SAMPLE_FLOAT | ||
dw SU_CHANNEL_COUNT | ||
dd SU_SAMPLE_RATE | ||
dd SU_SAMPLE_SIZE * SU_SAMPLE_RATE * SU_CHANNEL_COUNT | ||
dw SU_SAMPLE_SIZE * SU_CHANNEL_COUNT | ||
dw SU_SAMPLE_SIZE * 8 | ||
dw 0 | ||
|
||
wave_header: | ||
dd sound_buffer | ||
dd SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT | ||
times 2 dd 0 | ||
dd WHDR_PREPARED | ||
times 4 dd 0 | ||
wave_header_end: | ||
|
||
mmtime: | ||
dd TIME_SAMPLES | ||
sample: | ||
times 2 dd 0 | ||
mmtime_end: | ||
|
||
section .text | ||
symbols: | ||
extern _CreateThread@24 | ||
extern _waveOutOpen@24 | ||
extern _waveOutWrite@12 | ||
extern _waveOutGetPosition@12 | ||
extern _PeekMessageA@20 | ||
extern _TranslateMessage@4 | ||
extern _DispatchMessageA@4 | ||
%ifdef DELAY | ||
extern _Sleep@4 | ||
%endif ; DELAY | ||
|
||
global _mainCRTStartup | ||
_mainCRTStartup: | ||
; win32 uses the cdecl calling convention. This is more readable imo ;) | ||
; We can also skip the prologue; Windows doesn't mind. | ||
|
||
%ifdef SU_LOAD_GMDLS | ||
call _su_load_gmdls | ||
%endif ; SU_LOAD_GMDLS | ||
|
||
times 2 push 0 | ||
push sound_buffer | ||
lea eax, _su_render_song@4 | ||
push eax | ||
times 2 push 0 | ||
call _CreateThread@24 | ||
|
||
%ifdef DELAY | ||
; We can't start playing too early or the missing samples will be audible. | ||
push DELAY_MS | ||
call _Sleep@4 | ||
%endif ; DELAY | ||
|
||
; We render in the background while playing already. Fortunately, | ||
; Windows is slow with the calls below, so we're not worried that | ||
; we don't have enough samples ready before the track starts. | ||
times 3 push 0 | ||
push wave_format | ||
push WAVE_MAPPER | ||
push wave_out_handle | ||
call _waveOutOpen@24 | ||
|
||
push wave_header_end - wave_header | ||
push wave_header | ||
push dword [wave_out_handle] | ||
call _waveOutWrite@12 | ||
|
||
; We need to handle windows messages properly while playing, as waveOutWrite is async. | ||
mainloop: | ||
dispatchloop: | ||
push PM_REMOVE | ||
times 3 push 0 | ||
push msg | ||
call _PeekMessageA@20 | ||
jz dispatchloop_end | ||
|
||
push msg | ||
call _TranslateMessage@4 | ||
|
||
push msg | ||
call _DispatchMessageA@4 | ||
|
||
jmp dispatchloop | ||
dispatchloop_end: | ||
|
||
push mmtime_end - mmtime | ||
push mmtime | ||
push dword [wave_out_handle] | ||
call _waveOutGetPosition@12 | ||
|
||
cmp dword [sample], SU_LENGTH_IN_SAMPLES | ||
jne mainloop | ||
|
||
exit: | ||
; At least we can skip the epilogue :) | ||
leave | ||
ret |
Oops, something went wrong.