From asi
Provides BOM, schematics, and code for building a $68 wobbling duckoid robot that quacks with piezo and composes xenharmonic scales on ESP32-S3.
npx claudepluginhub plurigrid/asi --plugin asiThis skill uses the workspace's default tool permissions.
**Trit**: +1 (PLUS - generative/constructive)
Explains Coecke's Quantum Guitar: quantizes guitar strings by associating qubits to playable states, uses ZX-calculus notation, integrates Moth Actias synth with foot controllers for classical-quantum transitions.
Generates high-quality 3D human and humanoid robot motions from text prompts using Kimodo kinematic diffusion model with constraints like keyframes and end-effectors. Exports to NPZ, MuJoCo qpos, or AMASS.
Provides patterns for electric motor control including FOC, stepper motor control, encoder interfaces, current sensing, and power electronics for BLDC, PMSM, DC brushed, stepper applications.
Share bugs, ideas, or general feedback.
Trit: +1 (PLUS - generative/constructive) Color: #1FC4E0 (Cyan) URI: skill://quackbot-duckoid#1FC4E0
A mechanic wobbling duckoid robot that:
βββββββββ
β± O O β² β LED eyes ($0.50)
β ββββββ β β Servo beak ($2)
β )))) β β Piezo speaker ($1)
β²________β±
β
ββββββββββ΄βββββββββ
β± β²
β βββββββββββ β
β β ESP32-S3β β β MCU ($8)
Wing βββΆβ β +IMU β ββββ Wing
($2ea) β βββββββββββ β ($2ea)
β βββββββββ β
β β LiPo β β β Battery ($12)
β² βββββββββ β±
ββββββββββ¬βββββββββ
β
ββββββββββ§βββββββββ
β WOBBLE BASE β
β βββββ βββββ β β 2x MG996R ($10)
β β S βββββ S β β
β βββββ βββββ β
ββββββββββ€βββββββββ
β―βββββ΄βββββ― β Rubber feet ($2)
| Component | Qty | Unit Cost | Total |
|---|---|---|---|
| Head | $15 | ||
| SG90 Micro Servo (beak) | 1 | $2 | $2 |
| Piezo Buzzer/Speaker | 1 | $1 | $1 |
| 5mm LED (eyes) | 2 | $0.25 | $0.50 |
| MPU6050 IMU | 1 | $3 | $3 |
| 3D Print (head shell) | 1 | $8.50 | $8.50 |
| Body | $25 | ||
| ESP32-S3 DevKit | 1 | $8 | $8 |
| PAM8403 Amplifier | 1 | $1 | $1 |
| 2S LiPo 1000mAh | 1 | $12 | $12 |
| PCA9685 PWM Driver | 1 | $4 | $4 |
| Wobble Base | $20 | ||
| MG996R Servo | 2 | $5 | $10 |
| 3D Print (gimbal) | 1 | $5 | $5 |
| Rubber Feet | 2 | $1 | $2 |
| Brass Counterweight | 1 | $3 | $3 |
| Wings | $8 | ||
| SG90 Micro Servo | 2 | $2 | $4 |
| 3D Print (wings) | 2 | $2 | $4 |
| TOTAL | $68 |
QuackBot composes using xenharmonic scales that are not 12-TET:
Ratio: 1 25/21 9/7 7/5 5/3 9/5 15/7 7/3 25/9 3 ...
Step: 0 1 2 3 4 5 6 7 8 9 ...
Cents: 0 133.2 266.9 400.1 533.8 666.9 800.1 933.1 1066.9 1200
JUST_RATIOS = {
'C': 1/1, # Unison
'D': 9/8, # Major second
'E': 5/4, # Major third
'F': 4/3, # Perfect fourth
'G': 3/2, # Perfect fifth
'A': 5/3, # Major sixth
'B': 15/8, # Major seventh
}
CARLOS_ALPHA = 78.0 # cents per step (15.385 steps/octave)
CARLOS_BETA = 63.8 # cents per step (18.809 steps/octave)
CARLOS_GAMMA = 35.1 # cents per step (34.188 steps/octave)
A duck-optimized scale based on duck vocalization formants:
QUACK_SCALE = [
1.0, # Root quack
1.059, # Minor second quack
1.189, # Minor third quack
1.335, # Fourth quack
1.498, # Fifth quack
1.682, # Sixth quack
1.888, # Seventh quack
]
# Formant frequencies: F1=1200Hz, F2=2400Hz, F3=3600Hz
The 2-DOF gimbal creates a chaotic wobble pattern using coupled oscillators:
import numpy as np
class WobbleDynamics:
"""Kuramoto-style coupled oscillator for duck wobble."""
def __init__(self, natural_freq=2.0, coupling=0.5):
self.omega = natural_freq # rad/s
self.K = coupling
self.theta = [0.0, np.pi/4] # pitch, roll phases
def step(self, dt: float, imu_feedback: tuple) -> tuple:
"""Compute next wobble angles."""
pitch_accel, roll_accel = imu_feedback
# Kuramoto coupling
phase_diff = self.theta[1] - self.theta[0]
# Update phases with coupling and feedback
self.theta[0] += dt * (
self.omega +
self.K * np.sin(phase_diff) +
0.1 * pitch_accel
)
self.theta[1] += dt * (
self.omega * 1.1 + # Slight detuning
self.K * np.sin(-phase_diff) +
0.1 * roll_accel
)
# Convert to servo angles (Β±30Β°)
pitch_angle = 30 * np.sin(self.theta[0])
roll_angle = 30 * np.sin(self.theta[1])
return pitch_angle, roll_angle
class QuackSynthesizer:
"""Duck vocalization synthesizer."""
FORMANTS = [1200, 2400, 3600] # Hz
def quack(self, duration_ms: int = 200, pitch_shift: float = 1.0):
"""Generate a quack waveform."""
sr = 16000
t = np.linspace(0, duration_ms/1000, int(sr * duration_ms/1000))
# Fundamental with pitch shift
f0 = 220 * pitch_shift
# Sum formants
signal = np.zeros_like(t)
for i, formant in enumerate(self.FORMANTS):
amp = 1.0 / (i + 1) # Decreasing amplitude
signal += amp * np.sin(2 * np.pi * formant * pitch_shift * t)
# Apply envelope (sharp attack, quick decay)
envelope = np.exp(-t * 20) * (1 - np.exp(-t * 100))
return (signal * envelope * 32767).astype(np.int16)
def compose_nonstandard(self, scale: list, pattern: list) -> list:
"""Compose using nonstandard scale."""
quacks = []
for note_idx, duration in pattern:
pitch = scale[note_idx % len(scale)]
quacks.append(self.quack(duration, pitch))
return quacks
<mujoco model="quackbot">
<compiler angle="radian" meshdir="meshes"/>
<default>
<joint damping="0.1" armature="0.01"/>
<geom friction="0.8 0.02 0.01"/>
</default>
<asset>
<mesh name="body" file="duck_body.stl"/>
<mesh name="head" file="duck_head.stl"/>
<mesh name="beak" file="duck_beak.stl"/>
<mesh name="wing" file="duck_wing.stl"/>
</asset>
<worldbody>
<!-- Wobble Base -->
<body name="base" pos="0 0 0.05">
<geom type="cylinder" size="0.08 0.02" rgba="0.2 0.2 0.2 1"/>
<!-- Pitch Joint -->
<body name="pitch_link" pos="0 0 0.03">
<joint name="pitch" type="hinge" axis="0 1 0" range="-0.5 0.5"/>
<!-- Roll Joint -->
<body name="roll_link" pos="0 0 0.02">
<joint name="roll" type="hinge" axis="1 0 0" range="-0.5 0.5"/>
<!-- Duck Body -->
<body name="body" pos="0 0 0.08">
<geom type="mesh" mesh="body" rgba="1 0.9 0 1"/>
<inertial pos="0 0 0" mass="0.3" diaginertia="0.001 0.001 0.001"/>
<!-- Head -->
<body name="head" pos="0 0.06 0.05">
<geom type="mesh" mesh="head" rgba="1 0.9 0 1"/>
<!-- Beak -->
<body name="beak" pos="0 0.03 0">
<joint name="beak" type="hinge" axis="1 0 0" range="0 0.3"/>
<geom type="mesh" mesh="beak" rgba="1 0.5 0 1"/>
</body>
</body>
<!-- Left Wing -->
<body name="wing_l" pos="-0.06 0 0.02">
<joint name="wing_l" type="hinge" axis="0 1 0" range="-0.5 0.5"/>
<geom type="mesh" mesh="wing" rgba="1 0.9 0 1"/>
</body>
<!-- Right Wing -->
<body name="wing_r" pos="0.06 0 0.02">
<joint name="wing_r" type="hinge" axis="0 1 0" range="-0.5 0.5"/>
<geom type="mesh" mesh="wing" rgba="1 0.9 0 1" euler="0 0 3.14"/>
</body>
</body>
</body>
</body>
</body>
</worldbody>
<actuator>
<position name="pitch_servo" joint="pitch" kp="50"/>
<position name="roll_servo" joint="roll" kp="50"/>
<position name="beak_servo" joint="beak" kp="20"/>
<position name="wing_l_servo" joint="wing_l" kp="10"/>
<position name="wing_r_servo" joint="wing_r" kp="10"/>
</actuator>
</mujoco>
#include <ESP32Servo.h>
#include <driver/i2s.h>
#include <MPU6050.h>
// Servos
Servo pitchServo, rollServo, beakServo, wingLServo, wingRServo;
// IMU
MPU6050 imu;
// Wobble state
float theta[2] = {0, M_PI/4};
const float omega = 2.0;
const float K = 0.5;
void setup() {
// Attach servos
pitchServo.attach(13);
rollServo.attach(12);
beakServo.attach(14);
wingLServo.attach(27);
wingRServo.attach(26);
// Init IMU
Wire.begin();
imu.initialize();
// Init I2S for audio
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX,
.sample_rate = 16000,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.dma_buf_count = 8,
.dma_buf_len = 64,
};
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
}
void loop() {
// Read IMU
int16_t ax, ay, az, gx, gy, gz;
imu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
// Wobble dynamics (Kuramoto)
float dt = 0.02;
float phase_diff = theta[1] - theta[0];
theta[0] += dt * (omega + K * sin(phase_diff) + 0.0001 * ax);
theta[1] += dt * (omega * 1.1 + K * sin(-phase_diff) + 0.0001 * ay);
// Servo angles
int pitch = 90 + 30 * sin(theta[0]);
int roll = 90 + 30 * sin(theta[1]);
pitchServo.write(pitch);
rollServo.write(roll);
// Occasional quack
if (random(100) < 2) {
quack();
}
// Wing flap (sync with wobble)
wingLServo.write(90 + 20 * sin(theta[0] * 2));
wingRServo.write(90 - 20 * sin(theta[0] * 2));
delay(20);
}
void quack() {
// Generate quack waveform
int16_t buffer[800];
for (int i = 0; i < 800; i++) {
float t = i / 16000.0;
float env = exp(-t * 20) * (1 - exp(-t * 100));
float sig = sin(2 * M_PI * 1200 * t) +
0.5 * sin(2 * M_PI * 2400 * t) +
0.25 * sin(2 * M_PI * 3600 * t);
buffer[i] = (int16_t)(sig * env * 16000);
}
// Beak animation
beakServo.write(120);
size_t bytes_written;
i2s_write(I2S_NUM_0, buffer, sizeof(buffer), &bytes_written, portMAX_DELAY);
beakServo.write(90);
}
quackbot-duckoid (+1) β wobble-dynamics (0) β nonstandard-scales (+1) = needs -1
quackbot-duckoid (+1) β topos-of-music (-1) β wobble-dynamics (0) = 0 β
quackbot-duckoid (+1) β ksim-rl (-1) β mujoco-scenes (0) = 0 β
topos-of-music (-1): Mazzola's mathematical music theorywobble-dynamics (0): Kuramoto oscillators for motionnonstandard-scales (+1): Xenharmonic scale generationksim-rl (-1): RL training for behaviorscatsharp-sonification (0): GF(3) color β sound mappingMinimum viable BOM: $52
@article{bohlen1978,
title={13 Tonstufen in der Duodezime},
author={Bohlen, Heinz},
journal={Acustica},
year={1978}
}
@book{carlos1987,
title={Tuning: At the Crossroads},
author={Carlos, Wendy},
year={1987}
}
@article{kuramoto1975,
title={Self-entrainment of a population of coupled oscillators},
author={Kuramoto, Yoshiki},
journal={Lecture Notes in Physics},
year={1975}
}