Skip to content

← Creating

How Does a Computer Make a Sitar-Like Note Out of Noise?

Topic 34 · Grades 9–12 · Sound synthesis

You need lists and dictionaries (Grade 9); how computers store sound and pictures (Grade 7).

What this builds on from class
In this unit 8 sections
  1. Try it
  2. How it works
  3. Program it
  4. What your program shows
  5. Going further still
  6. Challenges
  7. Questions
  8. Where to go next

Tune a radio between two stations and you hear a hiss: noise, a jumble of every pitch at once with no note in it. Pluck a string on a sitar, a guitar or a sarangi and you hear a clear note that rings and slowly fades. They sound like opposites. In this topic you will turn the first into the second with a list of random numbers and one line of arithmetic.

The drawing below is a list of 80 random numbers, noise. The computer reads them out to the speaker, 16,000 a second. When it reaches the end of the list it goes back to the start, and as it reads each number it swaps it for the average of that number and the next one.

Try it

Pluck a string made of numbers

Press Pluck (turn your sound on, not too loud). Each pluck starts from new random numbers. Move Look at the list after to see what the averaging does to them.

the list after 0 passes (0.00 s)then round to the start againthe sound it makes0 s0.5 s1 s1.5 s2 s2.5 s
Averaging:
Pitch
201 Hz
Left after 1 second
20 %

Each round mixes every number with the next: the noise smooths into a wave, and the note mellows and fades.

Before you pluck it for the first time: the list starts as pure noise. What do you think you will hear?

Pick an answer to see why.

You need lists and dictionaries in Python (Grade 9) and how a computer stores sound as samples (Grade 7). Nothing about music is needed: what a note and a raag are is explained here.

Try it

Pluck the string a few times, then try these.

  • Averaging: None. Now each number is read out again unchanged every time round. You still hear a note, a harsh, buzzy one, and it never fades. Noise repeated over and over is a note. Its pitch is how many times a second the list comes round: 16,000 ÷ 80 = 200 times a second.
  • The list’s length. Make the list shorter and the note goes higher; make it longer and it goes lower. Watch the Pitch readout.
  • A little averaging (each number mostly itself, with a little of the next). The note stays bright for longer: its quick wiggles fade slowly. Half (each number swapped for the plain average of it and the next) mellows fastest, and its note fades sooner.
  • Look at the list after 0 passes, then a few, then many. The jagged noise smooths out into a gentle wave, and the wave shrinks.
  • Sitar’s bridge. A buzz that keeps coming back while the note rings. You will build this in Step 5, and see how far it is from a real sitar.

How it works

Two things happen together: going round the list makes a note, and the averaging shapes it into a string’s.

A pattern that repeats is a note

Your ear doesn’t hear numbers; it hears the air pushed back and forth. Any shape of wave at all, smooth or jagged, that repeats exactly 200 times a second sounds like a note with a pitch of 200 times a second. Scientists write “times a second” as hertz (Hz): 200 Hz.

So the length of the list sets the pitch: a list of n numbers, read at 16,000 a second, comes round 16,000 ÷ n times a second. That is why the noise, repeated, already sounds like a note. What it sounds like, its tone, comes from the shape that repeats.

Averaging smooths away the fast wiggles

Noise is a mixture of every kind of wiggle: slow ups and downs across the whole list, and quick zigzags from one number to the next. The averaging treats them very differently.

  • In a slow up and down, each number is nearly the same as the next. Their average is nearly the number itself: it hardly changes.
  • In a zigzag, up, down, up, down, each number is the opposite of the next. Their average is about zero: it almost vanishes in one pass.

This program puts smooth waves into a list of 80 numbers, with 1, 2, 4 and up to 40 ups and downs along it, and averages each one 100 times round, the way the string does:

import math n = 80for k in [1, 2, 4, 8, 20, 40]:    # a smooth wave that goes up and down k times along the list    wave = [math.sin(2 * math.pi * k * i / n) for i in range(n)]    for p in range(100):                      # 100 times round the list        wave = [(wave[i] + wave[(i + 1) % n]) / 2 for i in range(n)]    print(f'{k:2} ups and downs: {max(wave):.3f} of it left')

Output

 1 ups and downs: 0.926 of it left 2 ups and downs: 0.734 of it left 4 ups and downs: 0.290 of it left 8 ups and downs: 0.006 of it left20 ups and downs: 0.000 of it left40 ups and downs: 0.000 of it left

