mirror of
				https://github.com/OpenVoiceOS/OpenVoiceOS
				synced 2025-06-05 22:19:21 +02:00 
			
		
		
		
	MycroftOS: Add patched volume skill by default.
- This will change later on when the enclosure code is ready and take care of the volume control. For now we use ALSA and added the MycroftOS to ALSA_PLATFORMS
This commit is contained in:
		
							
								
								
									
										7
									
								
								buildroot-external/rootfs-overlay/opt/mycroft/skills/mycroft-volume.mycroftai/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								buildroot-external/rootfs-overlay/opt/mycroft/skills/mycroft-volume.mycroftai/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| settings.json | ||||
|  | ||||
| # python compiled files | ||||
| *.pyc | ||||
|  | ||||
| # Vim temp files | ||||
| .*.sw? | ||||
| @@ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
|  | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
|  | ||||
|    1. Definitions. | ||||
|  | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
|  | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
|  | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
|  | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
|  | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
|  | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
|  | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
|  | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
|  | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
|  | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
|  | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
|  | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
|  | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
|  | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
|  | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
|  | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
|  | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
|  | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
|  | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
|  | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
|  | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
|  | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
|  | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
|  | ||||
|    END OF TERMS AND CONDITIONS | ||||
|  | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
|  | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "[]" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
|  | ||||
|    Copyright [yyyy] [name of copyright owner] | ||||
|  | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
|  | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
| @@ -0,0 +1,25 @@ | ||||
| # <img src='https://raw.githack.com/FortAwesome/Font-Awesome/master/svgs/solid/volume-down.svg' card_color='#22a7f0' width='50' height='50' style='vertical-align:bottom'/> Volume Control | ||||
| Control the volume of your system | ||||
|  | ||||
| ## About | ||||
| Control the volume of Mycroft with verbal commands or by spinning the physical | ||||
| button on a Mark 1. | ||||
|  | ||||
| ## Examples | ||||
| * "Turn up the volume" | ||||
| * "Decrease the audio" | ||||
| * "Mute audio" | ||||
| * "Set volume to 5" | ||||
| * "Set volume to 75 percent" | ||||
|  | ||||
| ## Credits | ||||
| Mycroft AI (@MycroftAI) | ||||
|  | ||||
| ## Category | ||||
| **Configuration** | ||||
|  | ||||
| ## Tags | ||||
| #volume | ||||
| #volume-control | ||||
| #sound | ||||
| #system | ||||
| @@ -0,0 +1,399 @@ | ||||
| # Copyright 2017 Mycroft AI Inc. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #    http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| from alsaaudio import Mixer, mixers as alsa_mixers | ||||
| from os.path import dirname, join | ||||
|  | ||||
| from adapt.intent import IntentBuilder | ||||
| from mycroft.audio import wait_while_speaking | ||||
| from mycroft.messagebus.message import Message | ||||
| from mycroft.skills.core import MycroftSkill, intent_handler | ||||
| from mycroft.util import play_wav | ||||
| from mycroft.util.parse import extract_number | ||||
|  | ||||
|  | ||||
| ALSA_PLATFORMS = ['MycroftOS', 'mycroft_mark_1', 'picroft', 'unknown'] | ||||
|  | ||||
|  | ||||
| class VolumeSkill(MycroftSkill): | ||||
|     """ | ||||
|     Control the audio volume for the Mycroft system | ||||
|  | ||||
|     Terminology: | ||||
|        "Level" =  Mycroft volume levels, from 0 to 10 | ||||
|        "Volume" = ALSA mixer setting, from 0 to 100 | ||||
|     """ | ||||
|  | ||||
|     MIN_LEVEL = 0 | ||||
|     MAX_LEVEL = 10 | ||||
|  | ||||
|     # TODO: Translation layer (have to match word in Level.voc) | ||||
|     VOLUME_WORDS = { | ||||
|         'loud': 9, | ||||
|         'normal': 6, | ||||
|         'quiet': 3 | ||||
|     } | ||||
|  | ||||
|     def __init__(self): | ||||
|         super(VolumeSkill, self).__init__("VolumeSkill") | ||||
|         self.settings["default_level"] = 6  # can be 0 (off) to 10 (max) | ||||
|         self.settings["min_volume"] = 0     # can be 0 to 100 | ||||
|         if self.config_core['enclosure'].get('platform') == 'mycroft_mark_1': | ||||
|             self.settings["max_volume"] = 83   # can be 0 to 83 | ||||
|         else: | ||||
|             self.settings["max_volume"] = 100   # can be 0 to 100 | ||||
|         self.volume_sound = join(dirname(__file__), "blop-mark-diangelo.wav") | ||||
|         self.vol_before_mute = None | ||||
|         self._mixer = None | ||||
|  | ||||
|     def _clear_mixer(self): | ||||
|         """For Unknown platforms reinstantiate the mixer. | ||||
|  | ||||
|         For mycroft_mark_1 do not reinstantiate the mixer. | ||||
|         """ | ||||
|         platform = self.config_core['enclosure'].get('platform', 'unknown') | ||||
|         if platform != 'mycroft_mark_1': | ||||
|             self._mixer = None | ||||
|  | ||||
|     def _get_mixer(self): | ||||
|         self.log.debug('Finding Alsa Mixer for control...') | ||||
|         mixer = None | ||||
|         try: | ||||
|             # If there are only 1 mixer use that one | ||||
|             mixers = alsa_mixers() | ||||
|             if len(mixers) == 1: | ||||
|                 mixer = Mixer(mixers[0]) | ||||
|             elif 'Master' in mixers: | ||||
|                 # Try using the default mixer (Master) | ||||
|                 mixer = Mixer('Master') | ||||
|             elif 'PCM' in mixers: | ||||
|                 # PCM is another common one | ||||
|                 mixer = Mixer('PCM') | ||||
|             elif 'Digital' in mixers: | ||||
|                 # My mixer is called 'Digital' (JustBoom DAC) | ||||
|                 mixer = Mixer('Digital') | ||||
|             else: | ||||
|                 # should be equivalent to 'Master' | ||||
|                 mixer = Mixer() | ||||
|         except Exception: | ||||
|             # Retry instanciating the mixer with the built-in default | ||||
|             try: | ||||
|                 mixer = Mixer() | ||||
|             except Exception as e: | ||||
|                 self.log.error('Couldn\'t allocate mixer, {}'.format(repr(e))) | ||||
|         self._mixer = mixer | ||||
|         return mixer | ||||
|  | ||||
|     def initialize(self): | ||||
|         # Register handlers to detect percentages as reported by STT | ||||
|         for i in range(101):  # numbers 0 to 100 | ||||
|             self.register_vocabulary(str(i) + '%', 'Percent') | ||||
|  | ||||
|         # Register handlers for messagebus events | ||||
|         self.add_event('mycroft.volume.increase', | ||||
|                        self.handle_increase_volume) | ||||
|         self.add_event('mycroft.volume.decrease', | ||||
|                        self.handle_decrease_volume) | ||||
|         self.add_event('mycroft.volume.mute', | ||||
|                        self.handle_mute_volume) | ||||
|         self.add_event('mycroft.volume.unmute', | ||||
|                        self.handle_unmute_volume) | ||||
|         self.add_event('recognizer_loop:record_begin', | ||||
|                        self.duck) | ||||
|         self.add_event('recognizer_loop:record_end', | ||||
|                        self.unduck) | ||||
|  | ||||
|         self.vol_before_mute = self.__get_system_volume() | ||||
|  | ||||
|     @property | ||||
|     def mixer(self): | ||||
|         platform = self.config_core['enclosure'].get('platform', 'unknown') | ||||
|         if platform in ALSA_PLATFORMS: | ||||
|             return self._mixer or self._get_mixer() | ||||
|         else: | ||||
|             return None | ||||
|  | ||||
|     def _setvolume(self, vol, emit=True): | ||||
|         # Update ALSA | ||||
|         if self.mixer: | ||||
|             self.log.debug(vol) | ||||
|             self.mixer.setvolume(vol) | ||||
|         # TODO: Remove this and control volume at the Enclosure level in | ||||
|         # response to the mycroft.volume.set message. | ||||
|  | ||||
|         if emit: | ||||
|             # Notify non-ALSA systems of volume change | ||||
|             self.bus.emit(Message('mycroft.volume.set', | ||||
|                                   data={"percent": vol/100.0})) | ||||
|  | ||||
|     # Change Volume to X (Number 0 to) Intent Handlers | ||||
|     @intent_handler(IntentBuilder("SetVolume").require("Volume") | ||||
|                     .optionally("Increase").optionally("Decrease") | ||||
|                     .optionally("To").require("Level")) | ||||
|     def handle_set_volume(self, message): | ||||
|         self._clear_mixer() | ||||
|         default_vol = self.__get_system_volume(50) | ||||
|  | ||||
|         level = self.__get_volume_level(message, default_vol) | ||||
|         self._setvolume(self.__level_to_volume(level)) | ||||
|         if level == self.MAX_LEVEL: | ||||
|             self.speak_dialog('max.volume') | ||||
|         else: | ||||
|             self.speak_dialog('set.volume', data={'volume': level}) | ||||
|  | ||||
|     # Set Volume Percent Intent Handlers | ||||
|     @intent_handler(IntentBuilder("SetVolumePercent").require("Volume") | ||||
|                     .optionally("Increase").optionally("Decrease") | ||||
|                     .optionally("To").require("Percent")) | ||||
|     def handle_set_volume_percent(self, message): | ||||
|         self._clear_mixer() | ||||
|         percent = extract_number(message.data['utterance'].replace('%', '')) | ||||
|         percent = int(percent) | ||||
|         self._setvolume(percent) | ||||
|         self.speak_dialog('set.volume.percent', data={'level': percent}) | ||||
|  | ||||
|     # Volume Status Intent Handlers | ||||
|     @intent_handler(IntentBuilder("QueryVolume").optionally("Query") | ||||
|                     .require("Volume")) | ||||
|     def handle_query_volume(self, message): | ||||
|         self._clear_mixer() | ||||
|         level = self.__volume_to_level(self.__get_system_volume(0, show=True)) | ||||
|         self.speak_dialog('volume.is', data={'volume': round(level)}) | ||||
|  | ||||
|     @intent_handler(IntentBuilder("QueryVolumePhrase").require("QueryPhrase") | ||||
|                     .optionally("Volume")) | ||||
|     def handle_query_volume_phrase(self, message): | ||||
|         self.handle_query_volume(message) | ||||
|  | ||||
|     def __communicate_volume_change(self, message, dialog, code, changed): | ||||
|         play_sound = message.data.get('play_sound', False) | ||||
|         if play_sound: | ||||
|             if changed: | ||||
|                 play_wav(self.volume_sound) | ||||
|         else: | ||||
|             if (not changed) and (code != 0): | ||||
|                 self.speak_dialog('already.max.volume', data={'volume': code}) | ||||
|  | ||||
|     # Increase Volume Intent Handlers | ||||
|     @intent_handler(IntentBuilder("IncreaseVolume").require("Volume") | ||||
|                     .require("Increase")) | ||||
|     def handle_increase_volume(self, message): | ||||
|         self.__communicate_volume_change(message, 'increase.volume', | ||||
|                                          *self.__update_volume(+1)) | ||||
|  | ||||
|     @intent_handler(IntentBuilder("IncreaseVolumeSet").require("Set") | ||||
|                     .optionally("Volume").require("Increase")) | ||||
|     def handle_increase_volume_set(self, message): | ||||
|         self._clear_mixer() | ||||
|         self.handle_increase_volume(message) | ||||
|  | ||||
|     @intent_handler(IntentBuilder("IncreaseVolumePhrase") | ||||
|                     .require("IncreasePhrase")) | ||||
|     def handle_increase_volume_phrase(self, message): | ||||
|         self._clear_mixer() | ||||
|         self.handle_increase_volume(message) | ||||
|  | ||||
|     # Decrease Volume Intent Handlers | ||||
|     @intent_handler(IntentBuilder("DecreaseVolume").require("Volume") | ||||
|                     .require("Decrease")) | ||||
|     def handle_decrease_volume(self, message): | ||||
|         self.__communicate_volume_change(message, 'decrease.volume', | ||||
|                                          *self.__update_volume(-1)) | ||||
|  | ||||
|     @intent_handler(IntentBuilder("DecreaseVolumeSet").require("Set") | ||||
|                     .optionally("Volume").require("Decrease")) | ||||
|     def handle_decrease_volume_set(self, message): | ||||
|         self.handle_decrease_volume(message) | ||||
|  | ||||
|     @intent_handler(IntentBuilder("DecreaseVolumePhrase") | ||||
|                     .require("DecreasePhrase")) | ||||
|     def handle_decrease_volume_phrase(self, message): | ||||
|         self.handle_decrease_volume(message) | ||||
|  | ||||
|     # Maximum Volume Intent Handlers | ||||
|     @intent_handler(IntentBuilder("MaxVolume").optionally("Set") | ||||
|                     .require("Volume").optionally("Increase") | ||||
|                     .require("MaxVolume")) | ||||
|     def handle_max_volume(self, message): | ||||
|         self._clear_mixer() | ||||
|         self._setvolume(self.settings["max_volume"]) | ||||
|         speak_message = message.data.get('speak_message', True) | ||||
|         if speak_message: | ||||
|             self.speak_dialog('max.volume') | ||||
|             wait_while_speaking() | ||||
|         self.bus.emit(Message('mycroft.volume.duck')) | ||||
|  | ||||
|     @intent_handler(IntentBuilder("MaxVolumeIncreaseMax") | ||||
|                     .require("MaxVolumePhrase").optionally("Volume") | ||||
|                     .require("Increase").optionally("MaxVolume")) | ||||
|     def handle_max_volume_increase_to_max(self, message): | ||||
|         self.handle_max_volume(message) | ||||
|  | ||||
|     def duck(self, message): | ||||
|         self._clear_mixer() | ||||
|         if self.settings.get('ducking', True): | ||||
|             self._mute_volume() | ||||
|  | ||||
|     def unduck(self, message): | ||||
|         self._clear_mixer() | ||||
|         if self.settings.get('ducking', True): | ||||
|             self._unmute_volume() | ||||
|  | ||||
|     def _mute_volume(self, message=None, speak=False): | ||||
|         self.log.debug('MUTING!') | ||||
|         self.vol_before_mute = self.__get_system_volume() | ||||
|         self.log.debug(self.vol_before_mute) | ||||
|         if speak: | ||||
|             self.speak_dialog('mute.volume') | ||||
|             wait_while_speaking() | ||||
|         self._setvolume(0, emit=False) | ||||
|         self.bus.emit(Message('mycroft.volume.duck')) | ||||
|  | ||||
|     # Mute Volume Intent Handlers | ||||
|     @intent_handler(IntentBuilder("MuteVolume").require( | ||||
|         "Volume").require("Mute")) | ||||
|     def handle_mute_volume(self, message): | ||||
|         self._clear_mixer() | ||||
|         self._mute_volume(speak=message.data.get('speak_message', True)) | ||||
|  | ||||
|     def _unmute_volume(self, message=None, speak=False): | ||||
|         if self.vol_before_mute is None: | ||||
|             vol = self.__level_to_volume(self.settings["default_level"]) | ||||
|         else: | ||||
|             vol = self.vol_before_mute | ||||
|         self.vol_before_mute = None | ||||
|  | ||||
|         self._setvolume(vol, emit=False) | ||||
|         self.bus.emit(Message('mycroft.volume.unduck')) | ||||
|  | ||||
|         if speak: | ||||
|             self.speak_dialog('reset.volume', | ||||
|                               data={'volume': | ||||
|                                     self.settings["default_level"]}) | ||||
|  | ||||
|     # Unmute/Reset Volume Intent Handlers | ||||
|     @intent_handler(IntentBuilder("UnmuteVolume").require("Volume") | ||||
|                     .require("Unmute")) | ||||
|     def handle_unmute_volume(self, message): | ||||
|         self._clear_mixer() | ||||
|         self._unmute_volume(speak=message.data.get('speak_message', True)) | ||||
|  | ||||
|     def __volume_to_level(self, volume): | ||||
|         """ | ||||
|             Convert a 'volume' to a 'level' | ||||
|  | ||||
|             Args: | ||||
|                 volume (int): min_volume..max_volume | ||||
|             Returns: | ||||
|                 int: the equivalent level | ||||
|         """ | ||||
|         range = self.MAX_LEVEL - self.MIN_LEVEL | ||||
|         min_vol = self.settings["min_volume"] | ||||
|         max_vol = self.settings["max_volume"] | ||||
|         prop = float(volume - min_vol) / max_vol | ||||
|         level = int(round(self.MIN_LEVEL + range * prop)) | ||||
|         if level > self.MAX_LEVEL: | ||||
|             level = self.MAX_LEVEL | ||||
|         elif level < self.MIN_LEVEL: | ||||
|             level = self.MIN_LEVEL | ||||
|         return level | ||||
|  | ||||
|     def __level_to_volume(self, level): | ||||
|         """ | ||||
|             Convert a 'level' to a 'volume' | ||||
|  | ||||
|             Args: | ||||
|                 level (int): 0..MAX_LEVEL | ||||
|             Returns: | ||||
|                 int: the equivalent volume | ||||
|         """ | ||||
|         range = self.settings["max_volume"] - self.settings["min_volume"] | ||||
|         prop = float(level) / self.MAX_LEVEL | ||||
|         volume = int(round(self.settings["min_volume"] + int(range) * prop)) | ||||
|  | ||||
|         return volume | ||||
|  | ||||
|     @staticmethod | ||||
|     def __bound_level(level): | ||||
|         if level > VolumeSkill.MAX_LEVEL: | ||||
|             level = VolumeSkill.MAX_LEVEL | ||||
|         elif level < VolumeSkill.MIN_LEVEL: | ||||
|             level = VolumeSkill.MIN_LEVEL | ||||
|         return level | ||||
|  | ||||
|     def __update_volume(self, change=0): | ||||
|         """ | ||||
|             Attempt to change audio level | ||||
|  | ||||
|             Args: | ||||
|                 change (int): +1 or -1; the step to change by | ||||
|  | ||||
|             Returns: tuple(new level code int(0..10), | ||||
|                            whether level changed (bool)) | ||||
|         """ | ||||
|         old_level = self.__volume_to_level(self.__get_system_volume(0)) | ||||
|         new_level = self.__bound_level(old_level + change) | ||||
|         self.enclosure.eyes_volume(new_level) | ||||
|         self._setvolume(self.__level_to_volume(new_level)) | ||||
|         return new_level, new_level != old_level | ||||
|  | ||||
|     def __get_system_volume(self, default=50, show=False): | ||||
|         """ Get volume, either from mixer or ask on messagebus. | ||||
|  | ||||
|         The show parameter should only be True when a user is requesting | ||||
|         the volume and not the system. | ||||
|         TODO: Remove usage of Mixer and move that stuff to enclosure. | ||||
|         """ | ||||
|         vol = default | ||||
|         if self.mixer: | ||||
|             vol = min(self.mixer.getvolume()[0], 100) | ||||
|             self.log.debug('Volume before mute: {}'.format(vol)) | ||||
|         else: | ||||
|             vol_msg = self.bus.wait_for_response( | ||||
|                                 Message("mycroft.volume.get", {'show': show})) | ||||
|             if vol_msg: | ||||
|                 vol = int(vol_msg.data["percent"] * 100) | ||||
|  | ||||
|         return vol | ||||
|  | ||||
|     def __get_volume_level(self, message, default=None): | ||||
|         """ Retrievs volume from message. """ | ||||
|         level_str = message.data.get('Level', default) | ||||
|         level = self.settings["default_level"] | ||||
|  | ||||
|         try: | ||||
|             level = self.VOLUME_WORDS[level_str] | ||||
|         except KeyError: | ||||
|             try: | ||||
|                 level = int(extract_number(level_str)) | ||||
|                 if (level == self.MAX_LEVEL + 1): | ||||
|                     # Assume that user meant max volume | ||||
|                     level = self.MAX_LEVEL | ||||
|                 elif (level > self.MAX_LEVEL): | ||||
|                     # Guess that the user said something like 100 percent | ||||
|                     # so convert that into a level value | ||||
|                     level = self.MAX_LEVEL * level/100 | ||||
|             except ValueError: | ||||
|                 pass | ||||
|  | ||||
|         level = self.__bound_level(level) | ||||
|         return level | ||||
|  | ||||
|     def shutdown(self): | ||||
|         if self.vol_before_mute is not None: | ||||
|             self._unmute_volume() | ||||
|  | ||||
|  | ||||
| def create_skill(): | ||||
|     return VolumeSkill() | ||||
										
											Binary file not shown.
										
									
								
							| @@ -0,0 +1,2 @@ | ||||
