Nur eine Note - Musikinstrumentensynthese [geschlossen]

11

Erklärung

Die Aufgabe besteht darin, den Klang (eine gespielte Note) eines Musikinstruments (Ihrer Wahl) mithilfe einer Funktion in einer allgemeinen Programmiersprache (Ihrer Wahl) zu synthetisieren.

Es gibt zwei Ziele:

  • Qualität des resultierenden Klangs. Es sollte dem echten Instrument so gut wie möglich ähneln.
  • Minimalität. Es wird empfohlen, den Code unter 1500 Byte zu halten (weniger, wenn nur eine grundlegende Klangerzeugung vorhanden ist).

Es muss nur eine Generierungsfunktion bereitgestellt werden, die Boilerplate wird nicht für die Punktzahl gezählt.

Leider kann für die Wiedergabetreue keine Punktzahl berechnet werden, so dass es keine strengen Regeln geben kann.

Regeln:

  • Keine Abhängigkeit von Sample-Bibliotheken, speziellen Dingen der Musikgenerierung;
  • Kein Herunterladen aus dem Netzwerk oder Versuch, das MIDI eines Mikrofons oder einer Audiokarte oder etwas zu Externes wie dieses zu verwenden;
  • Die Maßeinheit für die Codegröße ist Bytes. Die Datei kann im aktuellen Verzeichnis erstellt werden. Bereits vorhandene Dateien (Koeffiziententabellen usw.) sind möglicherweise vorhanden, ihr Inhalt wird jedoch zur Punktzahl hinzugefügt. Sie müssen nach Namen geöffnet werden.
  • Der Boilerplate-Code (nicht zur Punktzahl gezählt) empfängt ein Array (eine Liste) von vorzeichenbehafteten Ganzzahlen und behandelt nur deren Ausgabe.
  • Das Ausgabeformat besteht aus kleinen 16-Bit-Endian-Wörtern mit 44100 Abtastwerten pro Sekunde und optionalem WAV-Header. Kein Versuch, komprimiertes Audio anstelle von einfachem WAV auszugeben;
  • Bitte wählen Sie verschiedene Instrumente für die Synthese aus (oder eine andere Qualitäts- oder Codegrößenkategorie für das Instrument). Sagen Sie jedoch zunächst nicht, was Sie simulieren. Lassen Sie andere Benutzer in Kommentaren raten.
  • Von elektronischen Instrumenten wird abgeraten.
  • Trommel ist ein Instrument. Die menschliche Stimme ist ein Instrument.

Boilerplates

Hier sind Boilerplates für einige Sprachen. Sie können eine ähnliche Kesselplatte auch für Ihre Sprache schreiben. Die auskommentierte "g" -Funktion ist nur für eine Demo gedacht (1 Sekunde 440 Hz Sinus).

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

Beispiel

Hier ist eine ungolfed C-Version nach dem Vorbild des Pianosounds:

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);
            }
        }
    }
}

Es erreicht ungefähr 1330 Bytes und bietet eine schlechte / mittelmäßige Qualität.

Vi.
quelle
2
Um eine geeignete Codegolf-Herausforderung zu sein, müssen Sie ein objektives Gewinnkriterium definieren. (Angesichts der Art dieser Herausforderung denke ich, dass es ein "Beliebtheitswettbewerb" sein muss, dh die meisten Upvotes.)
Breadbox
Das Beispiel scheint nicht zu funktionieren. Die Ausgabe ist vollständig verzerrt und weist viele Trennungen auf. In MinGW mit "gcc -o piano.exe piano.c" kompiliert und mit "piano.exe> ​​piano.wav" ausgeführt. Selbst bei Verwendung der einfachen 440-Hz-Ton-G-Funktion wird das gleiche Ergebnis erzielt. Übrigens können Sie M_PI anstelle Ihrer großen Zahlen verwenden. Es ist in math.h definiert.
Mike C
@ Mike C, Der Beginn der Ausgabe von C Boilerplate mit unkommentiertem qsollte folgendermaßen aussehen: pastebin.com/ZCB1v7QQ . Ist Ihr Gastgeber Big-Endian?
Vi.
Nein, ich benutze MinGW, also bin ich x86. Ich werde es auf einer meiner Linux-Boxen versuchen. Ich verstehe nicht, warum ich ein Problem habe. Seltsam.
Mike C
zählt $><<7.chrin Ruby? : P für 9 Zeichen! oder $><<?\afür 7 Zeichen
Türknauf