A wave with 2 ups and downs along the list goes up and down twice as often as the list comes round: it is a higher sound, an overtone of the note. Overtones with many ups and downs vanish quickly; the note itself, the one up and down, lasts. Anything that lets slow waves through and stops fast ones is called a low-pass filter, and averaging two neighbors is the simplest one there is.

That is what a real string does too. A plucked string starts with a sharp kink, full of high overtones that sound bright and twangy. They lose their energy fastest, so the sound turns mellow, and then the note itself dies away. The program never models a string, its tension or its weight; it only does the one thing that matters to the ear.

Program it

In Python, with your program’s sound played by the page. from sound import play gives you play(samples, rate): a list of numbers from −1 to 1, rate of them a second. (sound is this page’s own module; on your own computer you would need a sound library instead.) Press Run, then Play under the output. Nothing plays until you press it.

Step 1: a string in a list

The list ring is the string. here goes round and round it, using % to go back to 0 after the end. Each number is sent to the sound before it is swapped for the average of itself and the next.

import randomfrom sound import play RATE = 16000                 # samples a second def pluck(n, seconds):    noise = [random.uniform(-0.5, 0.5) for _ in range(n)]   # n numbers of noise    middle = sum(noise) / n    ring = [v - middle for v in noise]       # balanced round zero, like a string at rest    samples = []    for i in range(int(RATE * seconds)):        here = i % n                 # where we are in the list        after = (i + 1) % n          # the next place (after the end, back to 0)        samples.append(ring[here])        ring[here] = (ring[here] + ring[after]) / 2    return samples note = pluck(80, 2)print(len(note), 'samples')play(note, RATE)

Output

32000 samples

Look at the start of the wave under the output: jagged noise. Move the close-up along: by half a second it is a smooth wave that repeats. Change 80 to 40, Run, and Play: about an octave higher.

Why take middle away? The average of the noise isn’t exactly zero, and a string at rest is in the middle. A wave pushed to one side would waste the speaker’s movement.

Step 2: the sound remembers the string

There is a neater way to write the same thing. The number about to be read out was put into the ring exactly n samples ago, as the average of the two numbers read out n and n − 1 samples before. So the sound can be built from its own past, with no ring at all:

import randomfrom sound import play RATE = 16000 def pluck_round(n, seconds):    noise = [random.uniform(-0.5, 0.5) for _ in range(n)]    middle = sum(noise) / n    ring = [v - middle for v in noise]    samples = []    for i in range(int(RATE * seconds)):        here = i % n        after = (i + 1) % n        samples.append(ring[here])        ring[here] = (ring[here] + ring[after]) / 2    return samples def pluck(n, seconds):    noise = [random.uniform(-0.5, 0.5) for _ in range(n)]    middle = sum(noise) / n    samples = [v - middle for v in noise]    for i in range(n, int(RATE * seconds)):        samples.append((samples[i - n] + samples[i - n + 1]) / 2)    return samples random.seed(7)a = pluck_round(80, 2)random.seed(7)b = pluck(80, 2)print('The same sound?', a == b)

Output

The same sound? True

random.seed(7) makes both functions get the same random numbers, so their sounds can be compared. Every one of the 32,000 samples is the same. The new pluck is the one Karplus and Strong wrote down: each sample is the average of the two samples n and n − 1 places back. The steps after this use it.

Step 3: measure the pitch

Is the pitch 16,000 ÷ n, or 16,000 ÷ (n − ½)? Count it. Each time the wave rises through its middle, it has been round once. Count those rises in one second of the note, after it has settled:

import random RATE = 16000 def pluck(n, seconds):    noise = [random.uniform(-0.5, 0.5) for _ in range(n)]    middle = sum(noise) / n    samples = [v - middle for v in noise]    for i in range(n, int(RATE * seconds)):        samples.append((samples[i - n] + samples[i - n + 1]) / 2)    return samples def count_rises(samples, start, end):    """How many times the wave rises through its middle between two times (in seconds)."""    part = samples[int(start * RATE):int(end * RATE)]    middle = sum(part) / len(part)    count = 0    for i in range(1, len(part)):        if part[i - 1] < middle <= part[i]:            count = count + 1    return count random.seed(1)n = 40note = pluck(n, 1.5)print(f'Rate ÷ length:       {RATE / n:.1f} Hz')print(f'Rate ÷ (length − ½): {RATE / (n - 0.5):.1f} Hz')print(f'Counted in 1 second: {count_rises(note, 0.5, 1.5)}')

Output

Rate ÷ length:       400.0 HzRate ÷ (length − ½): 405.1 HzCounted in 1 second: 405

