Just one note - musical instrument synthesizing

11

8

Statement

The task is to synthesize sound (one note played) of some musical instrument (of your choice) using function in some general purpose programming language (of your choice).

There are two goals:

  • Quality of the resulting sound. It should resemble the real instrument as fine as possible;
  • Minimality. Keeping the code under 1500 bytes is adviced (less if there's only basic sound generation).

Only generation function need to be provided, boilerplate is not counted for the score.

Unfortunately no score can be calculated for the sound fidelity, so there can't be strict rules.

Rules:

  • No dependance on sample libraries, specialized music generation things;
  • No downloading from network or trying to use microphone or audio card's MIDI or something too external like this;
  • The code size measure unit is bytes. File can get created in current directory. Pre-existing files (coefficient tables, etc) may exist, but their content is added to the score + they must by opened by name.
  • The boilerplate code (not counted to score) receives array (list) of signed integers and only deals with outputting them.
  • Output format is signed little endian 16-bit words, 44100 samples per seconds, with optional WAV header. No trying to output compressed audio instead of plain wav;
  • Please choose different instruments for synthesizing (or other quality vs code size category for the instrument); but don't initially tell what are you simulating - let other users to guess in comments;
  • Electronic instruments are discouraged;
  • Drum is an instrument. Human voice is an instrument.

Boilerplates

Here are boilerplates for some languages. You can write similar boiler plate for your language as well. Commented out "g" function is just for a demo (1 second 440 Hz sine tone).

C:

//#!/usr/bin/tcc -run
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

/*
void g(signed short *array, int* length) {
    *length = 44100;
    int i;
    for(i=0; i<44100; ++i) array[i]=10000*sin(i*2.0*3.14159265358979323*440.0/44100.0);
}
*/

// define your g here

signed short array[44100*100];
int main(int argc, char* argv[]) {
    int size=0;
    memset(array,0,sizeof array);
    // i(array); // you may uncomment and implement some initialization
    g(array, &size);
    fwrite("RIFFH\x00\x00\x00WAVEfmt\x20\x12\x00\x00\x00\x01\x00\x01\x00\x44\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00\x00\x00LIST\x1a\x00\x00\x00INFOISFT\x0e\x00\x00\x00GolfNote\0\0\0\0\0\0data\x00\xff\xff\xff", 1, 80, stdout);
    fwrite(array, 1, size*sizeof(signed short), stdout);
    return 0;
}

Python 2:

#!/usr/bin/env python
import os
import re
import sys
import math
import struct
import array


#def g():
#    return [int(10000*math.sin(1.0*i*2*3.141592654*440.0/44100.0)) for i in xrange(0,44100)]

# define your g here


sys.stdout.write("RIFFH\x00\x00\x00WAVEfmt\x20\x12\x00\x00\x00\x01\x00\x01\x00\x44\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00\x00\x00LIST\x1a\x00\x00\x00INFOISFT\x0e\x00\x00\x00GolfNotePy\0\0\0\0data\x00\xff\xff\xff");
array.array("h", g()).tofile(sys.stdout);

Perl 5:

#!/usr/bin/perl

#sub g() {
#    return (map 10000*sin($_*3.14159265358979*2*440.0/44100.0), 0..(44100-1))
#}

# define you g here

my @a = g();
print "RIFFH\x00\x00\x00WAVEfmt\x20\x12\x00\x00\x00\x01\x00\x01\x00\x44\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00\x00\x00LIST\x1a\x00\x00\x00INFOISFT\x0e\x00\x00\x00GolfNotePl\0\0\0\0data\x00\xff\xff\xff";
print join("",map(pack("s", $_), @a));

Haskell:

#!/usr/bin/runhaskell

import qualified Data.Serialize.Put as P
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C8
import Data.Word
import Control.Monad

-- g :: [Word16]
-- g = map (\t->floor $ 10000 * sin(t*2*3.14159265358979*440/44100)) [0..44100-1]
-- insert your g here

main = do
    B.putStr $ C8.pack $ "RIFFH\x00\x00\x00WAVEfmt\x20\x12\x00\x00\x00\x01\x00\x01\x00\x44\xac\x00\x00\x88X\x01\x00\x02\x00\x10\x00\x00\x00LIST\x1a\x00\x00\0INFOISFT\x0e\x00\x00\x00GolfNote\0\0\0\0\0\0data\x00\xff\xff\xff"
    B.putStr $ P.runPut $ sequence_ $ map P.putWord16le g

Example

Here's ungolfed C version modeled after piano sound:

void g(signed short *array, int* length) {
    *length = 44100*5;
    int i;

    double overtones[]={4, 1, 0.5, 0.25, 0.125};

    double freq[]   = {393, 416, 376, 355, 339, 451, 555};
    double freq_k[] = {40,  0.8,  1,  0.8,   0.7,  0.4, 0.25};
    double corrector = 1/44100.0*2*3.14159265358979323;

    double volumes_begin[] ={0,     0.025, 0.05,   0.4};
    double volumes_end  [] ={0.025, 0.05,  0.4,    5};

    double volumes_kbegin[]={0,     1.8,   1,      0.4};
    double volumes_kend [] ={1.8,     1,   0.4,    0};

    for(i=0; i<44100*5; ++i) {
        int j;
        double volume = 0;

        for(j=0; j<sizeof volumes_begin/sizeof(*volumes_begin); ++j) {
            double t = i/44100.0;
            if(t>=volumes_begin[j] && t<volumes_end[j]) {
                volume += volumes_kbegin[j]*(volumes_end[j]-t  )/(volumes_end[j]-volumes_begin[j]);
                volume += volumes_kend[j]  *(t-volumes_begin[j])/(volumes_end[j]-volumes_begin[j]);
            }
        }

        int u;
        for(u=0; u<sizeof freq/sizeof(*freq); ++u) {
            for(j=0; j<sizeof overtones/sizeof(*overtones); ++j) {
                double f = freq[u]*(j+1);
                array[i] += freq_k[u]*volume*10000.0/(f)/1*overtones[j]*sin(1.0*i*corrector*f);
            }
        }
    }
}

It scores approximately 1330 bytes and provides poor/mediocre quality.

Vi.

Posted 2013-10-25T00:39:04.727

Reputation: 2 644

Question was closed 2016-04-19T14:28:37.433

2To be an appropriate codegolf challenge, you are required to define an objective winning criterion. (Given the nature of this challenge, I think it'll have to be "popularity contest", i.e. most number of upvotes.) – breadbox – 2013-10-25T01:15:55.090

The example doesn't seem to work. The output is completely distorted and has many break-ups in it. Compiled in MinGW with "gcc -o piano.exe piano.c" and executed with "piano.exe >piano.wav". Even using the simple 440 Hz tone g function has the same result. BTW, you can use M_PI in place of your huge numbers. It's defined in math.h. – Mike C – 2013-10-25T04:22:17.230

@Mike C, The beginning of output of C boilerplate with uncommented q should look like this: http://pastebin.com/ZCB1v7QQ . Is your host big-endian?

– Vi. – 2013-10-25T09:21:26.463

No, I'm using MinGW so I'm x86. I'll try it on one of my Linux boxes. I don't understand why I'm having a problem though. Strange. – Mike C – 2013-10-25T17:25:51.887

does $><<7.chr in Ruby count? :P for 9 characters! or $><<?\a for 7 chars – Doorknob – 2013-10-26T16:35:42.460

Don't understand the question. Typed that in ruby1.9.1 started in console, got no response... (I'm not a Ruby programmer). Score counting is simple: 1. Remove everything expect of the function that does the actual math and it's dependencies, 2. Save this to file, 3. See the file size. – Vi. – 2013-10-27T00:56:47.017

@Vi. Maybe it only works in Windows... ASCII 7 is the 'bell' character, which is supposed to make a "beep" noise :P – Doorknob – 2013-10-27T02:41:42.627

@Doorknob, Even if it works, it's not by the rules: No dependance on sample libraries, specialized music generation things, such as system beep sound, which can be considered as a sample and OS as musical library in this context. – Vi. – 2013-10-27T02:43:50.670

Answers

2

Java

My boilerplate plays the sound. I could golf g() a bit more, but it's currently at 273 chars which is well under 1500. I originally wrote this for 16kHz for a 4kB game and had to tweak the constants a bit to get the right tonal qualities at 44.1kHz playback, but I'm reasonably happy with it.

import java.io.*;
import javax.sound.sampled.*;

public class codegolf13003 {
    byte[]g(){byte[]d=new byte[88000];int r=1,R=1103515247,b[]=new int[650],i,o,s,y;for(i=0;i<b.length;r*=R)b[i++]=0x4000+((r>>16)&0x3fff);for(i=o=0;i<d.length;o=s){s=(o+1)%b.length;y=(b[o]+b[s])/2*((r&0x10000)<1?-1:1);r*=R;d[i++]=(byte)(b[o]=y);d[i++]=(byte)(y>>8);}return d;}

    public static void main(String[] args) throws Exception {
        byte[] data = new codegolf13003().g();
        ByteArrayInputStream bais = new ByteArrayInputStream(data);
        AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 1, 2, 44100, false/*LE*/);
        AudioInputStream stream = new AudioInputStream(bais, format, data.length / 2);
        new Previewer().preview(stream);
    }

    static class Previewer implements LineListener {
        Clip clip;

        public void preview(AudioInputStream ais) throws Exception {
            AudioFormat audioFormat = ais.getFormat();
            DataLine.Info info = new DataLine.Info(Clip.class, audioFormat);

            clip = (Clip)AudioSystem.getLine(info);
            clip.addLineListener(this);

            clip.open(ais);
            clip.start();
            while (true) Thread.sleep(50); // Avoid early exit of program
        }

        public void update(LineEvent le) {
            LineEvent.Type type = le.getType();
            if (type == LineEvent.Type.CLOSE) {
                System.exit(0);
            }
            else if (type == LineEvent.Type.STOP) {
                clip.close();
            }
        }
    }
}