Antworten:

2

Java

Mein Boilerplate spielt den Ton. Ich könnte g()ein bisschen mehr Golf spielen , aber es liegt derzeit bei 273 Zeichen, was weit unter 1500 liegt. Ich habe dies ursprünglich für 16 kHz für ein 4-kB-Spiel geschrieben und musste die Konstanten ein wenig anpassen, um die richtigen Tonqualitäten bei der Wiedergabe von 44,1 kHz zu erzielen, aber ich bin ziemlich zufrieden damit.

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();
            }
        }
    }
}

Weiterführende Literatur: Karplus-starke Synthese .

Peter Taylor
quelle
Um ohne PulseAudio zu starten, benutze ich Folgendes: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.
Angenommen, Sie wollten einige Percussions, sind sich aber nicht sicher, welche genau. Es klingt ein bisschen zu "elektronisch".
Vi.
@Vi., Ich überlasse es eine Weile anderen Leuten zu sagen, auf welches Instrument sie meiner Meinung nach zielen, bevor ich es enthülle.
Peter Taylor
Da die Leute ein paar Tage Zeit hatten, um zu raten, werde ich die Bohnen verschütten. Das beabsichtigte Instrument ist eine Schlinge.
Peter Taylor
Können Sie einen Link zur tatsächlich aufgezeichneten Probe zum Vergleich geben?
Vi.
2

C.

Hier ist die g()Funktion ohne 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;
    }
}

Ein interessantes Experiment besteht darin, mit der ersten Schleife zu spielen, die eine Anfangssequenz von Zufallswerten initialisiert. Das Ersetzen des Aufrufs rand()durch i*iändert den Charakter des Klangs auf plausible Weise (das heißt, es klingt so, als würde die Synthese ein anderes Mitglied derselben Instrumentenfamilie imitieren). i*i*iund i*i*i*igeben Sie andere Klangqualitäten, obwohl jeder dem Klang näher kommt rand(). Ein Wert wie i*327584oder i*571klingt dagegen ganz anders (und weniger wie eine Nachahmung von etwas Realem).


Eine weitere geringfügige Variation derselben Funktion kommt einem anderen Instrument noch näher, oder zumindest meinem Ohr.

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;
}

Bearbeitet, um hinzuzufügen: Ich habe dies nicht als Code-Golf-Frage behandelt, da es nicht als solches gekennzeichnet ist (über das Limit von 1500 Zeichen hinaus), aber da es in den Kommentaren erwähnt wurde, ist hier eine Golf-Version des oben genannten ( 96 Zeichen):

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();}

(Ich könnte es unter 80 Zeichen bringen, wenn ich die Funktionsoberfläche ändern könnte, um globale Variablen zu verwenden.)

Brot-Box
quelle
Karplus-starke Saite. Klingt für mich wie eine Stahlschnur.
Peter Taylor
@ PeterTaylor mein Gedanke genau. Die untere Variante hingegen klingt für mich genau wie die Darm- (oder Nylon-) Saite eines Cembalos. Es braucht nur den Rumpf der zurückkehrenden Feder danach, um die Illusion zu vervollständigen.
Brotkasten
Nach dem Leerzeichen und Verkürzung zu entfernen array, length, voidund signedin dem zweiten Code bekomme ich die Punktzahl: 113 Bytes. Sehr schöner Versuch. Und der Sound ist ziemlich gut.
Vi.