405, not 400: the half matters. A musician can hear the difference: 405 is about a fifth of the way from 400 to the next note of a piano.

The program counts from half a second to a second and a half, because at the start the noise still has many quick wiggles that cross the middle several times in one round.

Step 4: a raag, plucked

A raag is the set of notes, and the rules for moving among them, that a piece of Indian and Nepali classical music keeps to. Bhupali (Bhoopali), usually played early in the night, uses only five notes: Sa Re Ga Pa Dha. Each is a fixed fraction of Sa’s pitch; with the whole-number fractions many musicians tune to, Re is 9⁄8 of Sa, Ga 5⁄4, Pa 3⁄2 and Dha 5⁄3, and the next Sa up (Sa′) is twice Sa.

To play a note of pitch p, the list needs about 16,000 ÷ p + ½ numbers. A dictionary keeps each note’s length by its name:

import randomfrom sound import play RATE = 16000 def pluck(n, seconds, damp=0.996):    noise = [random.uniform(-0.5, 0.5) for _ in range(n)]    middle = sum(noise) / n    samples = [v - middle for v in noise]    for i in range(n, int(RATE * seconds)):        samples.append(damp * (samples[i - n] + samples[i - n + 1]) / 2)    return samples # Raag Bhupali's notes, as fractions of Sa's pitchBHUPALI = [('Sa', 1), ('Re', 9 / 8), ('Ga', 5 / 4), ('Pa', 3 / 2), ('Dha', 5 / 3), ("Sa'", 2)]SA = 100                                   # Sa's list lengthsa_pitch = RATE / (SA - 0.5) lengths = {}for name, ratio in BHUPALI:    pitch = sa_pitch * ratio               # the pitch this note should have    n = round(RATE / pitch + 0.5)          # the nearest list length    lengths[name] = n    print(f'{name:4} should be {pitch:5.1f} Hz: a list of {n} gives {RATE / (n - 0.5):5.1f} Hz') up = ['Sa', 'Re', 'Ga', 'Pa', 'Dha', "Sa'"]tune = []for name in up + up[::-1]:    tune = tune + pluck(lengths[name], 0.38)print(len(tune) / RATE, 'seconds')play(tune, RATE)

Output

Sa   should be 160.8 Hz: a list of 100 gives 160.8 HzRe   should be 180.9 Hz: a list of 89 gives 180.8 HzGa   should be 201.0 Hz: a list of 80 gives 201.3 HzPa   should be 241.2 Hz: a list of 67 gives 240.6 HzDha  should be 268.0 Hz: a list of 60 gives 268.9 HzSa'  should be 321.6 Hz: a list of 50 gives 323.2 Hz4.56 seconds

Play it: the five notes up to Sa′ and back down, the way a student sings a raag’s scale. A list’s length must be a whole number, so some notes come out a little off: Sa′ is 323.2 Hz instead of 321.6, about a tenth of the way to the next note of a piano. Challenge ★★★ finds a Sa whose list gets every note closer.

damp is new. Each time round, the average is made 0.4% smaller: a real string also loses energy to the air and the instrument’s body, and without it the low notes ring on much longer than a string’s would.

Step 5: a sitar-like note

Pluck a sitar and it doesn’t sound like a guitar. Two things make the difference.

A sitar lying on a carpet: a long neck with curved metal frets, a row of small pegs down its side, large pegs at the top, and a round body carved with flowers, with a wide dark bridge in the middle
Figure 34.1 A sitar. The large pegs near the top hold the played strings; the row of small pegs down the side holds the sympathetic strings, which run under the frets. Photo: Jan Kraus, Public domain
  • The bridge. On a guitar, each string leaves the bridge from a sharp edge. A sitar’s main bridge is wide, with a gently curved top that the string lies along and lifts off as it swings. Shaping that curve is called jawari, and it is skilled work. As the string vibrates, the point where it leaves the bridge moves, so the string keeps touching the bridge and lifting off it in every vibration. That is the sitar’s buzzing, bright, singing tone, and it stays bright while the note rings.
  • The sympathetic strings (taraf). Under the frets run about a dozen extra strings (13 on many sitars) that nobody plucks. The player retunes them for each raag, to its notes. When a played note matches one of them, that string starts to ring by itself, softly, and keeps ringing: a halo round the note.
A close-up of a sitar’s main bridge: a wide, flat-topped block of dark wood, with the played strings lying across its top, where they have left shiny marks
Figure 34.2 The main bridge, made of ebony. The shiny marks on its top, left by the first two strings, show where they lie along the curve. Photo: Jan Kraus, Public domain

