Code Explanation
NeoPixel Initialization
The first thing the code does after imports is initialize the strand of 30 NeoPixels connected to GPIO pin D26. They are set to 0.1 brightness to go easy on the retinas but feel free to adjust if you have something to diffuse or obstruct direct vision of the LEDs.
Command Line Arguments
The script supports a number of command line arguments to control its behavior.
-
--model-arch: Controls which version of the Moonshine model is used. Smaller model versions can transcribe faster but are less likely to be accurate.-
5- Medium streaming (default) -
4- Small streaming -
2- Tiny streaming
-
-
--embedding-model: The name of the embedding model to use for intent recognition. The default isembeddinggemma-300mwhich uses Google's EmbeddingGemma model. -
--threshold: The similarity threshold for matching the trigger phrases as a decimal. The default is0.6. This refers to the confidence level output of the intent recognition model, higher value means more confidence that the intent of the input text matches the specified value.Â
Transcriber & Intent Recognition Models Setup
A TranscriptPrinter class is defined that extends Moonshine's TranscriptEventListener. The functions implemented within it simply print to the terminal as the transcription is happening. The most recently printed line is updated over time as the model works. This is not strictly necessary for voice control, but it is helpful for troubleshooting to be able to see what the model thinks it is hearing.
The models used for transcription and embedding are determined based on the CLI arguments documented above. The embedding model is used to initialize an instance of IntentRecognizer. The transcription model is used to create a MicTranscriber. The MicTranscriber has listeners added for both the transcript_printer and the intent_recognizer causing it to print as it works, and scan for the specified intent trigger phrases.
Voice Command Callbacks
Setting up voice commands is a two part process:
- Define a callback function that will get called and take the appropriate action when the voice command is heard.
- Call
intent_recognizer.register_intent()passing in the command string and the callback function.Â
Here is the relevant code for the disco party command. Inside the callback function it prints a message and sets the global variable run_disco_animation to True. This variable gets checked in the main loop to control whether the disco animation will run on the NeoPixels.
# Disco Party animation setup
disco_party = ColorCycle(pixels, speed=0.35, colors=[_[1] for _ in colors[:8]])
run_disco_animation = False
# ...
def on_disco_party(trigger: str, utterance: str, similarity: float):
"""
Intent trigger listener callback function for Disco Party command.
Enables the disco party animation boolean.
"""
print("###########################")
print(f"# {trigger} - {utterance} - {similarity}")
print("# Disco Party!")
print("###########################")
global run_disco_animation
run_disco_animation = True
# Register intents with their trigger phrases and handlers
intents = {
"disco party": on_disco_party,
}
# ...
for intent, handler in intents.items():
intent_recognizer.register_intent(intent, handler)
In the code for this project, the commands are first gathered into a dictionary variable intents. Then a for loop is used to iterate over them registering each with the intent_recognizer.
The disco party command is the only one that is hard-coded directly.
Dynamic Light Color Callbacks
All of the light color callbacks are created dynamically in order to reduce copy/pasted boilerplate code.
The color words and RGB values are first defined in a list of tuples.
colors = [
("red", (255, 0, 0)),
("blue", (0, 0, 255)),
("green", (0, 255, 0)),
("yellow", (255, 255, 0)),
("orange", (255, 95, 0)),
("pink", (255, 0, 255)),
("purple", (90, 0, 255)),
("turquoise", (0, 255, 255)),
("off", (0, 0, 0)),
("black", (0, 0, 0)),
]
The word "off" is registered as the color black (0, 0, 0) meaning it will turn the LEDs off.
The higher order function build_lights_color_callback_function() is defined to build the callback functions for each of the colors. In Python, functions can be treated as normal variables. This function defines a new function into a variable and then returns it. It takes a tuple argument containing color word and RGB value from list of colors and creates an appropriate callback function for the color specified.
def build_lights_color_callback_function(input_data):
"""
Given a tuple with color name, and RGB values like:
("red", (255, 0, 0))
Create and return an intent trigger callback function
that turns the NeoPixels the specified color.
"""
def lights_color_callback(trigger: str, utterance: str, similarity: float):
print("###########################")
print(f"# {trigger} - {utterance} - {similarity}")
print(f"# Turning lights {input_data[0]}")
print("###########################")
global run_disco_animation
run_disco_animation = False
pixels.fill(input_data[1])
pixels.show()
return lights_color_callback
A for loop is used to create entries in the intents dictionary for each of the colors. For each color, two entries are created: "lights [color_word]" and "[color_word] lights". During testing I found that some colors work better when they're said before "lights" and others work better the other way around. Defining commands for both allows for flexibility when speaking and increases the chances that intent recognizer will find a match on any phrases that have a similar meaning.
for color in colors:
intents[f"lights {color[0]}"] = build_lights_color_callback_function(color)
intents[f"{color[0]} lights"] = build_lights_color_callback_function(color)
The same for loop mentioned in the disco party command section also registers all of the light color commands since they're all in the same intents dictionary.
for intent, handler in intents.items():
intent_recognizer.register_intent(intent, handler)
Page last edited February 25, 2026
Text editor powered by tinymce.