Skip to content

Commit 77fc17e

Browse files
authored
Replaces CFFI bindings by pybind11 bindings (#10)
Replaces CFFI bindings by pybind11 bindings, adds github action workflow for binary wheels compile, modifies setup to use pyproject file.
1 parent ed73d7a commit 77fc17e

31 files changed

Lines changed: 1405 additions & 3204 deletions
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: samplerate
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ${{ matrix.os }}
9+
strategy:
10+
fail-fast: false
11+
max-parallel: 12
12+
matrix:
13+
os: [ubuntu-latest, macos-latest, windows-latest]
14+
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
15+
steps:
16+
- uses: actions/checkout@v3
17+
- name: Checkout submodules
18+
shell: bash
19+
run: |
20+
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
21+
git submodule sync --recursive
22+
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
23+
- name: Set up Python ${{ matrix.python-version }}
24+
uses: actions/setup-python@v4
25+
with:
26+
python-version: ${{ matrix.python-version }}
27+
- name: Install dependencies
28+
run: |
29+
python -m pip install --upgrade pip
30+
pip install -U setuptools setuptools_scm wheel build twine
31+
pip install -r requirements.txt
32+
- name: Build package
33+
run: |
34+
python -m pip install -e .
35+
- name: Test with pytest
36+
run: |
37+
pytest
38+
- name: Test the universal wheels
39+
if: matrix.os == 'ubuntu-latest'
40+
run: |
41+
# do not build binary wheels on linux
42+
python -m build --sdist
43+
twine check dist/*
44+
- name: Test the binary wheels
45+
if: matrix.os != 'ubuntu-latest'
46+
run: |
47+
python -m build
48+
twine check dist/*
49+
- name: Publish sdist to pypi
50+
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') && matrix.os == 'ubuntu-latest'
51+
env:
52+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
53+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
54+
run: |
55+
twine upload --skip-existing dist/*
56+
- name: Publish bdist to pypi
57+
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') && matrix.os != 'ubuntu-latest'
58+
env:
59+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
60+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
61+
run: |
62+
twine upload --skip-existing dist/*
63+
- name: Publish sdist to pypi-test
64+
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/test-') && matrix.os == 'ubuntu-latest'
65+
env:
66+
TWINE_USERNAME: ${{ secrets.PYPITEST_USERNAME }}
67+
TWINE_PASSWORD: ${{ secrets.PYPITEST_PASSWORD }}
68+
TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/
69+
run: |
70+
twine upload --skip-existing dist/*
71+
- name: Publish bdist to pypi-test
72+
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/test-') && matrix.os != 'ubuntu-latest'
73+
env:
74+
TWINE_USERNAME: ${{ secrets.PYPITEST_USERNAME }}
75+
TWINE_PASSWORD: ${{ secrets.PYPITEST_PASSWORD }}
76+
TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/
77+
run: |
78+
twine upload --skip-existing dist/*

CMakeLists.txt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# https://stackoverflow.com/questions/51907755/building-a-pybind11-module-with-cpp-and-cuda-sources-using-cmake
2+
3+
cmake_minimum_required(VERSION 3.15)
4+
5+
message(STATUS "Found Python prefix ${PYTHON_PREFIX}")
6+
list(PREPEND CMAKE_PREFIX_PATH "${PYTHON_PREFIX}")
7+
8+
project(python-samplerate)
9+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
10+
11+
cmake_policy(SET CMP0094 NEW)
12+
13+
# adds the external dependencies
14+
add_subdirectory(external)
15+
16+
pybind11_add_module(python-samplerate src/samplerate.cpp)
17+
18+
target_include_directories(python-samplerate PRIVATE ./external/libsamplerate/include)
19+
20+
if(MSVC)
21+
target_compile_options(python-samplerate PRIVATE /EHsc /MP /bigobj)
22+
set(CMAKE_EXE_LINKER_FLAGS /MANIFEST:NO)
23+
endif()
24+
25+
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR
26+
CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR
27+
(CMAKE_CXX_COMPILER_ID MATCHES "Intel" AND NOT WIN32))
28+
target_compile_options(python-samplerate PRIVATE -std=c++14 -O3 -Wall -Wextra)
29+
endif()
30+
31+
### stick the package and libsamplerate version into the module
32+
target_compile_definitions(python-samplerate
33+
PUBLIC LIBSAMPLERATE_VERSION="${LIBSAMPLERATE_VERSION}"
34+
PRIVATE $<$<BOOL:${PACKAGE_VERSION_INFO}>:VERSION_INFO="${PACKAGE_VERSION_INFO}">
35+
)
36+
37+
### Final target setup
38+
set_target_properties(
39+
python-samplerate
40+
PROPERTIES
41+
PREFIX ""
42+
OUTPUT_NAME "samplerate"
43+
LINKER_LANGUAGE C
44+
)
45+
46+
target_link_libraries(python-samplerate PUBLIC samplerate)

MANIFEST.in

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
include README.rst
22
include LICENSE.rst
33
include versioneer.py
4-
include samplerate/_version.py
5-
include samplerate/_samplerate_data/*.dylib
6-
include samplerate/_samplerate_data/*.dll
4+
include src/*.cpp
5+
include CMakeLists.txt
6+
include external/CMakeLists.txt
7+
include requirements.txt
78
include examples/*.py

README.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,17 @@ It implements all three `APIs
3232
* **Callback API**: like Full API, but input samples are provided by a callback
3333
function
3434

35-
Library calls to `libsamplerate`_ are performed using `CFFI
36-
<http://cffi.readthedocs.io/en/latest/>`_.
35+
The `libsamplerate`_ library is statically built together with the python bindings
36+
using `pybind11 <https://github.com/pybind/pybind11/>`_.
3737

3838

3939
Installation
4040
------------
4141

4242
$ pip install samplerate
4343

44-
Binaries of `libsamplerate`_ for macOS and Windows (32 and 64 bit) are included
45-
and used if not present on the system. On Linux systems, you should also install
46-
`libsamplerate0` (Debian derivatives), `libsamplerate` (Arch), or similar.
44+
Binary wheels of `libsamplerate`_ for macOS and Windows (64 bit) are available.
45+
For other systems, a C++ 14 or above compiler is required to build the package.
4746

4847

4948
Usage

docs/conf.py

Lines changed: 47 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,9 @@
1818
# documentation root, use os.path.abspath to make it absolute, like shown here.
1919
#
2020
# import os
21-
import sys
22-
# sys.path.insert(0, os.path.abspath('.'))
23-
24-
# Mock C modules
25-
try:
26-
from unittest.Mock import MagicMock # Python >3.3
27-
except ImportError:
28-
try:
29-
from mock import MagicMock
30-
except ImportError:
31-
raise ImportError('No module named mock')
32-
33-
class Mock(MagicMock):
34-
@classmethod
35-
def __getattr__(cls, name):
36-
return MagicMock()
21+
# import sys
3722

38-
mock_modules = ['numpy', 'samplerate._src']
39-
sys.modules.update((mod_name, Mock()) for mod_name in mock_modules)
23+
# sys.path.insert(0, os.path.abspath('.'))
4024

4125
import samplerate
4226

@@ -49,25 +33,24 @@ def __getattr__(cls, name):
4933
# Add any Sphinx extension module names here, as strings. They can be
5034
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
5135
# ones.
52-
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode',
53-
'sphinx.ext.napoleon']
36+
extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon"]
5437

5538
# Add any paths that contain templates here, relative to this directory.
56-
templates_path = ['_templates']
39+
templates_path = ["_templates"]
5740

5841
# The suffix(es) of source filenames.
5942
# You can specify multiple suffix as a list of string:
6043
#
6144
# source_suffix = ['.rst', '.md']
62-
source_suffix = '.rst'
45+
source_suffix = ".rst"
6346

6447
# The master toctree document.
65-
master_doc = 'index'
48+
master_doc = "index"
6649

6750
# General information about the project.
68-
project = 'python-samplerate'
69-
copyright = '2017, Tino Wagner'
70-
author = 'Tino Wagner'
51+
project = "python-samplerate"
52+
copyright = "2017, Tino Wagner"
53+
author = "Tino Wagner"
7154

7255
# The version info for the project you're documenting, acts as replacement for
7356
# |version| and |release|, also used in various other places throughout the
@@ -77,28 +60,31 @@ def __getattr__(cls, name):
7760
# The full version, including alpha/beta/rc tags.
7861
release = samplerate.__version__
7962

63+
8064
# The short X.Y version.
8165
def get_short_version(version):
8266
"""Return short version from PEP-440 compatible version string."""
83-
if '+' in version:
84-
return version[:version.find('+')]
67+
if "+" in version:
68+
return version[: version.find("+")]
8569
return version
70+
71+
8672
version = get_short_version(release)
8773

8874
# The language for content autogenerated by Sphinx. Refer to documentation
8975
# for a list of supported languages.
9076
#
9177
# This is also used if you do content translation via gettext catalogs.
9278
# Usually you set "language" from the command line for these cases.
93-
language = None
79+
language = "en"
9480

9581
# List of patterns, relative to source directory, that match files and
9682
# directories to ignore when looking for source files.
9783
# This patterns also effect to html_static_path and html_extra_path
9884
exclude_patterns = []
9985

10086
# The name of the Pygments (syntax highlighting) style to use.
101-
pygments_style = 'sphinx'
87+
pygments_style = "sphinx"
10288

10389
# If true, `todo` and `todoList` produce output, else they produce nothing.
10490
todo_include_todos = False
@@ -109,40 +95,40 @@ def get_short_version(version):
10995
# The theme to use for HTML and HTML Help pages. See the documentation for
11096
# a list of builtin themes.
11197
#
112-
html_theme = 'alabaster'
98+
html_theme = "alabaster"
11399

114100
# Theme options are theme-specific and customize the look and feel of a theme
115101
# further. For a list of options available for each theme, see the
116102
# documentation.
117103
#
118104
html_theme_options = {
119-
'description': 'Sample rate conversion in Python using libsamplerate',
120-
'github_user': 'tuxu',
121-
'github_repo': 'python-samplerate',
122-
'github_banner': True,
123-
'fixed_sidebar': True,
105+
"description": "Sample rate conversion in Python using libsamplerate",
106+
"github_user": "tuxu",
107+
"github_repo": "python-samplerate",
108+
"github_banner": True,
109+
"fixed_sidebar": True,
124110
}
125111

126112
html_sidebars = {
127-
'**': [
128-
'about.html',
129-
'navigation.html',
130-
'relations.html',
131-
'searchbox.html',
132-
'donate.html',
113+
"**": [
114+
"about.html",
115+
"navigation.html",
116+
"relations.html",
117+
"searchbox.html",
118+
"donate.html",
133119
]
134120
}
135121

136122
# Add any paths that contain custom static files (such as style sheets) here,
137123
# relative to this directory. They are copied after the builtin static files,
138124
# so a file named "default.css" will overwrite the builtin "default.css".
139-
html_static_path = ['_static']
125+
html_static_path = ["_static"]
140126

141127

142128
# -- Options for HTMLHelp output ------------------------------------------
143129

144130
# Output file base name for HTML help builder.
145-
htmlhelp_basename = 'python-sampleratedoc'
131+
htmlhelp_basename = "python-sampleratedoc"
146132

147133

148134
# -- Options for LaTeX output ---------------------------------------------
@@ -151,15 +137,12 @@ def get_short_version(version):
151137
# The paper size ('letterpaper' or 'a4paper').
152138
#
153139
# 'papersize': 'letterpaper',
154-
155140
# The font size ('10pt', '11pt' or '12pt').
156141
#
157142
# 'pointsize': '10pt',
158-
159143
# Additional stuff for the LaTeX preamble.
160144
#
161145
# 'preamble': '',
162-
163146
# Latex figure (float) alignment
164147
#
165148
# 'figure_align': 'htbp',
@@ -169,8 +152,13 @@ def get_short_version(version):
169152
# (source start file, target name, title,
170153
# author, documentclass [howto, manual, or own class]).
171154
latex_documents = [
172-
(master_doc, 'python-samplerate.tex', 'python-samplerate Documentation',
173-
'Tino Wagner', 'manual'),
155+
(
156+
master_doc,
157+
"python-samplerate.tex",
158+
"python-samplerate Documentation",
159+
"Tino Wagner",
160+
"manual",
161+
),
174162
]
175163

176164

@@ -179,8 +167,7 @@ def get_short_version(version):
179167
# One entry per manual page. List of tuples
180168
# (source start file, name, description, authors, manual section).
181169
man_pages = [
182-
(master_doc, 'python-samplerate', 'python-samplerate Documentation',
183-
[author], 1)
170+
(master_doc, "python-samplerate", "python-samplerate Documentation", [author], 1)
184171
]
185172

186173

@@ -190,7 +177,13 @@ def get_short_version(version):
190177
# (source start file, target name, title, author,
191178
# dir menu entry, description, category)
192179
texinfo_documents = [
193-
(master_doc, 'python-samplerate', 'python-samplerate Documentation',
194-
author, 'python-samplerate', 'One line description of project.',
195-
'Miscellaneous'),
180+
(
181+
master_doc,
182+
"python-samplerate",
183+
"python-samplerate Documentation",
184+
author,
185+
"python-samplerate",
186+
"One line description of project.",
187+
"Miscellaneous",
188+
),
196189
]

docs/samplerate/index.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@
77
samplerate
88
converters
99
exceptions
10-
lowlevel

docs/samplerate/lowlevel.rst

Lines changed: 0 additions & 4 deletions
This file was deleted.

examples/play_modulation.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
is generated at source samplerate, then resampled to target samplerate, and
77
mixed onto the carrier.
88
"""
9-
from __future__ import print_function, division
9+
from __future__ import division, print_function
10+
1011
import numpy as np
1112
import sounddevice as sd
13+
1214
import samplerate as sr
1315

1416
source_samplerate = 3600

0 commit comments

Comments
 (0)