A program can imitate both, simply. For the bridge: whenever the wave is below the middle line (the string down on the bridge), give it a small random rattle, in proportion to how far down it is. For the taraf: five more strings, still to start with, one for each note of Bhupali, each shaken a tiny amount by the main string’s sound.

import randomfrom sound import play RATE = 16000 def pluck(n, seconds, buzz=0.0, damp=0.996):    noise = [random.uniform(-0.5, 0.5) for _ in range(n)]    middle = sum(noise) / n    samples = [v - middle for v in noise]    for i in range(n, int(RATE * seconds)):        new = damp * (samples[i - n] + samples[i - n + 1]) / 2        if samples[i - n] < 0:                     # down on the bridge: it rattles            new = new + buzz * samples[i - n] * random.uniform(-1, 1)        samples.append(new)    return samples def sympathy(main, lengths, pull=0.002, damp=0.999):    """Strings that nobody plucks, shaken a little by the main string's sound."""    strings = []    for n in lengths:        s = [0.0] * n                              # still, to start with        for i in range(len(main)):            s.append(damp * (s[i] + s[i + 1]) / 2 + pull * main[i])        strings.append(s[n:])    return strings def loudness(samples):    end = samples[-RATE // 2:]                     # the last half second    return (sum(v * v for v in end) / len(end)) ** 0.5 random.seed(3)TARAF = {'Sa': 100, 'Re': 89, 'Ga': 80, 'Pa': 67, 'Dha': 60}print('plucked', ''.join(f'{name:>5}' for name in TARAF))for played, n in [('Sa', 100), ('Pa', 67), ('Ma', 75)]:    main = pluck(n, 1.5)    rings = sympathy(main, TARAF.values())    print(f'{played:7}', ''.join(f'{1000 * loudness(s):5.1f}' for s in rings)) plain = pluck(100, 2)main = pluck(100, 2, buzz=0.4)rings = sympathy(main, TARAF.values())sitar = [0.6 * (m + sum(r[i] for r in rings)) for i, m in enumerate(main)]print('loudest sample:', round(max(abs(v) for v in sitar), 2))play(plain, RATE)play(sitar, RATE)

Output

plucked    Sa   Re   Ga   Pa  DhaSa       13.6  0.4  0.5  0.9  0.6Pa        1.4  0.6  0.6 12.2  0.8Ma        0.7  0.7  0.7  0.9  0.9loudest sample: 0.32

Play the two sounds one after the other: a plain plucked string, then the same Sa with the bridge’s buzz and the taraf.

Each row is one pluck, and each number how loudly a sympathetic string is ringing in the last half second of it (in thousandths). The numbers show the taraf working. Pluck Sa, and the Sa string rings 15 to 30 times as loud as the others; pluck Pa, and the Pa string answers. Pluck Ma, a note that isn’t in Bhupali, and none of them answers much. Nobody told the program which string should ring: a string driven at its own pitch builds up, a little more each time round, the way a swing goes higher when you push it at the right moment. That is resonance.

The sounds are added together, and the sum could go past 1, so the program makes it quieter (0.6 ×) and checks the loudest sample.

What your program shows

  • Noise becomes a note as soon as it repeats, at 16,000 ÷ (n − ½) times a second: you counted 405 for a list of 40.
  • Averaging is a low-pass filter: the quick wiggles die in a few rounds and the slow ones last, so the sound mellows and fades like a string’s.
  • Whole-number lengths can’t put every note of a raag exactly in tune.
  • Sympathetic strings resonate by themselves: the taraf string tuned to the plucked note rang 13.6 against 0.4 to 0.9 for the rest.

That is why the trick mattered. One addition and one halving a sample, and a cheap chip of the 1970s played notes that sounded like a guitar. And it was one of the first steps towards a new way of making sound: from a model of the instrument itself (see “Modeling the bridge”, below).

Going further still

A drum from the same loop

Karplus and Strong also found a drum. Toss a coin for each new sample, and on heads turn it upside down:

What it can’t do

The program buzzes and resonates, but no one would mistake it for a sitar. A real sitar has much more:

  • A body. The string’s sound comes out through a hollow gourd and a wooden top, which make some pitches louder than others. The program sends the string straight to the speaker.
  • The real bridge. The jawari’s buzz comes from the string wrapping onto the curve and lifting off it, and it changes as the swing dies away. In 1982 the mathematicians Burridge, Kappraff and Morshedi worked out the motion of a string against a curved obstacle like this; it is much harder than averaging two numbers. The program’s random rattle only gives a buzz that comes once in each vibration.
  • The player. The sitar is played with a wire pick (mizraab) on the finger, and its notes slide from one to the next (meend) as the player pulls the string sideways across a fret. Every pluck of the program is the same kind of pluck.
  • Exact tuning. Lengths are whole numbers. Jaffe and Smith’s fix was a filter that delays the sound by a fraction of a sample.