| Lydstyrken er allerede på maximum | ||||
| Jeg kan ikke blive højere | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume resuceret to {{volume}} | ||||
| Volume mindsket til {{volume}} | ||||
| Volume er nu {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volume er nu {{volume}} | ||||
| Lydstyrke forøget til {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Audio bliver muted | ||||
| Lyd bliver muted | ||||
| @@ -0,0 +1,2 @@ | ||||
| Lydstyrke nulstillet til {{volume} | ||||
| Lydstyrke nulstillet til {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Lydstyrke stillet til {{volume}} | ||||
| Lydstyrke indstillet til {{volume}} | ||||
| Lydstyrken ændret til {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Lydstyrken er sat til {{level}} procent | ||||
| Lydstyrkem er opdateret til {{level}} procent | ||||
| Lydstyrken er ændret til {{evel}} percent | ||||
| @@ -0,0 +1,2 @@ | ||||
| Lydstyrken er sat til {{volume}} | ||||
| Lydstyrken er {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Die Lautstärke ist bereits auf Maximum | ||||
| Ich kann nicht lauter werden | ||||
| @@ -0,0 +1,3 @@ | ||||
| Lautstärke auf {{volume}} reduziert | ||||
| Lautstärke auf {{volume}} reduziert | ||||
| Lautstärke ist jetzt {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Lautstärke ist jetzt {{volume}} | ||||
| Lautstärke auf {{volume}} erhöht | ||||
| @@ -0,0 +1,3 @@ | ||||
| Lautstärke auf maximum eingestellt | ||||
| Lautstärke auf Maximum gesetzt | ||||
| Lautstärke auf maximum geändert | ||||
| @@ -0,0 +1,2 @@ | ||||
| Audio wird gedämpft | ||||
| Der Ton wird gedämpft | ||||
| @@ -0,0 +1,2 @@ | ||||
| Lautstärke auf {{volume}} zurückgesetzt | ||||
| Lautstärke auf {{volume}} wiederhergestellt | ||||
| @@ -0,0 +1,3 @@ | ||||
| Lautstärke auf {{volume}} eingestellt | ||||
| Lautstärke auf {{volume}} aktualisiert | ||||
| Lautstärke auf {{volume}} geändert | ||||
| @@ -0,0 +1,3 @@ | ||||
| Lautstärke gesetzt auf {{level}} Prozent | ||||
| Lautstärke aktualisiert auf {{level}} Prozent | ||||
| Lautstärke auf {{level}} Prozent geändert | ||||
| @@ -0,0 +1,2 @@ | ||||
| Die Lautstärke ist auf {{volume}} gesetzt | ||||
| Die Lautstärke ist bei {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volume is already at maximum | ||||
| I can't get any louder | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume reduced to {{volume}} | ||||
| Volume decreased to {{volume}} | ||||
| Volume is now {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volume is now {{volume}} | ||||
| Volume increased to {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume set to maximum level | ||||
| Volume updated to maximum level | ||||
| Volume changed to maximum level | ||||
| @@ -0,0 +1,2 @@ | ||||
| Audio is going to be muted | ||||
| Sound is going to be muted | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volume reset to {{volume}} | ||||
| Volume restored to {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume set to {{volume}} | ||||
| Volume updated to {{volume}} | ||||
| Volume changed to {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume set to {{level}} percent | ||||
| Volume updated to {{level}} percent | ||||
| Volume changed to {{level}} percent | ||||
| @@ -0,0 +1,2 @@ | ||||
| The volume is set to {{volume}} | ||||
| The volume is at {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| El volumen ya está al máximo | ||||
| No se puede poner más fuerte | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volumen reducido a {{volume}} | ||||
| Volumen bajado a {{volume}} | ||||
| El volumen está al {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| El volumen está al {{volume}} | ||||
| Volumen subido al {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volumen a máximo nivel | ||||
| Volumen actualizado a máximo nivel | ||||
| Volumen cambiado a máximo nivel | ||||
| @@ -0,0 +1,2 @@ | ||||
| El audio se silenciará | ||||
| El sonido se silenciará | ||||
| @@ -0,0 +1,2 @@ | ||||
| El volumen se volverá a poner al {{volume}} | ||||
| El volumen restaurado al {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volumen puesto al {{volume}} | ||||
| Volumen actualizado al {{volume}} | ||||
| Volumen cambiado al {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volumen establecido al {{level}} por ciento | ||||
| Volumen actualizado al {{level}} por ciento | ||||
| Volumen cambiado al {{level}} por ciento | ||||
| @@ -0,0 +1,2 @@ | ||||
| El volumen está establecido al {{volume}} | ||||
| El volumen está al {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Le volume est déjà au maximum | ||||
| Je ne peux pas être plus fort | ||||
| @@ -0,0 +1,3 @@ | ||||
| Le volume a été réduit à {{volume}} | ||||
| Le volume a été baissé à {{volume}} | ||||
| Le volume est maintenant à {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Le volume est maintenant à {{volume}} | ||||
| Le volume a été augmenté à {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| L'audio va être mis en muet | ||||
| Le son va être mis en muet | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volume réinitialisé à {{volume}} | ||||
| Volume restauré à {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume définit à {{volume}} | ||||
| Volume mis à jour à {{volume}} | ||||
| Volume changé à {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Le volume a été définit à {{volume}} | ||||
| Le volume est à {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| A hangerő már maximumon van | ||||
| Hangosabbra nem megy | ||||
| @@ -0,0 +1,3 @@ | ||||
| A hangerő csökkentve. Új szint: {{volume}} | ||||
| A hangerő lecsökkent a következő szintre: {{volume}} | ||||
| A hangerő új szintje: {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| A hangerő új szintje: {{volume}} | ||||
| A hangerő megnövelve. Új szint: {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| A hang elnémul | ||||
| A hang elnémítva | ||||
| @@ -0,0 +1,2 @@ | ||||
| A hangerő visszaállítva, értéke: {{volume}} | ||||
| A hangerőt visszaállítottam, az új szint: {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| A hangerő beállítva a következő szintre: {{volume}} | ||||
| A hangerő megváltozott. Új értéke: {{volume}} | ||||
| A hangerő új értéke a következő: {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| A hangerő szintje: {{volume}} | ||||
| A hangerő értéke {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Il volume è già al massimo | ||||
| Non posso aumentare di più il volume | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume ridotto a {{volume}} | ||||
| Volume diminuito a {{volume}} | ||||
| Il volume adesso è {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Il volume adesso è {{volume}} | ||||
| Volume aumentato a {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume impostato a livello massimo | ||||
| Volume aggiornato al massimo livello | ||||
| Volume cambiato su livello massimo | ||||
| @@ -0,0 +1,2 @@ | ||||
| L'audio sta per essere disattivato | ||||
| Il suono sta per essere disattivato | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volume reimpostato a {{volume}} | ||||
| Volume ripristinato a {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume impostato a {{volume}} | ||||
| Volume aggiornato a {{volume}} | ||||
| Volume modificato a {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume impostato a {{level}} percento | ||||
| Volume aggiornato al {{level}} percento | ||||
| Volume modificato al {{level}} percento | ||||
| @@ -0,0 +1,2 @@ | ||||
| Il volume è impostato a {{volume}} | ||||
| Il volume è a {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Het geluidsniveau is al op maximum | ||||
| Ik kan niet harder | ||||
| @@ -0,0 +1,3 @@ | ||||
| Geluidsniveau verlaagd naar {{volume}} | ||||
| Geluidsniveau afgenomen tot {{volume}} | ||||
| Geluidsniveau is nu {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Geluidsniveau is nu {{volume}} | ||||
| Geluidsniveau toegenomen tot {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Audio zal worden gedempt | ||||
| Geluid zal worden gedempt | ||||
| @@ -0,0 +1,2 @@ | ||||
| Geluidsniveau reset naar {{volume}} | ||||
| Geluidsniveau hersteld naar {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Geluidsniveau gezet op {{volume}} | ||||
| Geluidsniveau bijgewerkt naar {{volume}} | ||||
| Geluidsniveau gewijzigd naar {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volume op {{level}} procent ingesteld | ||||
| Volume naar {{level}} procent bijgewerkt | ||||
| Volume in {{level}} procent veranderd | ||||
| @@ -0,0 +1,2 @@ | ||||
| Het geluidsniveau is gezet op {{volume}} | ||||
| Het geluidsniveau staat op {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Громкость уже на максимуме | ||||
| Я не могу быть громче | ||||
| @@ -0,0 +1,3 @@ | ||||
| Громкость уменьшена к {{volume}} | ||||
| Громкость убавлена до {{volume}} | ||||
| Громкость теперь {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Громкость теперь {{volume}} | ||||
| Громкость увеличена до {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Перехожу в режим без звука | ||||
| Звук будет выключен | ||||
| @@ -0,0 +1,2 @@ | ||||
| Громкость перезагружена на {{volume}} | ||||
| Громкость восстановлена до {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Громкость установлена на {{volume}} | ||||
| Громкость обновлена до {{volume}} | ||||
| Громкость изменена на {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Уровень звука установлен на {{volume}} | ||||
| Уровень звука {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volymen är redan på max | ||||
| Jag kan inte höja mer | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volymen har sänkts till {{volume}} | ||||
| Volymen sänktes till {{volume}} | ||||
| Volymen är nu {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volymen är nu {{volume}} | ||||
| Volymen ökades till {{volume}} | ||||
| @@ -0,0 +1,2 @@ | ||||
| Audio kommer att tystas | ||||
| Ljudet kommer att tystas | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volymen har återställts till {{volume}} | ||||
| Volymen har återställts till {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volymen har satts till {{volume}} | ||||
| Volymen har ändrats till {{volume}} | ||||
| Volymen är nu {{volume}} | ||||
| @@ -0,0 +1,3 @@ | ||||
| Volymen har satts till {{level}} procent | ||||
| Volymen har ändrats till {{level}} procent | ||||
| Volymen är nu {{level}} procent | ||||
| @@ -0,0 +1,2 @@ | ||||
| Volymen är satt till {{volume}} | ||||
| Volymen är satt till {{volume}} | ||||
| @@ -0,0 +1,8 @@ | ||||
| skillMetadata: | ||||
|   sections: | ||||
|   - name: Ducking | ||||
|     fields: | ||||
|     - name: ducking | ||||
|       type: checkbox | ||||
|       label: Duck while listening | ||||
|       value: "true" | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "set volume to 3", | ||||
|   "intent_type": "SetVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Level": "3" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "reset volume", | ||||
|   "intent_type": "UnmuteVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Unmute": "reset" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "update volume to 11", | ||||
|   "intent_type": "SetVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Level": "11" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "increase volume", | ||||
|   "intent_type": "IncreaseVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Increase": "increase" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "rise volume", | ||||
|   "intent_type": "IncreaseVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Increase": "rise" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "decrease volume", | ||||
|   "intent_type": "DecreaseVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Decrease": "decrease" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "reduce volume", | ||||
|   "intent_type": "DecreaseVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Decrease": "reduce" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "set volume to quiet", | ||||
|   "intent_type": "SetVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Level": "quiet" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "update volume to normal", | ||||
|   "intent_type": "SetVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Level": "normal" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "utterance": "change volume to loud", | ||||
|   "intent_type": "SetVolume", | ||||
|   "intent": { | ||||
|     "Volume": "volume", | ||||
|     "Level": "loud" | ||||
|   } | ||||
| } | ||||
| @@ -0,0 +1,5 @@ | ||||
| sænk | ||||
| reducere | ||||
| sænk | ||||
| sænk | ||||
| lavere | ||||
| @@ -0,0 +1,6 @@ | ||||
| hæv | ||||
| hæv | ||||
| boost | ||||
| øg | ||||
| skru op | ||||
| højere | ||||
| @@ -0,0 +1,15 @@ | ||||
| 0 | ||||
| 1 | ||||
| 2 | ||||
| 3 | ||||
| 4 | ||||
| 5 | ||||
| 6 | ||||
| 7 | ||||
| 8 | ||||
| 9 | ||||
| 10 | ||||
| 11 | ||||
| stille | ||||
| normal | ||||
| højt | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user