Repository: metrasynth/radiant-voices (language: python)

Issue:
Cannot load project with delay amount > 256
Reported from Telegram user:

> https://github.com/metrasynth/radiant-voices/blob/master/src/python/rv/modules/base/delay.py
> lines 32 & 33
>
> since we're able to specify delay in ms/hz (up to 8192),
> line 3 will generate an exception due to delay > 256 units

[issue-41-files.zip](https://github.com/metrasynth/radiant-voices/files/6062944/issue-41-files.zip)

Cannot load project with delay amount > 256
Reported from Telegram user:

> https://github.com/metrasynth/radiant-voices/blob/master/src/python/rv/modules/base/delay.py
> lines 32 & 33
>
> since we're able to specify delay in ms/hz (up to 8192),
> line 3 will generate an exception due to delay > 256 units

[issue-41-files.zip](https://github.com/metrasynth/radiant-voices/files/6062944/issue-41-files.zip)

1.0.1 dev  issue 41



Discussion / hints:

Fix patch:
diff --git a/.npmignore b/.npmignore
index 95d0eb1..44447b3 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,26 +1,33 @@
-tsconfig.json
-src
-docs
-examples
-requirements
-rv
-src/python/genrv
-src/python
-tests
+*.egg-info
+*.ipynb
+*.rst
 .editorconfig
 .flake8
+.github
+.idea
 .isort.cfg
 .pre-commit-config.yaml
 .prettierignore
 .prettierrc.yaml
+.pytest_cache
 .readthedocs.yml
 .travis.yml
 MANIFEST.in
 Pipfile
 Pipfile.lock
+docs
+examples
+genrv-config.yaml
+jest.config.js
 pytest.ini
+requirements
 requirements.txt
 setup.cfg
 setup.py
+specs
+src
+src/python
+src/python/genrv
+tests
 tox.ini
-*.ipynb
+tsconfig.json
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index dd2b3a7..f74fcc5 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,17 @@ Changelog
 =========
 
 
+1.0.1
+-----
+
+Bug fix release.
+
+Fixes (all versions)
+....................
+
+- Adds correct range information for delay controllers in Delay and Echo modules.
+
+
 1.0.0
 -----
 
diff --git a/Makefile b/Makefile
index ff53991..d460860 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,32 @@
+.PHONY: help
+help:
+	@echo "(TODO)"
+
 .PHONY: clean
 clean:
 	rm -rf node_modules
 	rm -rf examples/rv-react-ts-example/node_modules
 	rm -rf dist
+	rm -rf tmp
+
+.PHONY: devpi-upload
+devpi-upload:
+	devpi upload
+
+.PHONY: testenv
+testenv:
+	mkdir -p tmp
+	python -m venv tmp/testenv
+	tmp/testenv/bin/pip install ipython
+
+.PHONY: devpi-install
+devpi-install: testenv devpi-upload
+	source tmp/testenv/bin/activate && devpi install radiant-voices
+
+.PHONY: testenv-shell
+testenv-shell: devpi-upload testenv devpi-install
+	tmp/testenv/bin/ipython
+
+.PHONY: twine-upload
+twine-upload:
+	twine upload dist/radiant-voices-*.tar.gz
diff --git a/package.json b/package.json
index 6436051..0fb4c04 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "radiant-voices",
-  "version": "1.0.0",
+  "version": "1.0.1",
   "description": "Create, read, modify, and write SunVox files.",
   "repository": "https://github.com/metrasynth/radiant-voices/",
   "license": "MIT",
diff --git a/setup.py b/setup.py
index bc1d25e..6fd3068 100644
--- a/setup.py
+++ b/setup.py
@@ -45,7 +45,7 @@ def read(*names, **kwargs):
 
 setup(
     name="radiant-voices",
-    version="1.0.0",  # [TODO] generate this using a tool
+    version="1.0.1",  # [TODO] generate this using a tool
     url="https://github.com/metrasynth/radiant-voices",
     license="MIT",
     author="Matthew Scott",
diff --git a/specs/fileformat.yaml b/specs/fileformat.yaml
index 5879e0e..551eb7d 100644
--- a/specs/fileformat.yaml
+++ b/specs/fileformat.yaml
@@ -1012,8 +1012,28 @@ module_types:
     controllers:
       - dry: { min: 0, max: 512, default: 256 }
       - wet: { min: 0, max: 512, default: 256 }
-      - delay_l: { min: 0, max: 256, default: 128 }
-      - delay_r: { min: 0, max: 256, default: 160 }
+      - delay_l:
+          depends_on: delay_unit
+          default: 128
+          ranges:
+            sec/16384: { min: 0, max: 256 }
+            ms: { min: 0, max: 4000 }
+            hz: { min: 0, max: 8192 }
+            tick: { min: 0, max: 256 }
+            line: { min: 0, max: 256 }
+            line/2: { min: 0, max: 256 }
+            line/3: { min: 0, max: 256 }
+      - delay_r:
+          depends_on: delay_unit
+          default: 160
+          ranges:
+            sec/16384: { min: 0, max: 256 }
+            ms: { min: 0, max: 4000 }
+            hz: { min: 0, max: 8192 }
+            tick: { min: 0, max: 256 }
+            line: { min: 0, max: 256 }
+            line/2: { min: 0, max: 256 }
+            line/3: { min: 0, max: 256 }
       - volume_l: { min: 0, max: 256, default: 256 }
       - volume_r: { min: 0, max: 256, default: 256 }
       - channels: { enum: Channels, default: "stereo" }
@@ -1079,7 +1099,17 @@ module_types:
       - dry: { min: 0, max: 256, default: 256 }
       - wet: { min: 0, max: 256, default: 128 }
       - feedback: { min: 0, max: 256, default: 128 }
-      - delay: { min: 0, max: 256, default: 256 }
+      - delay:
+          depends_on: delay_unit
+          default: 256
+          ranges:
+            sec/256: { min: 0, max: 256 }
+            ms: { min: 0, max: 4000 }
+            hz: { min: 0, max: 8192 }
+            tick: { min: 0, max: 256 }
+            line: { min: 0, max: 256 }
+            line/2: { min: 0, max: 256 }
+            line/3: { min: 0, max: 256 }
       - channels: { enum: Channels, default: "stereo" }
       - delay_unit: { enum: DelayUnit, default: "sec/256" }
 
diff --git a/src/python/genrv/__init__.py b/src/python/genrv/__init__.py
index b14b21d..9d9699d 100644
--- a/src/python/genrv/__init__.py
+++ b/src/python/genrv/__init__.py
@@ -1,3 +1,3 @@
 """Radiant Voices Generator"""
 
-__version__ = "1.0.0"
+__version__ = "1.0.1"
diff --git a/src/python/rv/__init__.py b/src/python/rv/__init__.py
index fd75463..7441f07 100644
--- a/src/python/rv/__init__.py
+++ b/src/python/rv/__init__.py
@@ -1,5 +1,5 @@
 """Radiant Voices"""
 
-__version__ = "1.0.0"
+__version__ = "1.0.1"
 
 ENCODING = "utf8"
diff --git a/src/python/rv/modules/base/delay.py b/src/python/rv/modules/base/delay.py
index 8a32037..694cfb3 100644
--- a/src/python/rv/modules/base/delay.py
+++ b/src/python/rv/modules/base/delay.py
@@ -5,7 +5,7 @@
 """
 from enum import IntEnum
 
-from rv.controller import Controller
+from rv.controller import Controller, DependentRange, WarnOnlyRange
 
 
 class BaseDelay:
@@ -29,8 +29,38 @@ class DelayUnit(IntEnum):
 
     dry = Controller((0, 512), 256)
     wet = Controller((0, 512), 256)
-    delay_l = Controller((0, 256), 128)
-    delay_r = Controller((0, 256), 160)
+    delay_l = Controller(
+        DependentRange(
+            "delay_unit",
+            {
+                DelayUnit.sec_16384: WarnOnlyRange(0, 256),
+                DelayUnit.ms: WarnOnlyRange(0, 4000),
+                DelayUnit.hz: WarnOnlyRange(0, 8192),
+                DelayUnit.tick: WarnOnlyRange(0, 256),
+                DelayUnit.line: WarnOnlyRange(0, 256),
+                DelayUnit.line_2: WarnOnlyRange(0, 256),
+                DelayUnit.line_3: WarnOnlyRange(0, 256),
+            },
+            WarnOnlyRange(0, 256),
+        ),
+        128,
+    )
+    delay_r = Controller(
+        DependentRange(
+            "delay_unit",
+            {
+                DelayUnit.sec_16384: WarnOnlyRange(0, 256),
+                DelayUnit.ms: WarnOnlyRange(0, 4000),
+                DelayUnit.hz: WarnOnlyRange(0, 8192),
+                DelayUnit.tick: WarnOnlyRange(0, 256),
+                DelayUnit.line: WarnOnlyRange(0, 256),
+                DelayUnit.line_2: WarnOnlyRange(0, 256),
+                DelayUnit.line_3: WarnOnlyRange(0, 256),
+            },
+            WarnOnlyRange(0, 256),
+        ),
+        160,
+    )
     volume_l = Controller((0, 256), 256)
     volume_r = Controller((0, 256), 256)
     channels = Controller(Channels, Channels.stereo)
diff --git a/src/python/rv/modules/base/echo.py b/src/python/rv/modules/base/echo.py
index d6448e6..d435e8f 100644
--- a/src/python/rv/modules/base/echo.py
+++ b/src/python/rv/modules/base/echo.py
@@ -5,7 +5,7 @@
 """
 from enum import IntEnum
 
-from rv.controller import Controller
+from rv.controller import Controller, DependentRange, WarnOnlyRange
 
 
 class BaseEcho:
@@ -30,6 +30,21 @@ class DelayUnit(IntEnum):
     dry = Controller((0, 256), 256)
     wet = Controller((0, 256), 128)
     feedback = Controller((0, 256), 128)
-    delay = Controller((0, 256), 256)
+    delay = Controller(
+        DependentRange(
+            "delay_unit",
+            {
+                DelayUnit.sec_256: WarnOnlyRange(0, 256),
+                DelayUnit.ms: WarnOnlyRange(0, 4000),
+                DelayUnit.hz: WarnOnlyRange(0, 8192),
+                DelayUnit.tick: WarnOnlyRange(0, 256),
+                DelayUnit.line: WarnOnlyRange(0, 256),
+                DelayUnit.line_2: WarnOnlyRange(0, 256),
+                DelayUnit.line_3: WarnOnlyRange(0, 256),
+            },
+            WarnOnlyRange(0, 256),
+        ),
+        256,
+    )
     channels = Controller(Channels, Channels.stereo)
     delay_unit = Controller(DelayUnit, DelayUnit.sec_256)
diff --git a/src/ts/modtypes/delayBaseControllers.ts b/src/ts/modtypes/delayBaseControllers.ts
index bc12b78..19e6dc5 100644
--- a/src/ts/modtypes/delayBaseControllers.ts
+++ b/src/ts/modtypes/delayBaseControllers.ts
@@ -43,7 +43,29 @@ export class DelayBaseControllers implements Controllers {
   // noinspection JSUnusedGlobalSymbols
   set delayL(newValue: number) {
     const { controllerValues } = this
-    newValue = Math.min(Math.max(newValue, 0), 256)
+    switch (this.controllerValues.delayUnit) {
+      case DelayUnit.Sec_16384:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Ms:
+        newValue = Math.min(Math.max(newValue, 0), 4000)
+        break
+      case DelayUnit.Hz:
+        newValue = Math.min(Math.max(newValue, 0), 8192)
+        break
+      case DelayUnit.Tick:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line_2:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line_3:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+    }
     controllerValues.delayL = newValue
   }
   // noinspection JSUnusedGlobalSymbols
@@ -53,7 +75,29 @@ export class DelayBaseControllers implements Controllers {
   // noinspection JSUnusedGlobalSymbols
   set delayR(newValue: number) {
     const { controllerValues } = this
-    newValue = Math.min(Math.max(newValue, 0), 256)
+    switch (this.controllerValues.delayUnit) {
+      case DelayUnit.Sec_16384:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Ms:
+        newValue = Math.min(Math.max(newValue, 0), 4000)
+        break
+      case DelayUnit.Hz:
+        newValue = Math.min(Math.max(newValue, 0), 8192)
+        break
+      case DelayUnit.Tick:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line_2:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line_3:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+    }
     controllerValues.delayR = newValue
   }
   // noinspection JSUnusedGlobalSymbols
@@ -102,5 +146,7 @@ export class DelayBaseControllers implements Controllers {
   set delayUnit(newValue: DelayUnit) {
     const { controllerValues } = this
     controllerValues.delayUnit = newValue
+    this.delayL = this.delayL
+    this.delayR = this.delayR
   }
 }
diff --git a/src/ts/modtypes/echoBaseControllers.ts b/src/ts/modtypes/echoBaseControllers.ts
index 6368864..d72bd5a 100644
--- a/src/ts/modtypes/echoBaseControllers.ts
+++ b/src/ts/modtypes/echoBaseControllers.ts
@@ -53,7 +53,29 @@ export class EchoBaseControllers implements Controllers {
   // noinspection JSUnusedGlobalSymbols
   set delay(newValue: number) {
     const { controllerValues } = this
-    newValue = Math.min(Math.max(newValue, 0), 256)
+    switch (this.controllerValues.delayUnit) {
+      case DelayUnit.Sec_256:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Ms:
+        newValue = Math.min(Math.max(newValue, 0), 4000)
+        break
+      case DelayUnit.Hz:
+        newValue = Math.min(Math.max(newValue, 0), 8192)
+        break
+      case DelayUnit.Tick:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line_2:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+      case DelayUnit.Line_3:
+        newValue = Math.min(Math.max(newValue, 0), 256)
+        break
+    }
     controllerValues.delay = newValue
   }
   // noinspection JSUnusedGlobalSymbols
@@ -73,5 +95,6 @@ export class EchoBaseControllers implements Controllers {
   set delayUnit(newValue: DelayUnit) {
     const { controllerValues } = this
     controllerValues.delayUnit = newValue
+    this.delay = this.delay
   }
 }


Test patch:
diff --git a/tests/files/issue41/sample.sunvox b/tests/files/issue41/sample.sunvox
new file mode 100644
index 0000000..3bfc73a
Binary files /dev/null and b/tests/files/issue41/sample.sunvox differ
diff --git a/tests/python/readwrite/conftest.py b/tests/python/conftest.py
similarity index 94%
rename from tests/python/readwrite/conftest.py
rename to tests/python/conftest.py
index 9cc4c05..da25823 100644
--- a/tests/python/readwrite/conftest.py
+++ b/tests/python/conftest.py
@@ -7,7 +7,7 @@
 
 @pytest.fixture
 def test_files_path() -> Path:
-    return Path(__file__).parent / "../../files"
+    return Path(__file__).parent / "../files"
 
 
 @pytest.fixture
diff --git a/tests/python/issues/__init__.py b/tests/python/issues/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/python/issues/test_issue41.py b/tests/python/issues/test_issue41.py
new file mode 100644
index 0000000..e987d41
--- /dev/null
+++ b/tests/python/issues/test_issue41.py
@@ -0,0 +1,9 @@
+from rv.api import m
+
+
+def test_issue41(read_write_read_project):
+    project = read_write_read_project("issue41/sample")
+    delay: m.Delay = project.modules[1]
+    assert delay.delay_l == 8192
+    assert delay.delay_r == 8192
+    assert delay.delay_unit == delay.DelayUnit.hz
diff --git a/tests/ts/issues/issue41.test.ts b/tests/ts/issues/issue41.test.ts
new file mode 100644
index 0000000..02be56f
--- /dev/null
+++ b/tests/ts/issues/issue41.test.ts
@@ -0,0 +1,30 @@
+import { Project } from "@radiant-voices/project"
+import { readFileSync } from "fs"
+import { fromIffBuffer } from "@radiant-voices/chunks/fromIffBuffer"
+import { readSunVoxFile } from "@radiant-voices/reader/readSunVoxFile"
+import { m } from "@radiant-voices/modtypes"
+import { objectChunks } from "@radiant-voices/writer/objectChunks"
+import { toIffBuffer } from "@radiant-voices/chunks/toIffBuffer"
+
+describe("Reading issue41/sample.sunvox file", () => {
+  const filePath = "tests/files/issue41/sample.sunvox"
+  let project: Project
+  beforeAll(() => {
+    // read, write, read
+    const f = readFileSync(filePath)
+    project = readSunVoxFile(fromIffBuffer(f)) as Project
+    const f2 = toIffBuffer(objectChunks(project))
+    project = readSunVoxFile(fromIffBuffer(f2)) as Project
+  })
+  test("has expected delay values", () => {
+    const delay = project.modules[1] as m.Delay.Module
+    const { c } = delay
+    expect(c.delayL).toEqual(8192)
+    expect(c.delayR).toEqual(8192)
+    expect(c.delayUnit).toEqual(m.Delay.DelayUnit.Hz)
+    c.delayL = 7777
+    c.delayR = 6666
+    expect(c.delayL).toEqual(7777)
+    expect(c.delayR).toEqual(6666)
+  })
+})