init
This commit is contained in:
79
lib_audio_dsp/test/json/test_biquad_pipeline.py
Normal file
79
lib_audio_dsp/test/json/test_biquad_pipeline.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test biquad filter pipeline creation.
|
||||
"""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
from audio_dsp.models.biquad import Biquad
|
||||
|
||||
|
||||
def test_simple_biquad_pipeline():
|
||||
"""Test creating a simple biquad filter pipeline."""
|
||||
print("Creating simple stereo biquad pipeline...")
|
||||
|
||||
# Create a simple stereo biquad pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_biquad",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Simple Biquad",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "Biquad",
|
||||
"parameters": {
|
||||
"filter_type": {
|
||||
"type": "lowpass",
|
||||
"filter_freq": 1000,
|
||||
"q_factor": 0.707
|
||||
},
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoBiquad",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoBiquad", 0], ["StereoBiquad", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our biquad stage
|
||||
biquad_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "biquad":
|
||||
biquad_stage = stage
|
||||
break
|
||||
|
||||
assert biquad_stage is not None, "Could not find Biquad stage in pipeline"
|
||||
|
||||
assert biquad_stage.parameters.filter_type.type == "lowpass", \
|
||||
f"Expected filter_type 'lowpass', got {biquad_stage.parameters.filter_type.type}"
|
||||
|
||||
assert biquad_stage.parameters.filter_type.filter_freq == 1000, \
|
||||
f"Expected filter_freq 1000, got {biquad_stage.parameters.filter_type.filter_freq}"
|
||||
|
||||
assert biquad_stage.parameters.filter_type.q_factor == 0.707, \
|
||||
f"Expected q_factor 0.707, got {biquad_stage.parameters.filter_type.q_factor}"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_simple_biquad_pipeline()
|
||||
96
lib_audio_dsp/test/json/test_cascaded_biquads_pipeline.py
Normal file
96
lib_audio_dsp/test/json/test_cascaded_biquads_pipeline.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test cascaded biquads pipeline creation."""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
|
||||
|
||||
def test_parametric_eq_pipeline():
|
||||
"""Test creating a parametric EQ pipeline."""
|
||||
print("Creating parametric EQ pipeline...")
|
||||
|
||||
# Create a parametric EQ pipeline JSON with different filter types
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_cascaded_biquads",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Parametric EQ",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "ParametricEq8b",
|
||||
"parameters": {
|
||||
"filters": [
|
||||
{
|
||||
"type": "lowpass",
|
||||
"filter_freq": 10000,
|
||||
"q_factor": 0.707
|
||||
},
|
||||
{
|
||||
"type": "highpass",
|
||||
"filter_freq": 20,
|
||||
"q_factor": 0.707
|
||||
},
|
||||
{
|
||||
"type": "peaking",
|
||||
"filter_freq": 1000,
|
||||
"q_factor": 2.0,
|
||||
"gain_db": 6.0
|
||||
},
|
||||
{
|
||||
"type": "bypass"
|
||||
},
|
||||
{
|
||||
"type": "bypass"
|
||||
},
|
||||
{
|
||||
"type": "bypass"
|
||||
},
|
||||
{
|
||||
"type": "bypass"
|
||||
},
|
||||
{
|
||||
"type": "bypass"
|
||||
}
|
||||
]
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoEQ",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoEQ", 0], ["StereoEQ", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our parametric EQ stage
|
||||
eq_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "cascaded_biquads":
|
||||
eq_stage = stage
|
||||
break
|
||||
|
||||
assert eq_stage is not None, "Could not find Parametric EQ stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_parametric_eq_pipeline()
|
||||
69
lib_audio_dsp/test/json/test_compressor_pipeline.py
Normal file
69
lib_audio_dsp/test/json/test_compressor_pipeline.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test compressor pipeline creation."""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
|
||||
|
||||
def test_simple_compressor_pipeline():
|
||||
"""Test creating a simple compressor pipeline."""
|
||||
print("Creating simple stereo compressor pipeline...")
|
||||
|
||||
# Create a simple stereo compressor pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_compressor",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Simple Compressor",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "CompressorRMS",
|
||||
"parameters": {
|
||||
"ratio": 4.0,
|
||||
"threshold_db": -20.0,
|
||||
"attack_t": 0.01,
|
||||
"release_t": 0.2
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoCompressor",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoCompressor", 0], ["StereoCompressor", 1]],
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our compressor stage
|
||||
compressor_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "compressor_rms":
|
||||
compressor_stage = stage
|
||||
break
|
||||
|
||||
assert compressor_stage is not None, "Could not find Compressor stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_simple_compressor_pipeline()
|
||||
74
lib_audio_dsp/test/json/test_delay_pipeline.py
Normal file
74
lib_audio_dsp/test/json/test_delay_pipeline.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test delay pipeline creation."""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
from audio_dsp.stages.signal_chain import Delay
|
||||
|
||||
|
||||
def test_simple_delay_pipeline():
|
||||
"""Test creating a simple delay pipeline."""
|
||||
print("Creating simple stereo delay pipeline...")
|
||||
|
||||
# Create a simple stereo delay pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_delay",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Simple Delay",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "Delay",
|
||||
"config": {
|
||||
"max_delay": 2048,
|
||||
"units": "samples"
|
||||
},
|
||||
"parameters": {
|
||||
"delay": 1024,
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoDelay",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoDelay", 0], ["StereoDelay", 1]],
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our delay stage
|
||||
delay_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if isinstance(stage, Delay):
|
||||
delay_stage = stage
|
||||
break
|
||||
|
||||
assert delay_stage is not None, "Could not find Delay stage in pipeline"
|
||||
assert delay_stage.max_delay == 2048, f"Expected max_delay 2048, got {delay_stage.max_delay}"
|
||||
assert delay_stage.units == "samples", f"Expected units 'samples', got {delay_stage.units}"
|
||||
assert delay_stage.parameters.delay == 1024, f"Expected delay 1024, got {delay_stage.parameters.delay}"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_simple_delay_pipeline()
|
||||
125
lib_audio_dsp/test/json/test_envelope_detector_pipeline.py
Normal file
125
lib_audio_dsp/test/json/test_envelope_detector_pipeline.py
Normal file
@@ -0,0 +1,125 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test envelope detector pipeline creation."""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
|
||||
|
||||
def test_peak_envelope_detector_pipeline():
|
||||
"""Test creating a peak envelope detector pipeline."""
|
||||
print("Creating peak envelope detector pipeline...")
|
||||
|
||||
# Create a peak envelope detector pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_envelope_detector",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Peak Envelope Detector",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "EnvelopeDetectorPeak",
|
||||
"parameters": {
|
||||
"attack_t": 0.01,
|
||||
"release_t": 0.1
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0]],
|
||||
"name": "PeakDetector",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["inputs", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our envelope detector stage
|
||||
detector_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "envelope_detector_peak":
|
||||
detector_stage = stage
|
||||
break
|
||||
|
||||
assert detector_stage is not None, "Could not find Peak Envelope Detector stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
def test_rms_envelope_detector_pipeline():
|
||||
"""Test creating an RMS envelope detector pipeline."""
|
||||
print("Creating RMS envelope detector pipeline...")
|
||||
|
||||
# Create an RMS envelope detector pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_envelope_detector",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "RMS Envelope Detector",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "EnvelopeDetectorRMS",
|
||||
"parameters": {
|
||||
"attack_t": 0.05,
|
||||
"release_t": 0.2
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0]],
|
||||
"name": "RMSDetector",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["inputs", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our envelope detector stage
|
||||
detector_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "envelope_detector_rms":
|
||||
detector_stage = stage
|
||||
break
|
||||
|
||||
assert detector_stage is not None, "Could not find RMS Envelope Detector stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_peak_envelope_detector_pipeline()
|
||||
print("\n" + "="*50 + "\n")
|
||||
test_rms_envelope_detector_pipeline()
|
||||
408
lib_audio_dsp/test/json/test_json_parser.py
Normal file
408
lib_audio_dsp/test/json/test_json_parser.py
Normal file
@@ -0,0 +1,408 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
|
||||
from pathlib import Path
|
||||
import annotated_types
|
||||
|
||||
from audio_dsp.design.parse_json import Graph, make_pipeline, insert_forks, DspJson, pipeline_to_dspjson
|
||||
|
||||
from audio_dsp.models.stage import all_models
|
||||
from audio_dsp.stages import all_stages
|
||||
|
||||
from typing import get_origin, get_args, Literal
|
||||
from types import UnionType
|
||||
|
||||
|
||||
def find_autoforks(graph):
|
||||
for node in graph.nodes:
|
||||
if 'AutoFork' in node.placement.name:
|
||||
return True
|
||||
|
||||
assert False, "No AutoFork node found in the graph after insert_forks."
|
||||
|
||||
|
||||
def test_no_shared_edge():
|
||||
json_str = """
|
||||
{
|
||||
"name": "No shared edge",
|
||||
"fs": 44100,
|
||||
"inputs": [{
|
||||
"name": "inputs",
|
||||
"channels": 1
|
||||
}],
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "VolumeControl",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["inputs", 0]],
|
||||
"name": "VolumeControl_1",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [{
|
||||
"name": "outputs",
|
||||
"input": [["VolumeControl_1", 0]]
|
||||
}]
|
||||
}
|
||||
"""
|
||||
graph = Graph.model_validate_json(json_str)
|
||||
dsp_json = DspJson(ir_version=1, producer_name="test", producer_version="1.0", graph=graph)
|
||||
a = make_pipeline(dsp_json)
|
||||
|
||||
a.draw(Path("test_no_shared_edge"))
|
||||
new_graph = insert_forks(graph)
|
||||
print(f"Before insert_forks: {graph.model_dump_json()}")
|
||||
print(f"After insert_forks: {new_graph.model_dump_json()}")
|
||||
|
||||
dsp_json = DspJson(ir_version=1, producer_name="pipeline_to_dspjson", producer_version="1.0", graph=new_graph)
|
||||
new_json = pipeline_to_dspjson(a)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
for node in graph.nodes:
|
||||
if 'AutoFork' in node.placement.name:
|
||||
assert False, "AutoFork node found in the graph after insert_forks, but not needed."
|
||||
|
||||
|
||||
|
||||
|
||||
def test_shared_edge_from_graph_input():
|
||||
json_str = """
|
||||
{
|
||||
"name": "Shared edge from graph input",
|
||||
"fs": 44100,
|
||||
"inputs": [{
|
||||
"name": "inputs",
|
||||
"channels": 1
|
||||
}],
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "VolumeControl",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["inputs", 0]],
|
||||
"name": "VolumeControl_A",
|
||||
"thread": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"op_type": "Mixer",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["inputs", 0]],
|
||||
"name": "Mixer_B",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [{
|
||||
"name": "outputs",
|
||||
"input": [["VolumeControl_A", 0]]
|
||||
}]
|
||||
}
|
||||
"""
|
||||
graph = Graph.model_validate_json(json_str)
|
||||
dsp_json = DspJson(ir_version=1, producer_name="test", producer_version="1.0", graph=graph)
|
||||
a = make_pipeline(dsp_json)
|
||||
|
||||
a.draw(Path("test_shared_edge_from_graph_input"))
|
||||
new_graph = insert_forks(graph)
|
||||
print(f"Before insert_forks: {graph.model_dump_json()}")
|
||||
print(f"After insert_forks: {new_graph.model_dump_json()}")
|
||||
find_autoforks(new_graph)
|
||||
|
||||
|
||||
def test_shared_edge_from_producer_node():
|
||||
json_str = """
|
||||
{
|
||||
"name": "Shared edge from producer node",
|
||||
"fs": 44100,
|
||||
"inputs": [{
|
||||
"name": "inputs",
|
||||
"channels": 1
|
||||
}],
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "VolumeControl",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["inputs", 0]],
|
||||
"name": "Producer",
|
||||
"thread": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"op_type": "Mixer",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["Producer", 0]],
|
||||
"name": "Consumer_1",
|
||||
"thread": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"op_type": "Mixer",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["Producer", 0]],
|
||||
"name": "Consumer_2",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [{
|
||||
"name": "outputs",
|
||||
"input": [["Consumer_1", 0]]
|
||||
}]
|
||||
}
|
||||
"""
|
||||
graph = Graph.model_validate_json(json_str)
|
||||
dsp_json = DspJson(ir_version=1, producer_name="test", producer_version="1.0", graph=graph)
|
||||
a = make_pipeline(dsp_json)
|
||||
|
||||
a.draw(Path("test_shared_edge_from_producer_node"))
|
||||
new_graph = insert_forks(graph)
|
||||
print(f"Before insert_forks: {graph.model_dump_json()}")
|
||||
print(f"After insert_forks: {new_graph.model_dump_json()}")
|
||||
find_autoforks(new_graph)
|
||||
|
||||
|
||||
def test_shared_edge_with_graph_output():
|
||||
json_str = """
|
||||
{
|
||||
"name": "Shared edge with graph output",
|
||||
"fs": 44100,
|
||||
"inputs": [{
|
||||
"name": "inputs",
|
||||
"channels": 1
|
||||
}],
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "VolumeControl",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["inputs", 0]],
|
||||
"name": "Producer",
|
||||
"thread": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"op_type": "Mixer",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["Producer", 0]],
|
||||
"name": "Consumer",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [{
|
||||
"name": "outputs",
|
||||
"input": [["Producer", 0]]
|
||||
}]
|
||||
}
|
||||
"""
|
||||
graph = Graph.model_validate_json(json_str)
|
||||
dsp_json = DspJson(ir_version=1, producer_name="test", producer_version="1.0", graph=graph)
|
||||
a = make_pipeline(dsp_json)
|
||||
|
||||
a.draw(Path("test_shared_edge_with_graph_output"))
|
||||
new_graph = insert_forks(graph)
|
||||
print(f"Before insert_forks: {graph.model_dump_json()}")
|
||||
print(f"After insert_forks: {new_graph.model_dump_json()}")
|
||||
find_autoforks(new_graph)
|
||||
|
||||
|
||||
def test_again():
|
||||
json_str = """
|
||||
{
|
||||
"name": "Stereo Mixer with Volume",
|
||||
"fs": 48000,
|
||||
"inputs": [{
|
||||
"name": "stereo_in",
|
||||
"channels": 2
|
||||
}],
|
||||
"nodes": [
|
||||
{"op_type": "Mixer", "placement": {"input": [["stereo_in", 0], ["stereo_in", 1]], "name": "Mixer", "thread": 0}},
|
||||
{"op_type": "VolumeControl", "placement": {"input": [["Mixer", 0]], "name": "Volume", "thread": 0}}
|
||||
],
|
||||
"outputs": [{
|
||||
"name": "stereo_out",
|
||||
"input": [["Volume", 0], ["Volume", 0]]
|
||||
}]
|
||||
}
|
||||
"""
|
||||
graph = Graph.model_validate_json(json_str)
|
||||
dsp_json = DspJson(ir_version=1, producer_name="test", producer_version="1.0", graph=graph)
|
||||
a = make_pipeline(dsp_json)
|
||||
|
||||
a.draw(Path("test_again"))
|
||||
new_graph = insert_forks(graph)
|
||||
print(f"Before insert_forks: {graph.model_dump_json()}")
|
||||
print(f"After insert_forks: {new_graph.model_dump_json()}")
|
||||
find_autoforks(new_graph)
|
||||
|
||||
|
||||
def test_multiple_inputs_outputs_non_shared():
|
||||
"""
|
||||
Test a graph with two inputs (one mono and one stereo) and two outputs (one mono and one stereo)
|
||||
where no edge is shared between consumers.
|
||||
"""
|
||||
json_str = """
|
||||
{
|
||||
"name": "Multiple Inputs and Outputs Non-Shared Test",
|
||||
"fs": 48000,
|
||||
"inputs": [
|
||||
{"name": "mono_in", "channels": 1},
|
||||
{"name": "stereo_in", "channels": 2}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "VolumeControl",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["mono_in", 0]],
|
||||
"name": "VolumeControl_A",
|
||||
"thread": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"op_type": "Mixer",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["stereo_in", 0], ["stereo_in", 1]],
|
||||
"name": "Mixer_B",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{"name": "mono_out", "input": [["VolumeControl_A", 0]]},
|
||||
{"name": "stereo_out", "input": [["Mixer_B", 0], ["VolumeControl_A", 0]]}
|
||||
]
|
||||
}
|
||||
"""
|
||||
graph = Graph.model_validate_json(json_str)
|
||||
dsp_json = DspJson(ir_version=1, producer_name="test", producer_version="1.0", graph=graph)
|
||||
a = make_pipeline(dsp_json)
|
||||
|
||||
a.draw(Path("test_multiple_inputs_outputs_non_shared"))
|
||||
new_graph = insert_forks(graph)
|
||||
print("Test: Multiple Inputs and Outputs Non-Shared Test")
|
||||
print(f"Before insert_forks: {graph.model_dump_json(indent=2)}")
|
||||
print(f"After insert_forks: {new_graph.model_dump_json(indent=2)}")
|
||||
find_autoforks(new_graph)
|
||||
|
||||
|
||||
def test_multiple_inputs_outputs_shared():
|
||||
"""
|
||||
Test a graph with two inputs and two outputs where a node produces an output
|
||||
that is shared by both graph outputs. A Fork should be inserted.
|
||||
"""
|
||||
json_str = """
|
||||
{
|
||||
"name": "Multiple Inputs and Outputs Shared Test",
|
||||
"fs": 48000,
|
||||
"inputs": [
|
||||
{"name": "input1", "channels": 1},
|
||||
{"name": "input2", "channels": 1}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "Mixer",
|
||||
"config": {},
|
||||
"placement": {
|
||||
"input": [["input1", 0], ["input2", 0]],
|
||||
"name": "Mixer",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{"name": "output1", "input": [["Mixer", 0]]},
|
||||
{"name": "output2", "input": [["Mixer", 0]]}
|
||||
]
|
||||
}
|
||||
"""
|
||||
graph = Graph.model_validate_json(json_str)
|
||||
dsp_json = DspJson(ir_version=1, producer_name="test", producer_version="1.0", graph=graph)
|
||||
a = make_pipeline(dsp_json)
|
||||
|
||||
a.draw(Path("test_multiple_inputs_outputs_shared"))
|
||||
new_graph = insert_forks(graph)
|
||||
print("Test: Multiple Inputs and Outputs Shared Test")
|
||||
print(f"Before insert_forks: {graph.model_dump_json(indent=2)}")
|
||||
print(f"After insert_forks: {new_graph.model_dump_json(indent=2)}")
|
||||
|
||||
find_autoforks(new_graph)
|
||||
|
||||
|
||||
|
||||
def test_all_stages_models():
|
||||
all_m = all_models()
|
||||
all_s = all_stages()
|
||||
|
||||
failed = False
|
||||
for s in all_s:
|
||||
if s.startswith("_") or s in ["DSPThreadStage", "PipelineStage"]:
|
||||
continue
|
||||
|
||||
try:
|
||||
assert s in all_m, f"Stage {s} not found in all models"
|
||||
assert s == all_m[s].model_fields["op_type"].default, f"Stage {s} op_type mismatch"
|
||||
except AssertionError as e:
|
||||
print(e)
|
||||
failed = True
|
||||
continue
|
||||
|
||||
if "biquad" in s.lower() or "parametric" in s.lower():
|
||||
continue
|
||||
|
||||
if "parameters" not in all_m[s].model_fields:
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
if type(all_s[s].set_parameters.__annotations__["parameters"]) is UnionType:
|
||||
set_params_input = get_args(all_s[s].set_parameters.__annotations__["parameters"])
|
||||
model_params_type = all_m[s].model_fields["parameters"].default_factory
|
||||
assert model_params_type in set_params_input, f"Stage {s} set_parameters input type mismatch"
|
||||
|
||||
else:
|
||||
set_params_input = all_s[s].set_parameters.__annotations__["parameters"].__name__
|
||||
model_params_type = all_m[s].model_fields["parameters"].default_factory.__name__
|
||||
assert set_params_input == model_params_type, f"Stage {s} set_parameters input type mismatch"
|
||||
|
||||
except AssertionError as e:
|
||||
print(e)
|
||||
failed = True
|
||||
|
||||
for field, value in type(all_m[s].model_fields["parameters"].default_factory()).model_fields.items():
|
||||
try:
|
||||
if get_origin(value.annotation) is list:
|
||||
item_type = get_args(value.annotation)[0]
|
||||
meta = get_args(item_type)[1].metadata
|
||||
elif get_origin(value.annotation) is Literal:
|
||||
continue # Skip Literal types
|
||||
else:
|
||||
meta = value.metadata
|
||||
min = [g.ge for g in meta if isinstance(g, annotated_types.Ge)] or [
|
||||
g.gt for g in meta if isinstance(g, annotated_types.Gt)]
|
||||
max = [g.le for g in meta if isinstance(g, annotated_types.Le)] or [
|
||||
g.lt for g in meta if isinstance(g, annotated_types.Lt)]
|
||||
assert min, f"Minimum not defined for field {field} in {s}"
|
||||
if field not in ["position", "delay"]:
|
||||
assert max, f"Maximum not defined for field {field} in {s}"
|
||||
assert min[0] < max[0], f"Range not correct for field {field} in {s}"
|
||||
except AssertionError as e:
|
||||
print(e)
|
||||
failed = True
|
||||
continue
|
||||
|
||||
assert not failed, "Some stages failed the test."
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_shared_edge_from_graph_input()
|
||||
189
lib_audio_dsp/test/json/test_limiter_pipeline.py
Normal file
189
lib_audio_dsp/test/json/test_limiter_pipeline.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test limiter pipeline creation."""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
|
||||
|
||||
def test_rms_limiter_pipeline():
|
||||
"""Test creating an RMS limiter pipeline."""
|
||||
print("Creating RMS limiter pipeline...")
|
||||
|
||||
# Create an RMS limiter pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_limiter",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "RMS Limiter",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "LimiterRMS",
|
||||
"parameters": {
|
||||
"threshold_db": -6.0,
|
||||
"attack_t": 0.01,
|
||||
"release_t": 0.1
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoRMSLimiter",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoRMSLimiter", 0], ["StereoRMSLimiter", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our limiter stage
|
||||
limiter_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "limiter_rms":
|
||||
limiter_stage = stage
|
||||
break
|
||||
|
||||
assert limiter_stage is not None, "Could not find RMS Limiter stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
|
||||
def test_peak_limiter_pipeline():
|
||||
"""Test creating a peak limiter pipeline."""
|
||||
print("Creating peak limiter pipeline...")
|
||||
|
||||
# Create a peak limiter pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_limiter",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Peak Limiter",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "LimiterPeak",
|
||||
"parameters": {
|
||||
"threshold_db": -3.0,
|
||||
"attack_t": 0.005,
|
||||
"release_t": 0.05
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoPeakLimiter",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoPeakLimiter", 0], ["StereoPeakLimiter", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our limiter stage
|
||||
limiter_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "limiter_peak":
|
||||
limiter_stage = stage
|
||||
break
|
||||
|
||||
assert limiter_stage is not None, "Could not find Peak Limiter stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
def test_hard_peak_limiter_pipeline():
|
||||
"""Test creating a hard peak limiter pipeline."""
|
||||
print("Creating hard peak limiter pipeline...")
|
||||
|
||||
# Create a hard peak limiter pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_limiter",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Hard Peak Limiter",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "HardLimiterPeak",
|
||||
"parameters": {
|
||||
"threshold_db": -1.0,
|
||||
"attack_t": 0.001,
|
||||
"release_t": 0.02
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoHardPeakLimiter",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoHardPeakLimiter", 0], ["StereoHardPeakLimiter", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our limiter stage
|
||||
limiter_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "hard_limiter_peak":
|
||||
limiter_stage = stage
|
||||
break
|
||||
|
||||
assert limiter_stage is not None, "Could not find Hard Peak Limiter stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_rms_limiter_pipeline()
|
||||
print("\n" + "="*50 + "\n")
|
||||
test_peak_limiter_pipeline()
|
||||
print("\n" + "="*50 + "\n")
|
||||
test_hard_peak_limiter_pipeline()
|
||||
67
lib_audio_dsp/test/json/test_noise_gate_pipeline.py
Normal file
67
lib_audio_dsp/test/json/test_noise_gate_pipeline.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test noise gate pipeline creation."""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
|
||||
|
||||
def test_noise_gate_pipeline():
|
||||
"""Test creating a noise gate pipeline."""
|
||||
print("Creating noise gate pipeline...")
|
||||
|
||||
# Create a noise gate pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_noise_gate",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Noise Gate",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "NoiseGate",
|
||||
"parameters": {
|
||||
"threshold_db": -40.0,
|
||||
"attack_t": 0.01,
|
||||
"release_t": 0.1
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoNoiseGate",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoNoiseGate", 0], ["StereoNoiseGate", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our noise gate stage
|
||||
gate_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "noise_gate": # This matches the config file name
|
||||
gate_stage = stage
|
||||
break
|
||||
|
||||
assert gate_stage is not None, "Could not find Noise Gate stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_noise_gate_pipeline()
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test noise suppressor expander pipeline creation."""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
|
||||
|
||||
def test_noise_suppressor_expander_pipeline():
|
||||
"""Test creating a noise suppressor expander pipeline."""
|
||||
print("Creating noise suppressor expander pipeline...")
|
||||
|
||||
# Create a noise suppressor expander pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_noise_suppressor",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Noise Suppressor Expander",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "NoiseSuppressorExpander",
|
||||
"parameters": {
|
||||
"ratio": 3.0,
|
||||
"threshold_db": -35.0,
|
||||
"attack_t": 0.005,
|
||||
"release_t": 0.120
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoNoiseSuppressor",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoNoiseSuppressor", 0], ["StereoNoiseSuppressor", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our noise suppressor stage
|
||||
suppressor_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "noise_suppressor_expander":
|
||||
suppressor_stage = stage
|
||||
break
|
||||
|
||||
assert suppressor_stage is not None, "Could not find Noise Suppressor Expander stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_noise_suppressor_expander_pipeline()
|
||||
75
lib_audio_dsp/test/json/test_reverb_pipeline.py
Normal file
75
lib_audio_dsp/test/json/test_reverb_pipeline.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# Copyright 2025 XMOS LIMITED.
|
||||
# This Software is subject to the terms of the XMOS Public Licence: Version 1.
|
||||
"""Test reverb pipeline creation."""
|
||||
|
||||
from audio_dsp.design.parse_json import DspJson, make_pipeline, pipeline_to_dspjson
|
||||
|
||||
|
||||
def test_plate_reverb_pipeline():
|
||||
"""Test creating a plate reverb pipeline."""
|
||||
print("Creating plate reverb pipeline...")
|
||||
|
||||
# Create a plate reverb pipeline JSON
|
||||
pipeline_json = {
|
||||
"ir_version": 1,
|
||||
"producer_name": "test_reverb",
|
||||
"producer_version": "0.1",
|
||||
"graph": {
|
||||
"name": "Plate Reverb",
|
||||
"fs": 48000,
|
||||
"nodes": [
|
||||
{
|
||||
"op_type": "ReverbPlateStereo",
|
||||
"config": {
|
||||
"max_predelay": 30.0
|
||||
},
|
||||
"parameters": {
|
||||
"predelay": 15.0,
|
||||
"width": 0.8,
|
||||
"damping": 0.4,
|
||||
"decay": 0.6,
|
||||
"early_diffusion": 0.7,
|
||||
"late_diffusion": 0.6,
|
||||
"bandwidth": 0.9,
|
||||
"wet_dry_mix": 0.4
|
||||
},
|
||||
"placement": {
|
||||
"input": [["inputs", 0], ["inputs", 1]],
|
||||
"name": "StereoPlateReverb",
|
||||
"thread": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"name": "inputs",
|
||||
"channels": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "outputs",
|
||||
"input": [["StereoPlateReverb", 0], ["StereoPlateReverb", 1]]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dsp_json = DspJson(**pipeline_json)
|
||||
pipeline = make_pipeline(dsp_json)
|
||||
|
||||
# Find our reverb stage
|
||||
reverb_stage = None
|
||||
for stage in pipeline.stages:
|
||||
if stage.name == "reverb_plate":
|
||||
reverb_stage = stage
|
||||
break
|
||||
|
||||
assert reverb_stage is not None, "Could not find Plate Reverb stage in pipeline"
|
||||
|
||||
new_json = pipeline_to_dspjson(pipeline)
|
||||
assert dsp_json.graph == new_json.graph, "Pipeline JSON does not match original"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_plate_reverb_pipeline()
|
||||
Reference in New Issue
Block a user