Skip to content

Commit a4a15d8

Browse files
committed
Add new t-code axis for external application control.
1 parent 4bb6864 commit a4a15d8

File tree

13 files changed

+95
-51
lines changed

13 files changed

+95
-51
lines changed

qt_ui/algorithm_factory.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def create_3phase_continuous(self, device: DeviceConfiguration) -> AudioGenerati
8282
api=self.get_axis_volume_api(),
8383
master=self.get_axis_volume_master(),
8484
inactivity=self.get_axis_volume_inactivity(),
85+
external=self.get_axis_volume_external(),
8586
),
8687
carrier_frequency=self.get_axis_continuous_carrier_frequency(),
8788
),
@@ -104,6 +105,7 @@ def create_4phase_continuous(self, device: DeviceConfiguration) -> AudioGenerati
104105
api=self.get_axis_volume_api(),
105106
master=self.get_axis_volume_master(),
106107
inactivity=self.get_axis_volume_inactivity(),
108+
external=self.get_axis_volume_external(),
107109
),
108110
carrier_frequency=self.get_axis_continuous_carrier_frequency(device)
109111
),
@@ -126,6 +128,7 @@ def create_5phase_continuous(self, device: DeviceConfiguration) -> AudioGenerati
126128
api=self.get_axis_volume_api(),
127129
master=self.get_axis_volume_master(),
128130
inactivity=self.get_axis_volume_inactivity(),
131+
external=self.get_axis_volume_external(),
129132
),
130133
carrier_frequency=self.get_axis_continuous_carrier_frequency(),
131134
),
@@ -152,6 +155,7 @@ def create_3phase_pulsebased(self, device: DeviceConfiguration) -> AudioGenerati
152155
api=self.get_axis_volume_api(),
153156
master=self.get_axis_volume_master(),
154157
inactivity=self.get_axis_volume_inactivity(),
158+
external=self.get_axis_volume_external(),
155159
),
156160
carrier_frequency=self.get_axis_pulse_carrier_frequency(),
157161
pulse_frequency=self.get_axis_pulse_frequency(),
@@ -185,6 +189,7 @@ def create_3phase_abtest(self, device: DeviceConfiguration) -> AudioGenerationAl
185189
api=self.get_axis_volume_api(),
186190
master=self.get_axis_volume_master(),
187191
inactivity=self.get_axis_volume_inactivity(),
192+
external=self.get_axis_volume_external(),
188193
),
189194
a_volume=self.mainwindow.tab_a_b_testing.axis_a_volume,
190195
a_train_duration=self.mainwindow.tab_a_b_testing.axis_a_train_duration,
@@ -223,6 +228,7 @@ def create_focstim_3phase_pulsebased(self, device: DeviceConfiguration) -> Audio
223228
api=self.get_axis_volume_api(),
224229
master=self.get_axis_volume_master(),
225230
inactivity=self.get_axis_volume_inactivity(),
231+
external=self.get_axis_volume_external(),
226232
),
227233
carrier_frequency=self.get_axis_pulse_carrier_frequency(),
228234
pulse_frequency=self.get_axis_pulse_frequency(),
@@ -257,6 +263,11 @@ def get_axis_volume_inactivity(self):
257263
return create_constant_axis(1.0) # inactivity does NOT work in bake mode
258264
return self.mainwindow.tab_volume.volume.inactivity
259265

266+
def get_axis_volume_external(self):
267+
if self.create_for_bake:
268+
return create_constant_axis(1.0) # external volume does NOT work in bake mode
269+
return self.mainwindow.tab_volume.volume.external
270+
260271
def get_axis_continuous_carrier_frequency(self):
261272
default = self.mainwindow.tab_carrier.axis_carrier
262273
return self.get_axis_from_script_mapping(AxisEnum.CARRIER_FREQUENCY) or default

qt_ui/device_wizard/axes.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ class AxisEnum(Enum):
77
POSITION_BETA = 2
88

99
VOLUME_API = 10
10-
VOLUME_RAMP = 11
10+
VOLUME_MASTER = 11
1111
VOLUME_INACTIVITY = 12
12+
VOLUME_EXTERNAL = 13
1213

1314
CARRIER_FREQUENCY = 20
1415

@@ -37,6 +38,7 @@ def display_name(self) -> str:
3738
AxisEnum.POSITION_BETA: 'beta',
3839
AxisEnum.CARRIER_FREQUENCY: 'carrier frequency',
3940
AxisEnum.VOLUME_API: 'volume',
41+
AxisEnum.VOLUME_EXTERNAL: 'volume (external)',
4042

