@@ -122,6 +122,7 @@ def _load_menu_entries(self) -> List[ServiceMenuEntry]:
122
122
ServiceMenuEntry ("Audits Menu" , self ._audits_menu ),
123
123
ServiceMenuEntry ("Adjustments Menu" , self ._adjustments_menu ),
124
124
ServiceMenuEntry ("Utilities Menu" , self ._utilities_menu ),
125
+ ServiceMenuEntry ("Audio Menu" , self ._audio_menu )
125
126
126
127
]
127
128
return entries
@@ -184,6 +185,28 @@ def _load_adjustments_menu_entries(self) -> List[ServiceMenuEntry]:
184
185
async def _adjustments_menu (self ):
185
186
await self ._make_menu (self ._load_adjustments_menu_entries ())
186
187
188
+ # Audio
189
+ def _load_audio_menu_entries (self ) -> List [ServiceMenuEntry ]:
190
+ """Return the audio menu items with label and callback."""
191
+ items = [
192
+ ServiceMenuEntry ("Software Levels" , self ._volume_menu )
193
+ ]
194
+
195
+ self .debug_log ("Looking for platform volumes: %s" , self .machine .hardware_platforms )
196
+ for p , platform in self .machine .hardware_platforms .items ():
197
+ # TODO: Define an AudioInterface base class
198
+ if getattr (platform , "audio_interface" , None ):
199
+ self .debug_log ("Found '%s' platform audio for volume: %s" , p , platform )
200
+ # TODO: find a good way to get a name of a platform
201
+ name = p .title ()
202
+ items .append (ServiceMenuEntry (f"{ name } Levels" , partial (self ._volume_menu , platform )))
203
+ else :
204
+ self .debug_log ("Platform '%s' has no audio to configure volume: %s" , p , platform )
205
+ return items
206
+
207
+ async def _audio_menu (self ):
208
+ await self ._make_menu (self ._load_audio_menu_entries ())
209
+
187
210
# Utilities
188
211
def _load_utilities_menu_entries (self ) -> List [ServiceMenuEntry ]:
189
212
"""Return the utilities menu items with label and callback."""
@@ -433,6 +456,120 @@ async def _light_test_menu(self):
433
456
434
457
self .machine .events .post ("service_light_test_stop" )
435
458
459
+ async def _volume_menu (self , platform = None ):
460
+ position = 0
461
+ if platform :
462
+ item_configs = platform .audio_interface .amps
463
+ else :
464
+ item_configs = self .machine .config ["sound_system" ]["tracks" ]
465
+ items = [{
466
+ ** config ,
467
+ "name" : config .get ("name" , track ),
468
+ "label" : config .get ("label" , track ),
469
+ "is_platform" : bool (platform ),
470
+ # TODO: Give each software track a 'name' property
471
+ "value" : self .machine .variables .get_machine_var (f"{ config ['name' ] if platform else track } _volume" ) or config ['volume' ]
472
+ } for track , config in item_configs .items ()]
473
+
474
+ # do not crash if no items
475
+ if not items : # pragma: no cover
476
+ return
477
+
478
+ # Convert floats to ints for systems that use 0.0-1.0 for volume
479
+ for item in items :
480
+ if isinstance (item ['value' ], float ):
481
+ item ['value' ] = int (item ['value' ] * 100 )
482
+
483
+ # If supported on hardware platform, add option to write to firmware
484
+ if platform and hasattr (platform .audio_interface , "save_settings_to_firmware" ):
485
+ items .append ({
486
+ "name" : "write_to_firmware" ,
487
+ "label" : "Write Settings" ,
488
+ "is_platform" : True ,
489
+ "value" : "Confirm" ,
490
+ "levels_list" : ["Confirm" , "Saved" ]
491
+ })
492
+
493
+ self ._update_volume_slide (items , position )
494
+
495
+ while True :
496
+ key = await self ._get_key ()
497
+ if key == 'ESC' :
498
+ break
499
+ if key == 'UP' :
500
+ position += 1
501
+ if position >= len (items ):
502
+ position = 0
503
+ self ._update_volume_slide (items , position )
504
+ elif key == 'DOWN' :
505
+ position -= 1
506
+ if position < 0 :
507
+ position = len (items ) - 1
508
+ self ._update_volume_slide (items , position )
509
+ elif key == 'ENTER' :
510
+ # change setting
511
+ await self ._volume_change (items , position , platform , focus_change = "enter" )
512
+
513
+ self .machine .events .post ("service_volume_stop" )
514
+
515
+
516
+ def _update_volume_slide (self , items , position , is_change = False , focus_change = None ):
517
+ config = items [position ]
518
+ event = "service_volume_{}" .format ("edit" if is_change else "start" )
519
+ # The 'focus_change' argument can be used to start/stop sound files playing
520
+ # during the service menu, to test volume.
521
+ self .machine .events .post (event ,
522
+ settings_label = config ["label" ],
523
+ value_label = config ["value" ],
524
+ track = config ["name" ],
525
+ is_platform = config ["is_platform" ],
526
+ focus_change = focus_change )
527
+
528
+ async def _volume_change (self , items , position , platform , focus_change = None ):
529
+ self ._update_volume_slide (items , position , focus_change = focus_change )
530
+ if items [position ].get ("levels_list" ):
531
+ values = items [position ]["levels_list" ]
532
+ else :
533
+ # Use ints for values to avoid floating-point comparisons
534
+ values = [int ((0.05 * i ) * 100 ) for i in range (0 ,21 )]
535
+ value_position = values .index (items [position ]["value" ])
536
+ self ._update_volume_slide (items , position , is_change = True )
537
+
538
+ while True :
539
+ key = await self ._get_key ()
540
+ new_value = None
541
+ if key == 'ESC' :
542
+ self ._update_volume_slide (items , position , focus_change = "exit" )
543
+ break
544
+ if key == 'UP' :
545
+ value_position += 1
546
+ if value_position >= len (values ):
547
+ value_position = 0
548
+ new_value = values [value_position ]
549
+ elif key == 'DOWN' :
550
+ value_position -= 1
551
+ if value_position < 0 :
552
+ value_position = len (values ) - 1
553
+ new_value = values [value_position ]
554
+ if new_value is not None :
555
+ items [position ]['value' ] = new_value
556
+ # Check for a firmware update
557
+ if items [position ]['name' ] == "write_to_firmware" :
558
+ if new_value == "Saved" :
559
+ platform .audio_interface .save_settings_to_firmware ()
560
+ # Remove the options from the list
561
+ values = ['Saved' ]
562
+ items [position ]['levels_list' ] = values
563
+ else :
564
+ # Internally tracked values divide by 100 to store a float.
565
+ # External (hardware) values, use the value units provided
566
+ # TODO: Create an Amp/Track class to internalize this method.
567
+ if not items [position ].get ("levels_list" ):
568
+ new_value = new_value / 100
569
+ self .machine .variables .set_machine_var (f"{ items [position ]['name' ]} _volume" , new_value , persist = True )
570
+ self ._update_volume_slide (items , position , is_change = True )
571
+
572
+ # AUDIT Menu
436
573
def _load_audit_menu_entries (self ) -> List [ServiceMenuEntry ]:
437
574
"""Return the audit menu items with label and callback."""
438
575
return [
0 commit comments