mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
feat(examples): add example playing sointu tracks from Python (#108)
* Added Python code example. * Added pyinstaller build. * Clarified debugging steps in README.md. * Added linux implementation. * Cosmetics. * Updated README with correct steps.
This commit is contained in:
parent
f5eeabe5f3
commit
94589eb2eb
7
examples/code/Python/.gitignore
vendored
Normal file
7
examples/code/Python/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.venv
|
||||
build
|
||||
dist
|
||||
__pycache__
|
||||
setup.py
|
||||
*.egg-info
|
||||
*.so
|
18
examples/code/Python/README.md
Normal file
18
examples/code/Python/README.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Embed Sointu in Python
|
||||
This is an example for embedding Sointu into Python code.
|
||||
|
||||
# Configure the track
|
||||
Edit the `track` variable in `build.py` according to your needs.
|
||||
|
||||
# Build
|
||||
* Install Python 3.11 and poetry.
|
||||
* Download nasm and golang; place both of them in your system `PATH`.
|
||||
* Enable cgo by downloading a gcc and placing it into your system `PATH`.
|
||||
* Get the dependencies with `poetry install`.
|
||||
* Run the player using `poetry run python -m sointu_python`.
|
||||
* Pack everything into an executable using `poetry run pyinstaller sointu_python/sointu_python.spec`. The executable will be built in the `dist` subfolder.
|
||||
|
||||
# Rebuild after changes
|
||||
* Rebuild the example track bindings with `poetry build`.
|
||||
* Update the bindings module with `poetry install`.
|
||||
* Proceed iteration.
|
149
examples/code/Python/build.py
Normal file
149
examples/code/Python/build.py
Normal file
@ -0,0 +1,149 @@
|
||||
from distutils.command.build_ext import build_ext
|
||||
from distutils.errors import (
|
||||
CCompilerError,
|
||||
DistutilsExecError,
|
||||
DistutilsPlatformError,
|
||||
)
|
||||
from distutils.core import Extension
|
||||
from os.path import (
|
||||
dirname,
|
||||
join,
|
||||
abspath,
|
||||
exists,
|
||||
basename,
|
||||
splitext,
|
||||
)
|
||||
from os import mkdir
|
||||
from subprocess import run
|
||||
from platform import system
|
||||
from sys import exit
|
||||
|
||||
track = "../../patches/physics_girl_st.yml"
|
||||
|
||||
class BuildFailed(Exception):
|
||||
pass
|
||||
|
||||
class ExtBuilder(build_ext):
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
build_ext.run(self)
|
||||
except (DistutilsPlatformError, FileNotFoundError):
|
||||
raise BuildFailed('File not found. Could not compile C extension.')
|
||||
|
||||
def build_extension(self, ext):
|
||||
try:
|
||||
build_ext.build_extension(self, ext)
|
||||
except (CCompilerError, DistutilsExecError, DistutilsPlatformError, ValueError):
|
||||
raise BuildFailed('Could not compile C extension.')
|
||||
|
||||
def build(setup_kwargs):
|
||||
# Make sure the build directory exists and setup the
|
||||
# relative paths correctly.
|
||||
cwd = abspath(".")
|
||||
print("Running from:", cwd)
|
||||
|
||||
current_source_dir = abspath(dirname(__file__))
|
||||
project_source_dir = abspath(join(current_source_dir, "..", "..", ".."))
|
||||
current_binary_dir = join(current_source_dir, 'build')
|
||||
if not exists(current_binary_dir):
|
||||
mkdir(current_binary_dir)
|
||||
host_is_windows = system() == "Windows"
|
||||
executable_suffix = ".exe" if host_is_windows else ""
|
||||
object_suffix = ".obj" if host_is_windows else ".o"
|
||||
|
||||
# Build the sointu compiler first.
|
||||
compiler_executable = join(current_binary_dir, "sointu-compile{}".format(executable_suffix))
|
||||
result = run(
|
||||
args=[
|
||||
"go", "build",
|
||||
"-o", compiler_executable,
|
||||
"cmd/sointu-compile/main.go",
|
||||
],
|
||||
cwd=project_source_dir,
|
||||
shell=True if host_is_windows else False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print("sointu-compile build process exited with:", result.returncode)
|
||||
print(result.stdout)
|
||||
exit(1)
|
||||
|
||||
track_file_name = abspath(join(current_source_dir, track))
|
||||
(track_name_base, _) = splitext(basename(track_file_name))
|
||||
print("Compiling track:", track_file_name)
|
||||
|
||||
# Compile the track.
|
||||
sointu_compiler_arch = "amd64"
|
||||
track_asm_file = join(current_binary_dir, '{}.asm'.format(track_name_base))
|
||||
result = run(
|
||||
args=[
|
||||
compiler_executable,
|
||||
"-o", track_asm_file,
|
||||
"-arch={}".format(sointu_compiler_arch),
|
||||
track_file_name,
|
||||
],
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print("sointu-compile process exited with:", result.returncode)
|
||||
print(result.stdout)
|
||||
exit(1)
|
||||
|
||||
# Assemble the track.
|
||||
nasm_abi = "Win64" if host_is_windows else "Elf64"
|
||||
track_object_file = join(current_binary_dir, '{}{}'.format(track_name_base, object_suffix))
|
||||
print("Assembling track asm source:", track_asm_file)
|
||||
result = run(
|
||||
args=[
|
||||
'nasm',
|
||||
'-o', track_object_file,
|
||||
'-f', nasm_abi,
|
||||
track_asm_file,
|
||||
],
|
||||
)
|
||||
if result.returncode != 0:
|
||||
print("nasm process exited with:", result.returncode)
|
||||
print(result.stdout)
|
||||
exit(1)
|
||||
|
||||
# Export the plugin.
|
||||
print("Linking object file into Python extension module:", track_object_file)
|
||||
setup_kwargs.update({
|
||||
"ext_modules": [
|
||||
Extension(
|
||||
"sointu",
|
||||
include_dirs=[
|
||||
current_binary_dir,
|
||||
current_source_dir,
|
||||
],
|
||||
sources=[
|
||||
"sointu.c",
|
||||
],
|
||||
extra_compile_args=[
|
||||
"-DTRACK_HEADER=\"{}.h\"".format(track_name_base),
|
||||
] + ([
|
||||
"-DWIN32",
|
||||
] if host_is_windows else [
|
||||
"-DUNIX",
|
||||
"-fPIC",
|
||||
]),
|
||||
extra_objects=[
|
||||
track_object_file,
|
||||
],
|
||||
extra_link_args=[
|
||||
"dsound.lib",
|
||||
"ws2_32.lib",
|
||||
"ucrt.lib",
|
||||
"user32.lib",
|
||||
] if host_is_windows else [
|
||||
"-z", "noexecstack",
|
||||
"--no-pie",
|
||||
"-lasound",
|
||||
"-lpthread",
|
||||
"-lpython3.11",
|
||||
],
|
||||
),
|
||||
],
|
||||
"cmdclass": {
|
||||
"build_ext": ExtBuilder,
|
||||
},
|
||||
})
|
132
examples/code/Python/poetry.lock
generated
Normal file
132
examples/code/Python/poetry.lock
generated
Normal file
@ -0,0 +1,132 @@
|
||||
# This file is automatically @generated by Poetry and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "altgraph"
|
||||
version = "0.17.4"
|
||||
description = "Python graph (network) package"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
|
||||
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macholib"
|
||||
version = "1.16.3"
|
||||
description = "Mach-O header analysis and editing"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
|
||||
{file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
altgraph = ">=0.17"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.2"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pefile"
|
||||
version = "2023.2.7"
|
||||
description = "Python PE parsing module"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.0"
|
||||
files = [
|
||||
{file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"},
|
||||
{file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyinstaller"
|
||||
version = "6.0.0"
|
||||
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "<3.13,>=3.8"
|
||||
files = [
|
||||
{file = "pyinstaller-6.0.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:d84b06fb9002109bfc542e76860b81459a8585af0bbdabcfc5dcf272ef230de7"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:aa922d1d73881d0820a341d2c406a571cc94630bdcdc275427c844a12e6e376e"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:52e5b3a2371d7231de17515c7c78d8d4a39d70c8c095e71d55b3b83434a193a8"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4a75bde5cda259bb31f2294960d75b9d5c148001b2b0bd20a91f9c2116675a6c"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:5314f6f08d2bcbc031778618ba97d9098d106119c2e616b3b081171fe42f5415"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:0ad7cc3776ca17d0bededcc352cba2b1c89eb4817bfabaf05972b9da8c424935"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:cccdad6cfe7a5db7d7eb8df2e5678f8375268739d5933214e180da300aa54e37"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:fb6af82989dac7c58bd25ed9ba3323bc443f8c1f03804f69c9f5e363bf4a021c"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-win32.whl", hash = "sha256:68769f5e6722474bb1038e35560444659db8b951388bfe0c669bb52a640cd0eb"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-win_amd64.whl", hash = "sha256:438a9e0d72a57d5bba4f112d256e39ea4033c76c65414c0693d8311faa14b090"},
|
||||
{file = "pyinstaller-6.0.0-py3-none-win_arm64.whl", hash = "sha256:16a473065291dd7879bf596fa20e65bd9d1e8aafc2cef1bffa3e42e707e2e68e"},
|
||||
{file = "pyinstaller-6.0.0.tar.gz", hash = "sha256:d702cff041f30e7a53500b630e07b081e5328d4655023319253d73935e75ade2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
altgraph = "*"
|
||||
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
|
||||
packaging = ">=20.0"
|
||||
pefile = {version = ">=2022.5.30", markers = "sys_platform == \"win32\""}
|
||||
pyinstaller-hooks-contrib = ">=2021.4"
|
||||
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
|
||||
setuptools = ">=42.0.0"
|
||||
|
||||
[package.extras]
|
||||
hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyinstaller-hooks-contrib"
|
||||
version = "2023.9"
|
||||
description = "Community maintained hooks for PyInstaller"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pyinstaller-hooks-contrib-2023.9.tar.gz", hash = "sha256:76084b5988e3957a9df169d2a935d65500136967e710ddebf57263f1a909cd80"},
|
||||
{file = "pyinstaller_hooks_contrib-2023.9-py2.py3-none-any.whl", hash = "sha256:f34f4c6807210025c8073ebe665f422a3aa2ac5f4c7ebf4c2a26cc77bebf63b5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32-ctypes"
|
||||
version = "0.2.2"
|
||||
description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"},
|
||||
{file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "68.2.2"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"},
|
||||
{file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.11,<3.13"
|
||||
content-hash = "797bde9c30c55b3ddb24b1d3eceedd093d8a89eb934e6fe8fe7191dc9247224d"
|
27
examples/code/Python/pyproject.toml
Normal file
27
examples/code/Python/pyproject.toml
Normal file
@ -0,0 +1,27 @@
|
||||
[tool.poetry]
|
||||
name = "sointu-python"
|
||||
version = "0.1.0"
|
||||
description = "Play back Sointu tracks in Python."
|
||||
authors = ["Alexander Kraus <nr4@z10.info>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
packages = [
|
||||
{ include = "sointu_python" },
|
||||
]
|
||||
include = [
|
||||
{ path = "sointu*.so", format="wheel" }
|
||||
]
|
||||
|
||||
[tool.poetry.build]
|
||||
script = "build.py"
|
||||
generate-setup-file = true
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.11,<3.13"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pyinstaller = "^6.0.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0a3", "poetry>=0.12", "setuptools", "wheel"]
|
||||
build-backend = "poetry.core.masonry.api"
|
212
examples/code/Python/sointu.c
Normal file
212
examples/code/Python/sointu.c
Normal file
@ -0,0 +1,212 @@
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include "Python.h"
|
||||
|
||||
#include TRACK_HEADER
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
SUsample sound_buffer[SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT];
|
||||
#ifdef WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_EXTRA_LEAN
|
||||
#include <Windows.h>
|
||||
#include "mmsystem.h"
|
||||
#include "mmreg.h"
|
||||
#define CINTERFACE
|
||||
#include <dsound.h>
|
||||
|
||||
static WAVEFORMATEX wave_format = {
|
||||
#ifdef SU_SAMPLE_FLOAT
|
||||
WAVE_FORMAT_IEEE_FLOAT,
|
||||
#else
|
||||
WAVE_FORMAT_PCM,
|
||||
#endif
|
||||
SU_CHANNEL_COUNT,
|
||||
SU_SAMPLE_RATE,
|
||||
SU_SAMPLE_RATE * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
|
||||
SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
|
||||
SU_SAMPLE_SIZE*8,
|
||||
0
|
||||
};
|
||||
DSBUFFERDESC buffer_description = {
|
||||
sizeof(DSBUFFERDESC),
|
||||
DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_TRUEPLAYPOSITION,
|
||||
SU_LENGTH_IN_SAMPLES * SU_SAMPLE_SIZE * SU_CHANNEL_COUNT,
|
||||
0,
|
||||
&wave_format,
|
||||
0
|
||||
};
|
||||
|
||||
static HWND hWnd;
|
||||
static LPDIRECTSOUND direct_sound;
|
||||
static LPDIRECTSOUNDBUFFER direct_sound_buffer;
|
||||
static LPVOID p1;
|
||||
static DWORD l1;
|
||||
static DWORD last_play_cursor = 0;
|
||||
#endif /* WIN32 */
|
||||
|
||||
#ifdef UNIX
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
|
||||
static snd_pcm_t *pcm_handle;
|
||||
static pthread_t render_thread;
|
||||
static uint32_t render_thread_handle;
|
||||
static pthread_t playback_thread;
|
||||
static uint32_t playback_thread_handle;
|
||||
snd_htimestamp_t start_ts;
|
||||
|
||||
static int _snd_pcm_writei(void *params) {
|
||||
(void) params;
|
||||
snd_pcm_writei(pcm_handle, sound_buffer, SU_LENGTH_IN_SAMPLES);
|
||||
return 0;
|
||||
}
|
||||
#endif /* UNIX */
|
||||
|
||||
static PyObject *sointuError;
|
||||
|
||||
static PyObject *sointu_play_song(PyObject *self, PyObject *args) {
|
||||
#ifdef WIN32
|
||||
|
||||
#ifdef SU_LOAD_GMDLS
|
||||
su_load_gmdls();
|
||||
#endif // SU_LOAD_GMDLS
|
||||
|
||||
hWnd = GetForegroundWindow();
|
||||
if(hWnd == NULL) {
|
||||
hWnd = GetDesktopWindow();
|
||||
}
|
||||
|
||||
DirectSoundCreate(0, &direct_sound, 0);
|
||||
IDirectSound_SetCooperativeLevel(direct_sound, hWnd, DSSCL_PRIORITY);
|
||||
IDirectSound_CreateSoundBuffer(direct_sound, &buffer_description, &direct_sound_buffer, NULL);
|
||||
IDirectSoundBuffer_Lock(direct_sound_buffer, 0, SU_LENGTH_IN_SAMPLES * SU_CHANNEL_COUNT * SU_SAMPLE_SIZE, &p1, &l1, NULL, NULL, 0);
|
||||
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)su_render_song, p1, 0, 0);
|
||||
IDirectSoundBuffer_Play(direct_sound_buffer, 0, 0, 0);
|
||||
|
||||
#endif /* WIN32 */
|
||||
|
||||
#ifdef UNIX
|
||||
render_thread_handle = pthread_create(&render_thread, 0, (void * (*)(void *))su_render_song, sound_buffer);
|
||||
|
||||
// We can't start playing too early or the missing samples will be audible.
|
||||
sleep(2.);
|
||||
|
||||
// Play the track.
|
||||
snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
|
||||
snd_pcm_set_params(
|
||||
pcm_handle,
|
||||
#ifdef SU_SAMPLE_FLOAT
|
||||
SND_PCM_FORMAT_FLOAT,
|
||||
#else // SU_SAMPLE_FLOAT
|
||||
SND_PCM_FORMAT_S16_LE,
|
||||
#endif // SU_SAMPLE_FLOAT
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED,
|
||||
SU_CHANNEL_COUNT,
|
||||
SU_SAMPLE_RATE,
|
||||
0,
|
||||
SU_LENGTH_IN_SAMPLES
|
||||
);
|
||||
|
||||
// Enable playback time querying.
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
snd_pcm_sw_params_current(pcm_handle, swparams);
|
||||
snd_pcm_sw_params_set_tstamp_mode(pcm_handle, swparams, SND_PCM_TSTAMP_ENABLE);
|
||||
snd_pcm_sw_params_set_tstamp_type(pcm_handle, swparams, SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY);
|
||||
snd_pcm_sw_params(pcm_handle, swparams);
|
||||
|
||||
playback_thread_handle = pthread_create(&playback_thread, 0, (void *(*)(void *))_snd_pcm_writei, 0);
|
||||
|
||||
// Get the start time stamp.
|
||||
snd_pcm_uframes_t avail;
|
||||
snd_pcm_htimestamp(pcm_handle, &avail, &start_ts);
|
||||
#endif /* UNIX */
|
||||
|
||||
return PyLong_FromLong(0);
|
||||
}
|
||||
|
||||
static PyObject *sointu_playback_position(PyObject *self, PyObject *args) {
|
||||
#ifdef WIN32
|
||||
DWORD play_cursor = 0;
|
||||
IDirectSoundBuffer_GetCurrentPosition(direct_sound_buffer, (DWORD*)&play_cursor, NULL);
|
||||
return Py_BuildValue("i", play_cursor / SU_CHANNEL_COUNT / sizeof(SUsample));
|
||||
#endif /* WIN32 */
|
||||
|
||||
#ifdef UNIX
|
||||
snd_htimestamp_t ts;
|
||||
snd_pcm_uframes_t avail;
|
||||
snd_pcm_htimestamp(pcm_handle, &avail, &ts);
|
||||
|
||||
return Py_BuildValue("i", (int)((ts.tv_sec - start_ts.tv_sec + 1.e-9 * (ts.tv_nsec - start_ts.tv_nsec)) * SU_SAMPLE_RATE));
|
||||
#endif /* UNIX */
|
||||
}
|
||||
|
||||
static PyObject *sointu_playback_finished(PyObject *self, PyObject *args) {
|
||||
bool result = false;
|
||||
|
||||
#ifdef WIN32
|
||||
DWORD play_cursor = 0;
|
||||
IDirectSoundBuffer_GetCurrentPosition(direct_sound_buffer, (DWORD*)&play_cursor, NULL);
|
||||
result = play_cursor < last_play_cursor;
|
||||
last_play_cursor = play_cursor;
|
||||
#endif /* WIN32 */
|
||||
|
||||
#ifdef UNIX
|
||||
snd_htimestamp_t ts;
|
||||
snd_pcm_uframes_t avail;
|
||||
snd_pcm_htimestamp(pcm_handle, &avail, &ts);
|
||||
|
||||
result = ts.tv_sec - start_ts.tv_sec < 0;
|
||||
#endif /* UNIX */
|
||||
|
||||
return PyBool_FromLong(result);
|
||||
}
|
||||
|
||||
static PyObject *sointu_sample_rate(PyObject *self, PyObject *args) {
|
||||
return Py_BuildValue("i", SU_SAMPLE_RATE);
|
||||
}
|
||||
|
||||
static PyObject *sointu_track_length(PyObject *self, PyObject *args) {
|
||||
return Py_BuildValue("i", SU_LENGTH_IN_SAMPLES);
|
||||
}
|
||||
|
||||
static PyMethodDef sointuMethods[] = {
|
||||
{"play_song", sointu_play_song, METH_VARARGS, "Play sointu track."},
|
||||
{"playback_position", sointu_playback_position, METH_VARARGS, "Get playback position of sointu track currently playing."},
|
||||
{"playback_finished", sointu_playback_finished, METH_VARARGS, "Check if currently playing sointu track has finished playing."},
|
||||
{"sample_rate", sointu_sample_rate, METH_VARARGS, "Return the sample rate of the track compiled into this module."},
|
||||
{"track_length", sointu_track_length, METH_VARARGS, "Return the track length in samples."},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
static struct PyModuleDef sointumodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"sointu",
|
||||
NULL,
|
||||
-1,
|
||||
sointuMethods
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit_sointu(void) {
|
||||
PyObject *module = PyModule_Create(&sointumodule);
|
||||
if(module == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sointuError = PyErr_NewException("sointu.sointuError", NULL, NULL);
|
||||
Py_XINCREF(sointuError);
|
||||
|
||||
if(PyModule_AddObject(module, "error", sointuError) < 0) {
|
||||
Py_XDECREF(sointuError);
|
||||
Py_CLEAR(sointuError);
|
||||
Py_DECREF(module);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
0
examples/code/Python/sointu_python/__init__.py
Normal file
0
examples/code/Python/sointu_python/__init__.py
Normal file
16
examples/code/Python/sointu_python/__main__.py
Normal file
16
examples/code/Python/sointu_python/__main__.py
Normal file
@ -0,0 +1,16 @@
|
||||
from sointu import (
|
||||
play_song,
|
||||
playback_position,
|
||||
playback_finished,
|
||||
sample_rate,
|
||||
track_length,
|
||||
)
|
||||
from sys import exit
|
||||
|
||||
if __name__ == '__main__':
|
||||
play_song()
|
||||
|
||||
while not playback_finished():
|
||||
print("Playback time:", playback_position() / sample_rate())
|
||||
|
||||
exit(0)
|
58
examples/code/Python/sointu_python/sointu_python.spec
Normal file
58
examples/code/Python/sointu_python/sointu_python.spec
Normal file
@ -0,0 +1,58 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
from os.path import abspath, join
|
||||
from zipfile import ZipFile
|
||||
from platform import system
|
||||
|
||||
moduleName = 'sointu_python'
|
||||
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=[],
|
||||
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=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon=None,
|
||||
)
|
||||
|
||||
exeFileName = '{}{}'.format(moduleName, '.exe' if system() == 'Windows' else '')
|
||||
zipFileName = '{}-{}.zip'.format(moduleName, 'windows' if system() == 'Windows' else 'linux')
|
||||
ZipFile(join(distPath, zipFileName), mode='w').write(join(distPath, exeFileName), arcname=exeFileName)
|
Loading…
Reference in New Issue
Block a user