Choreographic Intent:

I created a pattern of oscillation to the main line of the song “I will always love you” by Whitney Houston. My main intent was to recreate the beat of this song using the sound of the servo motions.

Code:

print(“Starting servo_sweep script.”)

import math, time
import board
import pwmio

——————————————————————————–

class Servo():
def init(self, pin, pulse_rate=50):
self.pwm = pwmio.PWMOut(board.GP0, duty_cycle=0, frequency=pulse_rate)

    # Save the initialization arguments within the object for later reference.
    self.pin = pin
    self.pulse_rate = pulse_rate

    # Initialize the other state variables.
    self.target = None    # target angle; None indicates the "OFF" state
    self.debug = False    # flag to control print output

def write(self, angle):

    # calculate the desired pulse width in units of seconds
    if angle is None:
        pulse_width = 0.0
    else:
        pulse_width  = 0.001 + angle * (0.001 / 180.0)

    # calculate the duration in seconds of a single pulse cycle
    cycle_period = 1.0 / self.pulse_rate

    # calculate the desired ratio of pulse ON time to cycle duration
    duty_cycle   = pulse_width / cycle_period

    # convert the ratio into a 16-bit fixed point integer
    duty_fixed   = int(2**16 * duty_cycle)

    # limit the ratio range and apply to the hardware driver
    self.pwm.duty_cycle = min(max(duty_fixed, 0), 65535)

    # save the target value in the object attribute (i.e. variable)
    self.target = angle

    # if requested, print some diagnostics to the console
    if self.debug:
        print(f"Driving servo to angle {angle}")
        print(f" Pulse width {pulse_width} seconds")
        print(f" Duty cycle {duty_cycle}")
        print(f" Command value {self.pwm.duty_cycle}\n")

——————————————————————————–

def linear_move(servo, start, end, speed=60, update_rate=50):
# Calculate the number of seconds to wait between target updates to allow
# the motor to move.
# Units: seconds = 1.0 / (cycles/second)
interval = 1.0 / update_rate

# Compute the size of each step in degrees.
# Units:  degrees = (degrees/second) * second
step = speed * interval

# Output the start angle once before beginning the loop.  This guarantees at
# least one angle will be output even if the start and end are equal.
angle = start
servo.write(angle)

# Loop once for each incremental angle change.
while angle != end:
    time.sleep(interval)            # pause for the sampling interval

    # Update the target angle.  The positive and negative movement directions
    # are treated separately.
    if end >= start:
        angle += step;              # movement in the positive direction
        if angle > end:
            angle = end             # end at an exact position
    else:
        angle -= step               # movement in the negative direction
        if angle < end:
            angle = end             # end at an exact position

    servo.write(angle)              # update the hardware

——————————————————————————–

def ringing_move(servo, q_d, q=0.0, qd=0.0, k=4math.pimath.pi, b=2.0,
update_rate=50, duration=2.0, debug=False):

interval = 1.0 / update_rate

while duration &gt; 0.0:
    # Calculate the acceleration.
    #   qdd         acceleration in angles/sec^2
    #   k           spring constant relating the acceleration to the angular displacement
    #   b           damping constant relating the acceleration to velocity
    qdd = k * (q_d - q) - b * qd

    # integrate one time step using forward Euler integration
    q  += qd  * interval    # position changes in proportion to velocity
    qd += qdd * interval    # velocity changes in proportion to acceleration

    # update the servo command with the new angle
    servo.write(q)

    # print the output for plotting if requested
    if debug:
        print(q)


    time.sleep(interval)

    duration -= interval

——————————————————————————–

Create an object to represent a servo on the given hardware pin.

print(“Creating servo object.”)
servo = Servo(board.GP0)

print(“Starting main script.”)
while True:
# initial pause
time.sleep(2.0)

print("Starting ringing motions.")
#and
ringing_move(servo, 90.0, duration=1)
#i
ringing_move(servo, 45.0, duration=1)
#e
ringing_move(servo, 90.0, duration=1)
print("Starting linear motions.")
#i
linear_move(servo, 0.0, 180.0, speed=45)

#will
ringing_move(servo, 90.0, duration=1)
#al
ringing_move(servo, 45.0, duration=1)
#ways
linear_move(servo, 180.0, 0.0, speed=45)
#love
ringing_move(servo, 90.0, duration=1)
#you
linear_move(servo, 0.0, 180.0, speed=45)

# final pause
time.sleep(2.0)