Challenges

Three challenges, each checked on the page: write your function in the program above the checker (press Edit), then press Check my program. The examples show what your function should give; there are more tests you can’t see.

★ How long a list?

Write list_length(pitch): the length of list that plays nearest to pitch (in Hz) at 16,000 samples a second. Use the exact rule from Step 3: a list of n plays at 16,000 ÷ (n − ½), so n is about 16,000 ÷ pitch + ½. Round to the nearest whole number.

RATE = 16000 def list_length(pitch):    # Give back the whole-number list length that plays nearest to pitch.    return 0 print(list_length(160.8))

Try it

★ How long a list?

  1. Sa in Step 4 list_length(160.8) Should give 100 Yours —
  2. A high A list_length(440) Should give 37 Yours —
  3. Middle C on a piano list_length(261.6) Should give 62 Yours —
Tests passed
— / 10
Challenge
★ solved

Write your list_length in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Answer

★★ How long does it ring?

Write ring_time(samples, rate): how many seconds the sound rings. Find the loudest sample’s size (ignoring its sign), then the last sample whose size is at least a thousandth of that. Give back its time: its place in the list divided by rate (the first sample is at time 0). If every sample is 0, give back 0.

def ring_time(samples, rate):    # Give back the time of the last sample at least 1/1000 as big as the loudest.    return 0 print(ring_time([0.5, -0.4, 0.2, -0.01, 0.0004, 0.0001], 10))

Try it

★★ How long does it ring?

  1. The example ring_time([0.5, -0.4, 0.2, -0.01, 0.0004, 0.0001], 10) Should give 0.3 Yours —
  2. Loud to the end ring_time([0.1, -0.2, 0.3, -0.2, 0.1], 100) Should give 0.04 Yours —
  3. The loudest isn't first ring_time([0.01, -0.8, 0.3, 0.0009, -0.0007, 0.0002], 1000) Should give 0.003 Yours —
Tests passed
— / 8
Challenge
★★ solved

Write your ring_time in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Answer

★★★ The best Sa

In Step 4, a Sa list of 100 put Sa′ about a tenth of a note out of tune. Some lengths do better. Write best_sa(ratios, low, high). Each note’s fraction of Sa comes as a pair [top, bottom]: [9, 8] for Re’s 9⁄8. Try every Sa length from low to high; for each, find every note’s list length (as in Step 4) and how far out of tune it is in cents, abs(1200 * math.log2(got / wanted)) (100 cents is one step of a piano). A Sa length is as good as its worst note. Give back the Sa length whose worst note is closest to in tune; if two are equally good, the shorter.

import math RATE = 16000 def best_sa(ratios, low, high):    # Try each Sa length from low to high; give back the one whose worst note is best.    return low print(best_sa([[1, 1], [9, 8], [5, 4], [3, 2], [5, 3], [2, 1]], 90, 110))

Try it

★★★ The best Sa

  1. Bhupali, lists of 90 to 110 best_sa([[1, 1], [9, 8], [5, 4], [3, 2], [5, 3], [2, 1]], 90, 110) Should give 106 Yours —
  2. Only Sa and Sa' best_sa([[1, 1], [2, 1]], 50, 60) Should give 60 Yours —
Tests passed
— / 7
Challenge
★★★ solved

Write your best_sa in the program above (Edit), then check it.

A test your function gets wrong, anything it prints, and any error appear here.

Hint
Another hint
Answer

Questions

A list of 50 numbers is read at 16,000 samples a second with averaging. About what pitch do you hear?

Pick an answer to see why.

Why does the note get mellower as it rings?

Pick an answer to see why.

a.

Explain why a list of random numbers, read round and round with no averaging, sounds like a note and not like a hiss.

Answer
b.

A friend’s pluck uses (samples[i - n] + samples[i - n + 1]) / 2.1 and their note dies much faster than yours. Why? What else could they have changed to make it ring longer instead?

Answer
c.

In Step 5, the Sa sympathetic string rang loudly when Sa was plucked and hardly at all when Ma was. Explain why, using the word resonance.

Answer
d.

Give two reasons the program’s sitar-like note is not a real sitar, and one way a real sitar player changes the sound that the program can’t.

Answer

Where to go next