Further reading: Karplus-Strong synthesis.

Peter Taylor

Posted 2013-10-25T00:39:04.727

Reputation: 41 901

To start without PulseAudio I use this: java -Djavax.sound.sampled.Clip=com.sun.media.sound.DirectAudioDeviceProvider -Djavax.sound.sampled.Port=com.sun.media.sound.PortMixerProvider -Djavax.sound.sampled.SourceDataLine=com.sun.media.sound.DirectAudioDeviceProvider -Djavax.sound.sampled.TargetDataLine=com.sun.media.sound.DirectAudioDeviceProvider codegolf13003 – Vi. – 2013-10-25T09:33:20.193

Assuming you wanted some percussions, but unsure which one exactly. It sounds a bit too "electronical". – Vi. – 2013-10-25T09:35:51.567

@Vi., I'll leave it a while for other people to say which instrument they think I'm aiming for before I unveil it. – Peter Taylor – 2013-10-26T07:17:37.280

Since people have had a few days to guess, I'm going to spill the beans. The intended instrument is a snare. – Peter Taylor – 2013-11-01T09:00:34.040

Can you give a link to actual recorded sample to compare? – Vi. – 2013-11-01T17:02:04.253

@Vi., I might be able to extract one from a MIDI sound bank, but I'll have to check the licence... – Peter Taylor – 2013-11-01T22:59:02.863