4143
AxisEnum.PULSE_FREQUENCY: "pulse frequency",
4244
AxisEnum.PULSE_WIDTH: "pulse width",
@@ -64,6 +66,7 @@ def settings_key(self) -> str:
6466
AxisEnum.POSITION_BETA: 'POSITION_BETA',
6567
AxisEnum.CARRIER_FREQUENCY: 'CARRIER_FREQUENCY',
6668
AxisEnum.VOLUME_API: 'VOLUME_API',
69+
AxisEnum.VOLUME_EXTERNAL: 'VOLUME_EXTERNAL',
6770

6871
AxisEnum.PULSE_FREQUENCY: "PULSE_FREQUENCY",
6972
AxisEnum.PULSE_WIDTH: "PULSE_WIDTH",
@@ -89,6 +92,7 @@ def settings_key(self) -> str:
8992
AxisEnum.POSITION_BETA,
9093

9194
AxisEnum.VOLUME_API,
95+
AxisEnum.VOLUME_EXTERNAL,
9296
AxisEnum.CARRIER_FREQUENCY,
9397

9498
AxisEnum.PULSE_FREQUENCY,
@@ -109,6 +113,3 @@ def settings_key(self) -> str:
109113
AxisEnum.VIBRATION_2_RANDOM,
110114
]
111115

112-
text_and_data = []
113-
for k in all_axis:
114-
text_and_data.append((k.display_name(), k))

qt_ui/mainwindow.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def __init__(self, parent=None):
9090
self.alpha,
9191
self.beta,
9292
self.tab_volume.api_volume,
93+
self.tab_volume.external_volume,
9394

9495
self.tab_carrier.axis_carrier, # this gets set to the device-specific axis later
9596

qt_ui/media_settings_widget.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from net.media_source.vlc import VLC
99
from net.media_source.kodi import Kodi
1010
from qt_ui.additional_search_paths_dialog import AdditionalSearchPathsDialog
11-
from qt_ui.device_wizard.axes import AxisEnum, text_and_data
11+
from qt_ui.device_wizard.axes import AxisEnum
1212

1313
from PyQt5 import QtCore, QtWidgets
1414
from PyQt5.QtGui import QIcon
@@ -24,6 +24,7 @@
2424
from qt_ui.models.script_mapping import FunscriptTreeItem, ScriptMappingModel
2525
from qt_ui.widgets.table_view_with_combobox import ComboBoxDelegate, ButtonDelegate
2626
from qt_ui.models import funscript_kit, additional_search_paths
27+
from qt_ui.models.funscript_kit import FunscriptKitItem
2728
from qt_ui import settings
2829

2930

@@ -64,8 +65,12 @@ def __init__(self):
6465
self.treeView.setModel(self.model)
6566
self.treeView.expandAll()
6667

