This Python + Pygame program draws the trace of 4 decaying sine waves, 2 per axis, with rainbow colours. It generates a sequence of random harmonographs.

A harmonograph is a mechanical device typically seen in science museums, that has two or more pendulae with attached pens, that can draw on a sheet of paper. The pendulae are set in motion and the pen draws pretty patterns on the paper. This is easily simulated in a computer program, by plotting orthogonal sine waves acting together on a drawing point. This generates Lissajous figures, which are decayed to make a pleasing nesting of ‘parallel’ lines, like you might see on bank notes.

It’s fast, and can be set to go much faster (or slower) if you want. Tip: set the display window to fullscreen. MIT license; download from GitHub

#!/usr/bin/python
'''    Spectral Harmonographs   Copyright 2014 Alan Richmond (Tuxar.uk)
'''
print("Quit: q key, Screenshot: spacebar")

import pygame, sys, random as r
from pygame.locals import *
from math import pi, sin, cos, exp
#                        EDIT THESE:
width,height=1280,720       # YouTube HD
width,height=1920,1080      # my left monitor
width,height=1280,1024      # my right monitor
width,height=1680,1050      # Lucy's monitor
width,height=1200,800
#width,height=2560,1440      # YT channel art
dd=0.99995                  # decay factor
dt=0.02                     # time increment
speed=200                   # yes, speed
hui=57*2                    # Hue increment
hue,sat,val,aaa=0,100,100,0
sd=0.005                    # frequency spread (from integer)
mx=4                        # max range for amplitudes and frequencies
print("Hit SPACE to save")

def check_event():
    global save
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
        elif event.type == KEYDOWN and event.key == K_q:
            sys.exit()
        elif event.type == KEYDOWN and event.key == K_SPACE:
            save=True
            print("Saving when finished...")

def scale(length):
    while True:
        a1,a2=r.randint(-mx,mx),r.randint(-mx,mx)
        max=abs(a1)+abs(a2)
        if max > 0: break
    return a1,a2,length/(2*max)
steps=0
pygame.init()
pygame.event.set_allowed([QUIT, KEYDOWN])
screen = pygame.display.set_mode((width,height),DOUBLEBUF)
screen.set_alpha(None)
#fg=pygame.Color(0,0,0,0)
#fg=(0,0,0)
fg=(255,255,255)
save=False
while True:
#   Amplitudes and scales
    ax1,ax2,xscale=scale(width)
    ay1,ay2,yscale=scale(height)
    fx1, fx2 =  r.randint(1,mx) + r.gauss(0,sd), r.randint(1,mx) + r.gauss(0,sd)
    fy1, fy2 =  r.randint(1,mx) + r.gauss(0,sd), r.randint(1,mx) + r.gauss(0,sd)
    px1, px2 =  r.uniform(0,2*pi), r.uniform(0,2*pi)
    py1, py2 =  r.uniform(0,2*pi), r.uniform(0,2*pi)
    print(ax1,ax2,ay1,ay2)
    print(fx1,fx2,fy1,fy2)
    print(px1,px2,py1,py2)

    dec=1.0
    t=0.0                       # angle for sin
    first=True
    while dec > 0.015:
                                # calculate next x,y point along line
        x = xscale * dec * (ax1*sin(t * fx1 + px1) + ax2*sin(t * fx2 + px2)) + width/2
        y = yscale * dec * (ay1*sin(t * fy1 + py1) + ay2*sin(t * fy2 + py2)) + height/2
        dec*=dd                 # decay
        if not first:           # ignore any complaint about prev_x,y being undefined
#            fg.hsva=(hue,sat,val,aaa)
#            hue = (hue + dt*hui) % 360      # cycle hue
            pygame.draw.aaline(screen, fg, (x, y), (prev_x, prev_y), 1)
        else:
            first=False

        prev_x = x              # save x,y for next line segment start
        prev_y = y
        if steps%speed==0: pygame.display.update()
        steps+=1
        t+=dt                   # increment angle for sin
        check_event()

    if save:                    # parameters are encoded into filename
        pars='shg-{0}_{1}-{2}_{3}-{4}_{5}'.format(ax1,ax2,fx1,fx2,px1,px2)
        pygame.image.save(screen, pars+'.jpg')
        print("Saved as "+pars+'.jpg')
        save=False

    screen.fill((0,0,0))
#    screen.fill((255,255,255))

You may need to install python and/or pygame, e.g. on Ubuntu/Debian style linuxes:

sudo apt-get install pygame
sudo apt-get install python
Go to the directory you saved it in and make it executable, then run it:

chmod +x harmonograph.py
./harmonograph.py
Set the display window to fullscreen (right click on title bar, maybe it’s under More Actions?).

[welcomewikilite wikiurl=”https://en.wikipedia.org/wiki/Harmonograph” sections=”Short description” settings=””]

3 thoughts on “Spectral Harmonographs”

    1. The wordpress plugin for Python has a habit of changing ‘>’ to ‘&’…
      I’ve changed the code, and another one further down, please try again
      and forgive my late reply. Sorry.

  1. Hi there,

    just stumbled across this page here and wanted to mention something. The code above is working….partially. All lines appear white. However, the code on the Github is missing the parentesis on all print commands, after adding these I was able to get the images as shown here (with colored lines).

    On a first glance I couldn’t find the mistake for the missing color in the code on this page, but I’ll have a closer look soon.

    Best regards and thank you for this beautiful example
    – Chris

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.