mirror of
https://github.com/onyx-and-iris/voicemeeter-api-python.git
synced 2026-04-09 08:43:29 +00:00
Compare commits
8 Commits
868017c79f
...
add-to-bus
| Author | SHA1 | Date | |
|---|---|---|---|
| 714d2fc972 | |||
| c797912458 | |||
|
|
f702b4feb3 | ||
|
|
f8f10e358f | ||
| f7abc5248b | |||
| fec4315be2 | |||
| a3e3db3c37 | |||
| 3e201443e0 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -129,7 +129,8 @@ dmypy.json
|
||||
.pyre/
|
||||
|
||||
# test reports
|
||||
tests/reports/junit-*.xml
|
||||
tests/reports/
|
||||
!tests/reports/badge-*.svg
|
||||
|
||||
# test/config
|
||||
test-*.py
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
[](https://github.com/onyx-and-iris/voicemeeter-api-python/blob/dev/LICENSE)
|
||||
[](https://python-poetry.org/)
|
||||
[](https://github.com/astral-sh/ruff)
|
||||
[](./tests/reports/basic.html)
|
||||
[](./tests/reports/banana.html)
|
||||
[](./tests/reports/potato.html)
|
||||

|
||||

|
||||

|
||||
|
||||
# Python Wrapper for Voicemeeter API
|
||||
|
||||
|
||||
9
examples/eq_edit/README.md
Normal file
9
examples/eq_edit/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## About
|
||||
|
||||
The purpose of this script is to demonstratehow to utilize the channels and cells that are available as part of the EQ. It should take audio playing in the second virtual strip and then apply a LGF on the first physical at 500 Hz.
|
||||
|
||||
## Use
|
||||
|
||||
Configured for banana version.
|
||||
|
||||
Make sure you are playing audio into the second virtual strip and out of the first physical bus, both channels are unmuted and that you aren't monitoring another mixbus. Then run the script.
|
||||
21
examples/eq_edit/__main__.py
Normal file
21
examples/eq_edit/__main__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import time
|
||||
|
||||
import voicemeeterlib
|
||||
|
||||
def main():
|
||||
KIND_ID = 'banana'
|
||||
|
||||
with voicemeeterlib.api(KIND_ID) as vm:
|
||||
vm.bus[0].eq.on = True
|
||||
vm.bus[0].eq.channel[0].cell[0].on = True
|
||||
vm.bus[0].eq.channel[0].cell[0].f = 500
|
||||
vm.bus[0].eq.channel[0].cell[0].type = 3 # Should correspond to LPF
|
||||
|
||||
time.sleep(3)
|
||||
vm.bus[0].eq.on = False
|
||||
vm.bus[0].eq.channel[0].cell[0].on = False
|
||||
vm.bus[0].eq.channel[0].cell[0].f = 50
|
||||
vm.bus[0].eq.channel[0].cell[0].type = 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
8
examples/events/README.md
Normal file
8
examples/events/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Events
|
||||
|
||||
If you want to receive updates on certain events there are two routes you can take:
|
||||
|
||||
- Register a class that implements an `on_update(self, event) -> None` method on the `{Remote}.subject` class.
|
||||
- Register callback functions/methods on the `{Remote}.subject` class, one for each type of update.
|
||||
|
||||
Included are examples of both approaches.
|
||||
@@ -58,5 +58,5 @@ def test_all():
|
||||
def generate_badges():
|
||||
for kind in ['basic', 'banana', 'potato']:
|
||||
subprocess.run(
|
||||
['tox', 'r', '-e', 'genbadges'], env=os.environ.copy() | {'KIND': kind}
|
||||
['tox', 'r', '-e', 'genbadge'], env=os.environ.copy() | {'KIND': kind}
|
||||
)
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
body {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
/* do not increase min-width as some may use split screens */
|
||||
min-width: 800px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
p {
|
||||
color: black;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/******************************
|
||||
* SUMMARY INFORMATION
|
||||
******************************/
|
||||
#environment td {
|
||||
padding: 5px;
|
||||
border: 1px solid #e6e6e6;
|
||||
vertical-align: top;
|
||||
}
|
||||
#environment tr:nth-child(odd) {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
#environment ul {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/******************************
|
||||
* TEST RESULT COLORS
|
||||
******************************/
|
||||
span.passed,
|
||||
.passed .col-result {
|
||||
color: green;
|
||||
}
|
||||
|
||||
span.skipped,
|
||||
span.xfailed,
|
||||
span.rerun,
|
||||
.skipped .col-result,
|
||||
.xfailed .col-result,
|
||||
.rerun .col-result {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
span.error,
|
||||
span.failed,
|
||||
span.xpassed,
|
||||
.error .col-result,
|
||||
.failed .col-result,
|
||||
.xpassed .col-result {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.col-links__extra {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
/******************************
|
||||
* RESULTS TABLE
|
||||
*
|
||||
* 1. Table Layout
|
||||
* 2. Extra
|
||||
* 3. Sorting items
|
||||
*
|
||||
******************************/
|
||||
/*------------------
|
||||
* 1. Table Layout
|
||||
*------------------*/
|
||||
#results-table {
|
||||
border: 1px solid #e6e6e6;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
#results-table th,
|
||||
#results-table td {
|
||||
padding: 5px;
|
||||
border: 1px solid #e6e6e6;
|
||||
text-align: left;
|
||||
}
|
||||
#results-table th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/*------------------
|
||||
* 2. Extra
|
||||
*------------------*/
|
||||
.logwrapper {
|
||||
max-height: 230px;
|
||||
overflow-y: scroll;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
.logwrapper.expanded {
|
||||
max-height: none;
|
||||
}
|
||||
.logwrapper.expanded .logexpander:after {
|
||||
content: "collapse [-]";
|
||||
}
|
||||
.logwrapper .logexpander {
|
||||
z-index: 1;
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
width: max-content;
|
||||
border: 1px solid;
|
||||
border-radius: 3px;
|
||||
padding: 5px 7px;
|
||||
margin: 10px 0 10px calc(100% - 80px);
|
||||
cursor: pointer;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
.logwrapper .logexpander:after {
|
||||
content: "expand [+]";
|
||||
}
|
||||
.logwrapper .logexpander:hover {
|
||||
color: #000;
|
||||
border-color: #000;
|
||||
}
|
||||
.logwrapper .log {
|
||||
min-height: 40px;
|
||||
position: relative;
|
||||
top: -50px;
|
||||
height: calc(100% + 50px);
|
||||
border: 1px solid #e6e6e6;
|
||||
color: black;
|
||||
display: block;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
padding: 5px;
|
||||
padding-right: 80px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
div.media {
|
||||
border: 1px solid #e6e6e6;
|
||||
float: right;
|
||||
height: 240px;
|
||||
margin: 0 5px;
|
||||
overflow: hidden;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.media-container {
|
||||
display: grid;
|
||||
grid-template-columns: 25px auto 25px;
|
||||
align-items: center;
|
||||
flex: 1 1;
|
||||
overflow: hidden;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.media-container--fullscreen {
|
||||
grid-template-columns: 0px auto 0px;
|
||||
}
|
||||
|
||||
.media-container__nav--right,
|
||||
.media-container__nav--left {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.media-container__viewport {
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
height: inherit;
|
||||
}
|
||||
.media-container__viewport img,
|
||||
.media-container__viewport video {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.media__name,
|
||||
.media__counter {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
flex: 0 0 25px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.collapsible td:not(.col-links) {
|
||||
cursor: pointer;
|
||||
}
|
||||
.collapsible td:not(.col-links):hover::after {
|
||||
color: #bbb;
|
||||
font-style: italic;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.col-result {
|
||||
width: 130px;
|
||||
}
|
||||
.col-result:hover::after {
|
||||
content: " (hide details)";
|
||||
}
|
||||
|
||||
.col-result.collapsed:hover::after {
|
||||
content: " (show details)";
|
||||
}
|
||||
|
||||
#environment-header h2:hover::after {
|
||||
content: " (hide details)";
|
||||
color: #bbb;
|
||||
font-style: italic;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#environment-header.collapsed h2:hover::after {
|
||||
content: " (show details)";
|
||||
color: #bbb;
|
||||
font-style: italic;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/*------------------
|
||||
* 3. Sorting items
|
||||
*------------------*/
|
||||
.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.sortable.desc:after {
|
||||
content: " ";
|
||||
position: relative;
|
||||
left: 5px;
|
||||
bottom: -12.5px;
|
||||
border: 10px solid #4caf50;
|
||||
border-bottom: 0;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
.sortable.asc:after {
|
||||
content: " ";
|
||||
position: relative;
|
||||
left: 5px;
|
||||
bottom: 12.5px;
|
||||
border: 10px solid #4caf50;
|
||||
border-top: 0;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
}
|
||||
|
||||
.hidden, .summary__reload__button.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.summary__data {
|
||||
flex: 0 0 550px;
|
||||
}
|
||||
.summary__reload {
|
||||
flex: 1 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.summary__reload__button {
|
||||
flex: 0 0 300px;
|
||||
display: flex;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
background-color: #4caf50;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.summary__reload__button:hover {
|
||||
background-color: #46a049;
|
||||
}
|
||||
.summary__spacer {
|
||||
flex: 0 0 550px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.filters,
|
||||
.collapse {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.filters button,
|
||||
.collapse button {
|
||||
color: #999;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.filters button:hover,
|
||||
.collapse button:hover {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.filter__label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
tox.ini
2
tox.ini
@@ -10,7 +10,7 @@ commands_pre =
|
||||
commands =
|
||||
poetry run pytest tests
|
||||
|
||||
[testenv:genbadges]
|
||||
[testenv:genbadge]
|
||||
passenv = *
|
||||
setenv = VIRTUALENV_DISCOVERY=pyenv
|
||||
allowlist_externals = poetry
|
||||
|
||||
@@ -88,6 +88,24 @@ class Bus(IRemote):
|
||||
|
||||
|
||||
class BusEQ(IRemote):
|
||||
@classmethod
|
||||
def make(cls, remote, i):
|
||||
"""
|
||||
Factory method for BusEQ.
|
||||
|
||||
Returns a BusEQ class.
|
||||
"""
|
||||
kls = (cls,)
|
||||
return type(
|
||||
'BusEQ',
|
||||
kls,
|
||||
{
|
||||
'channel': tuple(
|
||||
BusEQCh.make(remote, i, j) for j in range(remote.kind.channels)
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
return f'Bus[{self.index}].eq'
|
||||
@@ -109,6 +127,85 @@ class BusEQ(IRemote):
|
||||
self.setter('ab', 1 if val else 0)
|
||||
|
||||
|
||||
class BusEQCh(IRemote):
|
||||
@classmethod
|
||||
def make(cls, remote, i, j):
|
||||
"""
|
||||
Factory method for Bus EQ channel.
|
||||
|
||||
Returns a BusEQCh class.
|
||||
"""
|
||||
kls = (cls,)
|
||||
return type(
|
||||
'BusEQCh',
|
||||
kls,
|
||||
{
|
||||
'cell': tuple(
|
||||
BusEQChCell(remote, i, j, k) for k in range(remote.kind.cells)
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
def __init__(self, remote, i, j):
|
||||
super().__init__(remote, i)
|
||||
self.channel_index = j
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
return f'Bus[{self.index}].eq.channel[{self.channel_index}]'
|
||||
|
||||
|
||||
class BusEQChCell(IRemote):
|
||||
def __init__(self, remote, i, j, k):
|
||||
super().__init__(remote, i)
|
||||
self.channel_index = j
|
||||
self.cell_index = k
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
return f'Bus[{self.index}].eq.channel[{self.channel_index}].cell[{self.cell_index}]'
|
||||
|
||||
@property
|
||||
def on(self) -> bool:
|
||||
return self.getter('on') == 1
|
||||
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
self.setter('on', 1 if val else 0)
|
||||
|
||||
@property
|
||||
def type(self) -> int:
|
||||
return int(self.getter('type'))
|
||||
|
||||
@type.setter
|
||||
def type(self, val: int):
|
||||
self.setter('type', val)
|
||||
|
||||
@property
|
||||
def f(self) -> float:
|
||||
return round(self.getter('f'), 1)
|
||||
|
||||
@f.setter
|
||||
def f(self, val: float):
|
||||
self.setter('f', val)
|
||||
|
||||
@property
|
||||
def gain(self) -> float:
|
||||
return round(self.getter('gain'), 1)
|
||||
|
||||
@gain.setter
|
||||
def gain(self, val: float):
|
||||
self.setter('gain', val)
|
||||
|
||||
@property
|
||||
def q(self) -> float:
|
||||
return round(self.getter('q'), 1)
|
||||
|
||||
@q.setter
|
||||
def q(self, val: float):
|
||||
self.setter('q', val)
|
||||
|
||||
|
||||
class PhysicalBus(Bus):
|
||||
@classmethod
|
||||
def make(cls, remote, i, kind):
|
||||
@@ -321,7 +418,7 @@ def bus_factory(is_phys_bus, remote, i) -> Union[PhysicalBus, VirtualBus]:
|
||||
{
|
||||
'levels': BusLevel(remote, i),
|
||||
'mode': BUSMODEMIXIN_cls(remote, i),
|
||||
'eq': BusEQ(remote, i),
|
||||
'eq': BusEQ.make(remote, i),
|
||||
},
|
||||
)(remote, i)
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ class KindMapClass(metaclass=SingletonType):
|
||||
asio: tuple
|
||||
insert: int
|
||||
composite: int
|
||||
channels: int
|
||||
cells: int
|
||||
|
||||
@property
|
||||
def phys_in(self) -> int:
|
||||
@@ -76,6 +78,8 @@ class BasicMap(KindMapClass):
|
||||
asio: tuple = (0, 0)
|
||||
insert: int = 0
|
||||
composite: int = 0
|
||||
channels: int = 0
|
||||
cells: int = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -86,6 +90,8 @@ class BananaMap(KindMapClass):
|
||||
asio: tuple = (6, 8)
|
||||
insert: int = 22
|
||||
composite: int = 8
|
||||
channels: int = 9
|
||||
cells: int = 6
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -96,6 +102,8 @@ class PotatoMap(KindMapClass):
|
||||
asio: tuple = (10, 8)
|
||||
insert: int = 34
|
||||
composite: int = 8
|
||||
channels: int = 9
|
||||
cells: int = 6
|
||||
|
||||
|
||||
def kind_factory(kind_id):
|
||||
|
||||
Reference in New Issue
Block a user