67-
combobox_items = text_and_data.copy()
68-
combobox_items.insert(0, ('(none)', AxisEnum.NONE))
68+
combobox_items = []
69+
combobox_items.append(('(none)', AxisEnum.NONE))
70+
for item in funscript_kit.FunscriptKitModel.load_from_settings().children:
71+
item: FunscriptKitItem
72+
if item.allow_funscript_control:
73+
combobox_items.append((item.axis.display_name(), item.axis))
6974
self.treeView.setItemDelegateForColumn(1, ComboBoxDelegate(combobox_items, self))
7075
self.treeView.setEditTriggers(
7176
# QAbstractItemView.AllEditTriggers

qt_ui/models/funscript_kit.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,28 @@
77

88

99
defaults = {
10-
AxisEnum.POSITION_ALPHA: ('alpha', 'L0', -1, 1, True),
11-
AxisEnum.POSITION_BETA: ('beta', 'L1', -1, 1, True),
12-
AxisEnum.VOLUME_API: ('volume', '', 0, 1, True),
13-
AxisEnum.CARRIER_FREQUENCY: ('frequency', '', 500, 1000, True),
14-
15-
AxisEnum.PULSE_FREQUENCY: ('pulse_frequency', '', 0, 100, True),
16-
AxisEnum.PULSE_WIDTH: ('pulse_width', '', 4, 10, True),
17-
AxisEnum.PULSE_INTERVAL_RANDOM: ('pulse_interval_random', '', 0, 1, True),
18-
AxisEnum.PULSE_RISE_TIME: ('pulse_rise_time', '', 2, 20, True),
19-
20-
AxisEnum.VIBRATION_1_FREQUENCY: ('vib1_frequency', '', 0, 100, True),
21-
AxisEnum.VIBRATION_1_STRENGTH: ('vib1_strength', '', 0, 1, True),
22-
AxisEnum.VIBRATION_1_LEFT_RIGHT_BIAS: ('vib1_left_right_bias', '', 0, 1, True),
23-
AxisEnum.VIBRATION_1_HIGH_LOW_BIAS: ('vib1_up_down_bias', '', 0, 1, True),
24-
AxisEnum.VIBRATION_1_RANDOM: ('vib1_random', '', 0, 1, True),
25-
26-
AxisEnum.VIBRATION_2_FREQUENCY: ('vib2_frequency', '', 0, 100, True),
27-
AxisEnum.VIBRATION_2_STRENGTH: ('vib2_strength', '', 0, 1, True),
28-
AxisEnum.VIBRATION_2_LEFT_RIGHT_BIAS: ('vib2_left_right_bias', '', 0, 1, True),
29-
AxisEnum.VIBRATION_2_HIGH_LOW_BIAS: ('vib2_up_down_bias', '', 0, 1, True),
30-
AxisEnum.VIBRATION_2_RANDOM: ('vib2_random', '', 0, 1, True),
10+
AxisEnum.POSITION_ALPHA: ('alpha', 'L0', -1, 1, True, True),
11+
AxisEnum.POSITION_BETA: ('beta', 'L1', -1, 1, True, True),
12+
AxisEnum.VOLUME_API: ('volume', '', 0, 1, True, True),
13+
AxisEnum.VOLUME_EXTERNAL: ('', '', 0, 1, False, False),
14+
AxisEnum.CARRIER_FREQUENCY: ('frequency', '', 500, 1000, True, True),
15+
16+
AxisEnum.PULSE_FREQUENCY: ('pulse_frequency', '', 0, 100, True, True),
17+
AxisEnum.PULSE_WIDTH: ('pulse_width', '', 4, 10, True, True),
18+
AxisEnum.PULSE_INTERVAL_RANDOM: ('pulse_interval_random', '', 0, 1, True, True),
19+
AxisEnum.PULSE_RISE_TIME: ('pulse_rise_time', '', 2, 20, True, True),
20+
21+
AxisEnum.VIBRATION_1_FREQUENCY: ('vib1_frequency', '', 0, 100, True, True),
22+
AxisEnum.VIBRATION_1_STRENGTH: ('vib1_strength', '', 0, 1, True, True),
23+
AxisEnum.VIBRATION_1_LEFT_RIGHT_BIAS: ('vib1_left_right_bias', '', 0, 1, True, True),
24+
AxisEnum.VIBRATION_1_HIGH_LOW_BIAS: ('vib1_up_down_bias', '', 0, 1, True, True),
25+
AxisEnum.VIBRATION_1_RANDOM: ('vib1_random', '', 0, 1, True, True),
26+
27+
AxisEnum.VIBRATION_2_FREQUENCY: ('vib2_frequency', '', 0, 100, True, True),
28+
AxisEnum.VIBRATION_2_STRENGTH: ('vib2_strength', '', 0, 1, True, True),
29+
AxisEnum.VIBRATION_2_LEFT_RIGHT_BIAS: ('vib2_left_right_bias', '', 0, 1, True, True),
30+
AxisEnum.VIBRATION_2_HIGH_LOW_BIAS: ('vib2_up_down_bias', '', 0, 1, True, True),
31+
AxisEnum.VIBRATION_2_RANDOM: ('vib2_random', '', 0, 1, True, True),
3132
}
3233

3334
@dataclass
@@ -38,6 +39,7 @@ class FunscriptKitItem:
3839
limit_min: float
3940
limit_max: float
4041
auto_loading: bool
42+
allow_funscript_control: bool
4143

4244

4345
def split_functipt_names(str):
@@ -53,19 +55,20 @@ def __init__(self):
5355
def load_from_settings():
5456
kit = FunscriptKitModel()
5557
for axis in all_axis:
56-
kit.children.append(FunscriptKitItem(axis, '', None, None, False))
58+
kit.children.append(FunscriptKitItem(axis, '', None, None, False, False))
5759

5860
settings = get_settings_instance()
5961
settings.beginGroup('funscript_configuration')
6062
for item in kit.children:
61-
default_funscript_name, default_tcode_axis_name, default_min, default_max, default_auto_load = defaults[item.axis]
63+
default_funscript_name, default_tcode_axis_name, default_min, default_max, default_auto_load, default_allow_funscript_control = defaults[item.axis]
6264

6365
settings.beginGroup(item.axis.settings_key())
6466
item.funscript_names = split_functipt_names(settings.value('funscript_names', default_funscript_name, str))
6567
item.limit_min = settings.value('limit_min', default_min, float)
6668
item.limit_max = settings.value('limit_max', default_max, float)
6769
item.auto_loading = settings.value('auto_loading', default_auto_load, bool)
6870
item.tcode_axis_name = settings.value('tcode_axis', default_tcode_axis_name, str)
71+
item.allow_funscript_control = default_allow_funscript_control
6972
settings.endGroup()
7073

7174
settings.endGroup()
@@ -128,7 +131,10 @@ def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
128131
if role == Qt.DisplayRole:
129132
return item.axis.display_name()
130133
if col == 1:
131-
return ', '.join(item.funscript_names)
134+
if item.allow_funscript_control:
135+
return ', '.join(item.funscript_names)
136+
else:
137+
return ''
132138
if col == 2:
133139
return item.tcode_axis_name
134140
if col == 3:
@@ -142,8 +148,14 @@ def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
142148
return None
143149

144150
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
151+
item: FunscriptKitItem = self.children[index.row()]
145152
if index.column() == 0:
146153
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
154+
if index.column() == 1:
155+
if item.allow_funscript_control:
156+
return Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsEnabled
157+
else:
158+
return Qt.ItemIsSelectable
147159
if index.column() == 5:
148160
return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable
149161
return Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsEnabled

qt_ui/models/script_mapping.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def auto_link_funscript(self, kit: FunscriptKitModel, item: FunscriptTreeItem):
279279
suffix = item.funscript_type
280280
if suffix:
281281
for kit_item in kit.funscript_conifg():
282-
if kit_item.auto_loading:
282+
if kit_item.auto_loading and kit_item.allow_funscript_control:
283283
if suffix in kit_item.funscript_names:
284284
item.axis = kit_item.axis
285285
logger.info(f'auto-linking `{item.file_name}` to {kit_item.axis.display_name()}.')

qt_ui/tcode_command_router.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ class TCodeCommandRouter:
2525
def __init__(self,
2626
alpha: AbstractAxis,
2727
beta: AbstractAxis,
28-
api_volume: AbstractAxis,
28+
volume_api: AbstractAxis,
29+
volume_external: AbstractAxis,
2930

3031
carrier_frequency: AbstractAxis, # either pulse of continuous
3132

@@ -50,7 +51,8 @@ def __init__(self,
5051
):
5152
self.alpha = alpha
5253
self.beta = beta
53-
self.api_volume = api_volume
54+
self.volume_api = volume_api
55+
self.volume_external = volume_external
5456
self.carrier_frequency = carrier_frequency
5557
self.pulse_frequency = pulse_frequency
5658
self.pulse_width = pulse_width
@@ -75,7 +77,8 @@ def reload_kit(self):
7577
axis_enum_to_axis = {
7678
AxisEnum.POSITION_ALPHA: self.alpha,
7779
AxisEnum.POSITION_BETA: self.beta,
78-
AxisEnum.VOLUME_API: self.api_volume,
80+
AxisEnum.VOLUME_API: self.volume_api,
81+
AxisEnum.VOLUME_EXTERNAL: self.volume_external,
7982
AxisEnum.CARRIER_FREQUENCY: self.carrier_frequency,
8083

8184
AxisEnum.PULSE_FREQUENCY: self.pulse_frequency,
@@ -100,14 +103,13 @@ def reload_kit(self):
100103
mapping = {}
101104
for child in kit.children:
102105
child: FunscriptKitItem
103-
if child.auto_loading:
104-
if len(child.tcode_axis_name) == 2:
105-
if child.axis in axis_enum_to_axis:
106-
route = Route(axis_enum_to_axis[child.axis], child.limit_min, child.limit_max)
107-
if child.tcode_axis_name not in mapping:
108-
mapping[child.tcode_axis_name] = route
109-
elif len(child.tcode_axis_name) != 0:
110-
logger.error(f'Invalid T-Code axis name: {child.tcode_axis_name}. Axis name must be 2 chars.')
106+
if len(child.tcode_axis_name) == 2:
107+
if child.axis in axis_enum_to_axis:
108+
route = Route(axis_enum_to_axis[child.axis], child.limit_min, child.limit_max)
109+
if child.tcode_axis_name not in mapping:
110+
mapping[child.tcode_axis_name] = route
111+
elif len(child.tcode_axis_name) != 0:
112+
logger.error(f'Invalid T-Code axis name: {child.tcode_axis_name}. Axis name must be 2 chars.')
111113

112114
# UGLY: patch in five-phase axis
113115
mapping['E0'] = Route(self.fivephase_position.e1, 0, 1)

qt_ui/volume_control_widget.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ def __init__(self, parent=None):
1919
self.api_volume = create_temporal_axis(1.0)
2020
self.inactivity_volume = create_temporal_axis(1.0)
2121
self.master_volume = create_temporal_axis(self.doubleSpinBox_volume.value())
22+
self.external_volume = create_temporal_axis(1.0)
2223
self.volume = VolumeParams(
2324
api=self.api_volume,
2425
master=self.master_volume,
2526
inactivity=self.inactivity_volume,
27+
external=self.external_volume,
2628
)
2729
self.monitor_axis = []
2830

qt_ui/widgets/volume_widget.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def set_axis(self, volume: VolumeParams):
2828
api=volume.api,
2929
master=volume.master,
3030
inactivity=volume.inactivity,
31+
external=volume.external
3132
)
3233

3334
def refresh(self):
@@ -37,10 +38,12 @@ def refresh(self):
3738
master_volume = self.volume.master.last_value()
3839
inactivity_volume = self.volume.inactivity.last_value()
3940
api_volume = self.volume.api.interpolate(time.time() - self.latency)
40-
self.setValue(int(master_volume * api_volume * inactivity_volume * 100))
41+
external_volume = self.volume.external.interpolate(time.time() - self.latency)
42+
self.setValue(int(master_volume * api_volume * inactivity_volume * external_volume * 100))
4143

4244
self.setToolTip(
4345
f"master volume: {master_volume * 100:.0f}%\n" +
46+
f"tcode/funscript volume: {api_volume * 100:.0f}%\n" +
4447
f"inactivity volume: {inactivity_volume * 100:.0f}%\n" +
45-
f"api volume: {api_volume * 100:.0f}%"
48+
f"external volume: {external_volume * 100:.0f}%"
4649
)

stim_math/audio_gen/continuous.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ def generate_audio(self, samplerate, steady_clock: np.ndarray, system_time_estim
3434
volume = \
3535
np.clip(self.params.volume.master.last_value(), 0, 1) * \
3636
np.clip(self.params.volume.api.interpolate(system_time_estimate), 0, 1) * \
37-
np.clip(self.params.volume.inactivity.last_value(), 0, 1)
37+
np.clip(self.params.volume.inactivity.last_value(), 0, 1) * \
38+
np.clip(self.params.volume.external.last_value(), 0, 1)
3839
volume *= self.vibration.generate_vibration_signal(samplerate, len(steady_clock))
3940
if not self.media.is_playing():
4041
volume *= 0
@@ -84,7 +85,8 @@ def generate_audio(self, samplerate, steady_clock: np.ndarray, system_time_estim
8485
volume = \
8586
np.clip(self.params.volume.master.last_value(), 0, 1) * \
8687
np.clip(self.params.volume.api.interpolate(system_time_estimate), 0, 1) * \
87-
np.clip(self.params.volume.inactivity.last_value(), 0, 1)
88+
np.clip(self.params.volume.inactivity.last_value(), 0, 1) * \
89+
np.clip(self.params.volume.external.last_value(), 0, 1)
8890
volume *= self.vibration.generate_vibration_signal(samplerate, len(steady_clock))
8991
if not self.media.is_playing():
9092
volume *= 0
@@ -132,7 +134,8 @@ def generate_audio(self, samplerate, steady_clock: np.ndarray, system_time_estim
132134
volume = \
133135
np.clip(self.params.volume.master.last_value(), 0, 1) * \
134136
np.clip(self.params.volume.api.interpolate(system_time_estimate), 0, 1) * \
135-
np.clip(self.params.volume.inactivity.last_value(), 0, 1)
137+
np.clip(self.params.volume.inactivity.last_value(), 0, 1) * \
138+
np.clip(self.params.volume.external.last_value(), 0, 1)
136139
volume *= self.vibration.generate_vibration_signal(samplerate, len(steady_clock))
137140
if not self.media.is_playing():
138141
volume *= 0

0 commit comments

Comments
 (0)