2

C

Here's the g() function, without the boilerplate.

void g(signed short *array, int* length)
{
    short r[337];
    int c, i;

    *length = 44100 * 6;
    for (i = 0 ; i < 337 ; ++i)
        r[i] = rand();
    *array = *r;
    for (i = c = 1 ; i < *length ; ++i) {
        array[i] = r[c];
        r[c] = (r[c] + array[i - 1]) * 32555 / 65536;
        c = (c + 1) % 337;
    }
}

An interesting experiment is to play with the first loop that initializes a beginning sequence of random values. Replacing the call to rand() with i*i changes the character of the sound in a plausible way (that is, it sounds like the synthesis is imitating a different member of the same instrument family). i*i*i and i*i*i*i give other sound qualities, though each one gets closer to sounding like rand(). A value like i*327584 or i*571, on the other hand, sounds quite different (and less like an imitation of something real).


Another minor variation of the same function comes even closer to another instrument, or at least it does to my ear.

void g(signed short *array, int* length)
{
    int i;

    *length = 44100 * 6;
    for (i = 0 ; i < 337 ; ++i)
        array[i] = rand();
    for ( ; i < *length ; ++i)
        array[i] = (array[i - 337] + array[i - 1]) * 32555 / 65536;
}

Edited to Add: I hadn't been treating this as a code golf question, since it's not flagged as such (beyond the 1500-char limit), but since it's been brought up in the comments, here's a golfed version of the above (96 characters):

g(short*a,int*n){int i=0;for(*n=1<<18;i<*n;++i)
a[i]=i>336?32555*(a[i-337]+a[i-1])/65536:rand();}

(I could get it down below 80 characters if I could change the function interface to use global variables.)

breadbox

Posted 2013-10-25T00:39:04.727

Reputation: 6 893

Karplus-Strong string. Sounds like a steel string to me. – Peter Taylor – 2013-10-26T07:17:01.733

@PeterTaylor my thought exactly. The bottom variant, on the other hand, sounds to me exactly like the gut (or nylon) string of a harpsichord. It just needs the thunk of the returning quill afterwards to complete the illusion. – breadbox – 2013-10-26T16:22:39.443

After removing the whitespace and shortening array, length, void and signed in the second code I got the score: 113 bytes. Very nice try. And the sound is rather good. – Vi. – 2013-10-27T01:05:33.303