Skip to content

Commit

Permalink
Fix OSX GHA (#324)
Browse files Browse the repository at this point in the history
* Fix OSX GHA

* Fixup OSX test under GHA

* Bump dependencies

* RM unused code

* Fixup tests

* Move pytest_plugins definition

* Add more smoke tests

* Fixup tests

* Remove dead code
  • Loading branch information
josiah-wolf-oberholtzer authored Mar 9, 2023
1 parent 7e2d623 commit d6634ea
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 209 deletions.
3 changes: 1 addition & 2 deletions .github/actions/supercollider/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,7 @@ runs:
cmake \
-DCMAKE_PREFIX_PATH=`brew --prefix qt5` \
-DRULE_LAUNCH_COMPILE=ccache \
-DSC_CODESIGN_AFTER_DEPLOY=OFF \
-DSC_DISABLE_XCODE_CODESIGNING=ON \
-DSC_VERIFY_APP=ON \
-DSC_ED=OFF \
-DSC_EL=OFF \
-DSC_IDE=OFF \
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ jobs:
os: [ubuntu-latest]
python-version: [3.8, 3.9, "3.10"]
include:
# - os: macos-latest
# python-version: 3.9
- os: macos-latest
python-version: "3.10"
- os: windows-latest
python-version: "3.10"
timeout-minutes: 45
Expand Down
12 changes: 5 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,25 @@ classifiers = [
]
dependencies = [
"PyYAML >= 6.0",
"platformdirs >= 2.6.0",
"platformdirs >= 3.1.0",
"uqbar >= 0.6.9",
'typing-extensions; python_version<"3.8"',
]
description = "A Python API for SuperCollider"
dynamic = ["version"]
keywords = ["audio", "dsp", "music composition", "scsynth", "supercollider", "synthesis"]
license = {text = "MIT"}
name = "supriya"
readme = "README.md"
requires-python = ">=3.8"
requires-python = ">= 3.8"

[project.optional-dependencies]
docs = [
"jupyter",
"librosa",
"matplotlib >= 3.3.0",
"mypy >= 0.990",
"mypy >= 1.1.0",
# "numpy <= 1.22", # librosa imports numba 0.56 which requires numpy 1.22 or lower
"sphinx-immaterial >= 0.11.0",
"sphinx-immaterial >= 0.11.3",
"sphinxext-opengraph",
]
ipython = [
Expand All @@ -62,9 +61,8 @@ test = [
"matplotlib >= 3.3.0",
"mypy >= 0.990",
"pytest >= 7.0.0",
"pytest-asyncio >= 0.19.0",
"pytest-asyncio",
"pytest-cov",
"pytest-helpers-namespace",
"pytest-mock",
"pytest-rerunfailures",
"types-PyYAML",
Expand Down
47 changes: 47 additions & 0 deletions supriya/osc.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ class OscMessage:
>>> OscMessage.from_datagram(datagram)
OscMessage('/g_new', 0, 0)
::
>>> print(osc_message)
size 20
0 2f 67 5f 6e 65 77 00 00 2c 69 69 00 00 00 00 00 |/g_new..,ii.....|
16 00 00 00 00 |....|
.. container:: example
::
Expand All @@ -84,6 +91,14 @@ class OscMessage:
>>> OscMessage.from_datagram(datagram)
OscMessage('/foo', True, [None, [3.25]], OscMessage('/bar'))
::
>>> print(osc_message)
size 40
0 2f 66 6f 6f 00 00 00 00 2c 54 5b 4e 5b 66 5d 5d |/foo....,T[N[f]]|
16 62 00 00 00 40 50 00 00 00 00 00 0c 2f 62 61 72 |b...@P....../bar|
32 00 00 00 00 2c 00 00 00 |....,...|
.. container:: example
::
Expand Down Expand Up @@ -118,6 +133,18 @@ class OscMessage:
OscMessage('/ffff', False, True, None),
),
), ['a', 'b', ['c', 'd']])
::
>>> print(osc_message)
size 112
0 2f 66 6f 6f 00 00 00 00 2c 69 66 62 5b 73 73 5b |/foo....,ifb[ss[|
16 73 73 5d 5d 00 00 00 00 00 00 00 01 40 20 00 00 |ss]]........@ ..|
32 00 00 00 3c 23 62 75 6e 64 6c 65 00 00 00 00 00 |...<#bundle.....|
48 00 00 00 01 00 00 00 14 2f 62 61 72 00 00 00 00 |......../bar....|
64 2c 73 66 00 62 61 7a 00 40 40 00 00 00 00 00 10 |,sf.baz.@@......|
80 2f 66 66 66 66 00 00 00 2c 46 54 4e 00 00 00 00 |/ffff...,FTN....|
96 61 00 00 00 62 00 00 00 63 00 00 00 64 00 00 00 |a...b...c...d...|
"""

### INITIALIZER ###
Expand Down Expand Up @@ -316,6 +343,15 @@ class OscBundle:
timestamp=1401557034.5,
)
::
>>> print(inner_bundle)
size 56
0 23 62 75 6e 64 6c 65 00 d7 34 8e aa 80 00 00 00 |#bundle..4......|
16 00 00 00 10 2f 6f 6e 65 00 00 00 00 2c 69 00 00 |..../one....,i..|
32 00 00 00 01 00 00 00 10 2f 74 77 6f 00 00 00 00 |......../two....|
48 2c 69 00 00 00 00 00 02 |,i......|
::
>>> outer_bundle = supriya.osc.OscBundle(
Expand All @@ -335,6 +371,17 @@ class OscBundle:
),
)
::
>>> print(outer_bundle)
size 96
0 23 62 75 6e 64 6c 65 00 00 00 00 00 00 00 00 01 |#bundle.........|
16 00 00 00 38 23 62 75 6e 64 6c 65 00 d7 34 8e aa |...8#bundle..4..|
32 80 00 00 00 00 00 00 10 2f 6f 6e 65 00 00 00 00 |......../one....|
48 2c 69 00 00 00 00 00 01 00 00 00 10 2f 74 77 6f |,i........../two|
64 00 00 00 00 2c 69 00 00 00 00 00 02 00 00 00 10 |....,i..........|
80 2f 74 68 72 65 65 00 00 2c 69 00 00 00 00 00 03 |/three..,i......|
::
>>> datagram = outer_bundle.to_datagram()
Expand Down
77 changes: 0 additions & 77 deletions supriya/soundfiles.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
"""
Tools for interacting with soundfiles.
"""
import aifc
import asyncio
import dataclasses
import hashlib
import shlex
import sndhdr
import wave
from os import PathLike
from pathlib import Path
from typing import Optional, Tuple
Expand Down Expand Up @@ -142,77 +139,3 @@ def _build_output_file_path(
render_directory_path = Path(output_path).resolve()
output_file_path = render_directory_path / self._build_file_path()
return output_file_path


class SoundFile:
### INITIALIZER ###

def __init__(self, file_path):
self._file_path = Path(file_path)
if not self._file_path.exists():
raise ValueError(self._file_path)
headers = sndhdr.what(self._file_path)
self._frame_count = headers.nframes
self._sample_rate = headers.framerate
self._channel_count = headers.nchannels
self._sample_width = headers.sampwidth
self._file_type = headers.filetype

### PUBLIC METHODS ###

def at_frame(self, frame):
if self.file_type not in ("aiff", "wav"):
raise ValueError(self.file_type)
if not (0 <= frame <= self.frame_count):
raise ValueError(frame)
if self.sample_width not in (16, 24, 32):
raise ValueError(f"Cannot decode sample width {self.sample_width}")
with open(self.file_path, "rb") as file_pointer:
reader_proc = {"aiff": aifc.open, "wav": wave.open}[self.file_type]
with reader_proc(file_pointer, "rb") as reader:
reader.setpos(frame)
raw_data = reader.readframes(1)
maximum = 2 ** (self.sample_width - 1)
integers = []
stride = self.sample_width // 8
endianness = "big" if self.file_type == "aiff" else "little"
for i in range(self.channel_count):
chunk = raw_data[i * stride : i * stride + stride]
integers.append(int.from_bytes(chunk, endianness, signed=True))
return [float(x) / maximum for x in integers]

def at_percent(self, percent):
return self.at_frame(int(self.frame_count * percent))

def at_second(self, second):
return self.at_frame(int(second * self.sample_rate))

### PUBLIC PROPERTIES ###

@property
def channel_count(self):
return self._channel_count

@property
def seconds(self):
return float(self._frame_count) / float(self._sample_rate)

@property
def file_path(self):
return self._file_path

@property
def file_type(self):
return self._file_type

@property
def frame_count(self):
return self._frame_count

@property
def sample_rate(self):
return self._sample_rate

@property
def sample_width(self):
return self._sample_width
4 changes: 0 additions & 4 deletions supriya/synthdefs/synthdefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,6 @@ def _optimize_ugen_graph(ugens):
ugen._optimize_graph(sort_bundles)
return tuple(sort_bundles)

def _register_with_local_server(self, server):
synthdef_name = self.actual_name
server._synthdefs[synthdef_name] = self

@staticmethod
def _remap_controls(ugens, control_mapping):
for ugen in ugens:
Expand Down
116 changes: 2 additions & 114 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import pathlib
import platform

import pytest

import supriya

pytest_plugins = ["helpers_namespace", "sphinx.testing.fixtures"]


# ### FIXTURES ### #
pytest_plugins = ["sphinx.testing.fixtures"]


@pytest.fixture
def server():
server = supriya.Server()
server.set_latency(0.0)
server.boot()
server.add_synthdefs(supriya.assets.synthdefs.default)
server.add_synthdefs(supriya.default)
server.sync()
yield server
server.quit()
Expand All @@ -29,109 +23,3 @@ def persistent_server():
server.boot()
yield server
server.quit()


# ### HELPERS ### #


@pytest.helpers.register
def assert_soundfile_ok(
file_path,
exit_code,
expected_duration,
expected_sample_rate,
expected_channel_count,
):
file_path = pathlib.Path(file_path)
assert file_path.exists(), file_path
if platform.system() != "Windows":
# scsynth.exe renders but exits non-zero
# https://github.com/supercollider/supercollider/issues/5769
assert exit_code == 0, exit_code
soundfile = supriya.soundfiles.SoundFile(file_path)
assert round(soundfile.seconds, 2) == expected_duration, round(soundfile.seconds, 2)
assert soundfile.sample_rate == expected_sample_rate, soundfile.sample_rate
assert soundfile.channel_count == expected_channel_count, soundfile.channel_count


@pytest.helpers.register
def build_d_recv_commands(synthdefs):
d_recv_commands = []
synthdefs = sorted(synthdefs, key=lambda x: x.anonymous_name)
for synthdef in synthdefs:
compiled_synthdef = synthdef.compile(use_anonymous_name=True)
compiled_synthdef = bytearray(compiled_synthdef)
d_recv_commands.append(["/d_recv", compiled_synthdef])
return d_recv_commands


@pytest.helpers.register
def build_dc_synthdef(channel_count=1):
with supriya.synthdefs.SynthDefBuilder(out_bus=0, source=0) as builder:
source = supriya.ugens.K2A.ar(source=builder["source"])
supriya.ugens.Out.ar(bus=builder["out_bus"], source=[source] * channel_count)
return builder.build()


@pytest.helpers.register
def build_basic_synthdef(bus=0):
builder = supriya.synthdefs.SynthDefBuilder()
with builder:
supriya.ugens.Out.ar(bus=bus, source=supriya.ugens.SinOsc.ar())
return builder.build()


@pytest.helpers.register
def build_diskin_synthdef(channel_count=1):
with supriya.synthdefs.SynthDefBuilder(out_bus=0, buffer_id=0) as builder:
source = supriya.ugens.DiskIn.ar(
buffer_id=builder["buffer_id"], channel_count=channel_count
)
supriya.ugens.Out.ar(bus=builder["out_bus"], source=source)
return builder.build()


@pytest.helpers.register
def build_duration_synthdef(bus=0):
builder = supriya.synthdefs.SynthDefBuilder(duration=0)
with builder:
supriya.ugens.Out.ar(
bus=bus, source=supriya.ugens.Line.ar(duration=builder["duration"])
)
return builder.build()


@pytest.helpers.register
def build_gate_synthdef(bus=0):
builder = supriya.synthdefs.SynthDefBuilder(gate=1)
with builder:
envelope = supriya.synthdefs.Envelope.asr()
envgen = supriya.ugens.EnvGen.ar(envelope=envelope, gate=builder["gate"])
source = supriya.ugens.Saw.ar() * envgen
supriya.ugens.Out.ar(bus=bus, source=source)
return builder.build()


@pytest.helpers.register
def build_multiplier_synthdef(channel_count=1):
with supriya.synthdefs.SynthDefBuilder(
in_bus=0, out_bus=0, multiplier=1
) as builder:
source = supriya.ugens.In.ar(bus=builder["in_bus"], channel_count=channel_count)
supriya.ugens.ReplaceOut.ar(
bus=builder["out_bus"], source=source * builder["multiplier"]
)
return builder.build()


@pytest.helpers.register
def sample_soundfile(file_path, rounding=6):
soundfile = supriya.soundfiles.SoundFile(file_path)
return {
0.0: [round(x, rounding) for x in soundfile.at_percent(0)],
0.21: [round(x, rounding) for x in soundfile.at_percent(0.21)],
0.41: [round(x, rounding) for x in soundfile.at_percent(0.41)],
0.61: [round(x, rounding) for x in soundfile.at_percent(0.61)],
0.81: [round(x, rounding) for x in soundfile.at_percent(0.81)],
0.99: [round(x, rounding) for x in soundfile.at_percent(0.99)],
}
Loading

0 comments on commit d6634ea

Please sign in to comment.