Tylko jedna nuta - synteza instrumentu muzycznego [zamknięte]

11

Komunikat

Zadanie polega na syntezie dźwięku (jednej nuty) wybranego instrumentu muzycznego (wybranego) za pomocą funkcji w jakimś języku programowania ogólnego przeznaczenia (wybranym przez Ciebie).

Istnieją dwa cele:

  • Jakość uzyskanego dźwięku. Powinien przypominać prawdziwy instrument tak dobrze, jak to możliwe;
  • Minimalizm. Zalecane jest utrzymanie kodu poniżej 1500 bajtów (mniej, jeśli istnieje tylko podstawowe generowanie dźwięku).

Należy podać tylko funkcję generowania, płyta kotła nie jest liczona jako wynik.

Niestety nie można obliczyć żadnego wyniku dla wierności dźwięku, więc nie mogą istnieć surowe zasady.

Zasady:

  • Bez zależności od bibliotek próbek, specjalistycznych rzeczy do generowania muzyki;
  • Żadnego pobierania z sieci ani próbowania korzystania z MIDI mikrofonu lub karty dźwiękowej, lub czegoś zbyt zewnętrznego;
  • Jednostką miary wielkości kodu są bajty. Plik można utworzyć w bieżącym katalogu. Istniejące pliki (tabele współczynników itp.) Mogą istnieć, ale ich zawartość jest dodawana do partytury + należy je otworzyć według nazwy.
  • Kod bojlera (nie liczony do wyniku) otrzymuje tablicę (listę) liczb całkowitych ze znakiem i zajmuje się tylko ich wyprowadzaniem.
  • Format wyjściowy jest podpisany małymi 16-bitowymi słowami endian, 44100 próbek na sekundę, z opcjonalnym nagłówkiem WAV. Nie ma potrzeby wysyłania skompresowanego dźwięku zamiast zwykłego wav;
  • Proszę wybrać inne instrumenty do syntezy (lub inną kategorię jakości względem wielkości kodu dla instrumentu); ale początkowo nie mów, co symulujesz - pozwól innym użytkownikom zgadywać w komentarzach;
  • Instrumenty elektroniczne są odradzane;
  • Bęben jest instrumentem. Ludzki głos jest instrumentem.

Kotły

Oto plansze dla niektórych języków. Możesz również napisać podobną płytę kotłową dla swojego języka. Skomentowana funkcja „g” jest tylko dla wersji demonstracyjnej (1 sekunda sinusoidy 440 Hz).

DO:

//#!/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

Przykład

Oto nie golfowa wersja C wzorowana na brzmieniu fortepianu:

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

Ma około 1330 bajtów i zapewnia słabą / przeciętną jakość.

Vi.
źródło
2
Aby być odpowiednim wyzwaniem dla kundla, musisz określić obiektywne kryterium wygranej. (Ze względu na charakter tego wyzwania, myślę, że to musi być „popularność konkursu”, czyli najbardziej liczba upvotes.)
Breadbox
Ten przykład nie działa. Wyjście jest całkowicie zniekształcone i zawiera wiele rozpadów. Skompilowany w MinGW z „gcc -o piano.exe piano.c” i wykonany z „piano.exe> ​​piano.wav”. Nawet użycie prostej funkcji t g 440 Hz daje ten sam rezultat. BTW, możesz użyć M_PI zamiast swoich wielkich liczb. Jest to zdefiniowane w matematyce. H.
Mike C
@Mike C, Początek produkcji płyty kotłowej C z niekomentowanym qpowinien wyglądać tak: pastebin.com/ZCB1v7QQ . Czy Twój gospodarz to big-endian?
Vi.
Nie, używam MinGW, więc jestem x86. Spróbuję na jednym z moich Linux'ów. Nie rozumiem jednak, dlaczego mam problem. Dziwne.
Mike C
robi $><<7.chrsię liczyć Ruby? : P dla 9 znaków! lub $><<?\ana 7 znaków
Klamka

Odpowiedzi:

2

Jawa

Moja płyta kotła odtwarza dźwięk. Mogłem grać g()w golfa trochę więcej, ale obecnie ma on 273 znaki, czyli znacznie poniżej 1500. Pierwotnie napisałem to dla 16kHz dla gry 4kB i musiałem nieco ulepszyć stałe, aby uzyskać odpowiednią jakość tonalną przy odtwarzaniu 44,1kHz, ale ja jestem z tego dość zadowolony.

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

Dalsza lektura: Synteza Karplus-Strong .

Peter Taylor
źródło
Aby rozpocząć bez PulseAudio używam tego: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.
Zakładając, że chciałeś mieć jakieś instrumenty perkusyjne, ale nie wiesz, który dokładnie. Brzmi to trochę zbyt „elektronicznie”.
Vi.
@Vi. Pozostawię chwilę, aby inni ludzie powiedzieli, do którego instrumentu myślą, że zamierzam celować, zanim go odsłonię.
Peter Taylor
Ponieważ ludzie mieli kilka dni na odgadnięcie, rozleję fasolę. Zamierzonym instrumentem jest sidło.
Peter Taylor
Czy możesz podać link do faktycznej zarejestrowanej próbki do porównania?
Vi.
2

do

Oto g()funkcja bez płyty grzewczej.

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

Ciekawym eksperymentem jest gra z pierwszą pętlą, która inicjuje początkową sekwencję losowych wartości. Wymiana wywołanie rand()ze i*izmienia charakter brzmienia w wiarygodny sposób (to znaczy, że to brzmi jak synteza jest naśladowanie innego członka tej samej rodziny rozdzielczej). i*i*ii i*i*i*inadaj innym jakościom dźwięk, choć każdy z nich zbliża się do brzmienia rand(). Wartość taka jak i*327584lub i*571, z drugiej strony, brzmi zupełnie inaczej (a mniej jak imitacja czegoś prawdziwego).


Kolejna niewielka odmiana tej samej funkcji zbliża się jeszcze bardziej do innego instrumentu, a przynajmniej do mojego ucha.

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

Edytowano w celu dodania: Nie traktowałem tego jako pytania do golfa kodowego, ponieważ nie jest ono oznaczone jako takie (poza limitem 1500 znaków), ale ponieważ zostało poruszone w komentarzach, oto wersja golfa powyższego ( 96 znaków):

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

(Mógłbym obniżyć go poniżej 80 znaków, gdybym mógł zmienić interfejs funkcji, aby używał zmiennych globalnych.)

chlebak
źródło
Sznurek Karplus-Strong. Brzmi dla mnie jak stalowa struna.
Peter Taylor
@PeterTaylor dokładnie myślałem. Natomiast wariant dolny brzmi dla mnie dokładnie jak struna jelitowa (lub nylonowa) klawesynu. Po prostu potrzebuje kawałka powracającego pióra, aby ukończyć iluzję.
breadbox
Po usunięciu spacji i skrócenie array, length, voida signedw drugim kodzie mam wynik: 113 bajtów. Bardzo fajna próba. A dźwięk jest raczej dobry.
Vi.