Apenas uma nota - sintetizando instrumento musical [fechado]

11

Declaração

A tarefa é sintetizar o som (uma nota tocada) de algum instrumento musical (de sua escolha) usando a função em alguma linguagem de programação de uso geral (de sua escolha).

Existem dois objetivos:

  • Qualidade do som resultante. Deve parecer o instrumento real o mais fino possível;
  • Minimalidade. É recomendável manter o código abaixo de 1500 bytes (menos se houver apenas geração básica de som).

Somente a função de geração precisa ser fornecida, o clichê não é contado para a pontuação.

Infelizmente, nenhuma pontuação pode ser calculada para a fidelidade do som, portanto não pode haver regras estritas.

Regras:

  • Não há dependência de bibliotecas de amostras, coisas especializadas em geração de música;
  • Não é necessário baixar da rede ou tentar usar o MIDI do microfone ou da placa de áudio ou algo muito externo como esse;
  • A unidade de medida do tamanho do código é bytes. O arquivo pode ser criado no diretório atual. Arquivos pré-existentes (tabelas de coeficientes, etc.) podem existir, mas seu conteúdo é adicionado à pontuação + eles devem ser abertos pelo nome.
  • O código padrão (não contado para pontuar) recebe a matriz (lista) de números inteiros assinados e trata apenas da saída deles.
  • O formato de saída é assinado com pequenas palavras endian de 16 bits, 44100 amostras por segundo, com cabeçalho WAV opcional. Não é necessário emitir áudio compactado em vez de wav simples;
  • Por favor, escolha instrumentos diferentes para sintetizar (ou outra categoria de qualidade versus tamanho do código do instrumento); mas não conte inicialmente o que você está simulando - permita que outros usuários adivinhem nos comentários;
  • Instrumentos eletrônicos são desencorajados;
  • Drum é um instrumento. A voz humana é um instrumento.

Boilerplates

Aqui estão boilerplates para alguns idiomas. Você também pode escrever placas semelhantes para o seu idioma. A função "g" comentada é apenas para uma demonstração (1 segundo tom senoidal de 440 Hz).

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

Exemplo

Aqui está a versão C não destruída, modelada após o som do piano:

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

Ele pontua aproximadamente 1330 bytes e fornece qualidade ruim / medíocre.

Vi.
fonte
2
Para ser um desafio adequado ao codegolf, você deve definir um critério de vitória objetivo. (Dada a natureza desse desafio, eu acho que vai ter que ser "popularidade concurso", ou seja, maior número de upvotes.)
breadbox
O exemplo parece não funcionar. A saída é completamente distorcida e possui muitas interrupções. Compilado no MinGW com "gcc -o piano.exe piano.c" e executado com "piano.exe> ​​piano.wav". Mesmo usando a função simples de tom 440 Hz g tem o mesmo resultado. BTW, você pode usar M_PI no lugar de seus grandes números. Está definido em math.h.
Mike C
@ Mike C, O início da saída do clichê C com comentário qnão comentado deve ser assim: pastebin.com/ZCB1v7QQ . O seu host é big-endian?
Vi.
Não, eu estou usando MinGW, então eu sou x86. Vou experimentá-lo em uma das minhas caixas Linux. Eu não entendo por que estou tendo um problema. Estranho.
Mike C
faz $><<7.chrna contagem de Ruby? : P para 9 caracteres! ou $><<?\apor 7 caracteres
Maçaneta da porta

Respostas:

2

Java

Meu clichê reproduz o som. Eu poderia jogar g()um pouco mais, mas atualmente está em 273 caracteres, bem abaixo de 1500. Eu escrevi isso originalmente para 16kHz para um jogo de 4kB e tive que ajustar as constantes um pouco para obter as qualidades tonais certas na reprodução de 44,1kHz, mas eu estou razoavelmente feliz com isso.

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

Leitura adicional: Síntese de Karplus-Strong .

Peter Taylor
fonte
Para começar sem o PulseAudio, eu uso o seguinte: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.
Supondo que você quisesse algumas percussões, mas não sabia exatamente qual. Parece um pouco "eletrônico".
Vi.
@ Vi., Vou deixar um tempo para outras pessoas dizerem qual instrumento elas acham que eu estou buscando antes de revelá-lo.
Peter Taylor
Como as pessoas tiveram alguns dias para adivinhar, eu vou derramar o feijão. O instrumento pretendido é uma armadilha.
Peter Taylor
Você pode fornecer um link para a amostra gravada real para comparar?
Vi.
2

C

Aqui está a g()função, sem o clichê.

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

Um experimento interessante é jogar com o primeiro loop que inicializa uma sequência inicial de valores aleatórios. Substituir a chamada rand()por i*ialtera o caráter do som de uma maneira plausível (ou seja, parece que a síntese está imitando um membro diferente da mesma família de instrumentos). i*i*ie i*i*i*idê outras qualidades sonoras, embora cada uma se aproxime mais da sua aparência rand(). Um valor como i*327584ou i*571, por outro lado, soa bem diferente (e menos como uma imitação de algo real).


Outra variação menor da mesma função se aproxima ainda mais de outro instrumento, ou pelo menos no meu ouvido.

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

Editado para adicionar: eu não estava tratando isso como uma questão de código de golfe, já que não está sinalizada como tal (além do limite de 1500 caracteres), mas desde que foi mencionada nos comentários, aqui está uma versão em golfe do item acima ( 96 caracteres):

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

(Eu poderia diminuir para menos de 80 caracteres se pudesse alterar a interface da função para usar variáveis ​​globais.)

caixa de pão
fonte
Corda Karplus-Strong. Parece uma corda de aço para mim.
Peter Taylor
@ PeterTaylor meu pensamento exatamente. A variante inferior, por outro lado, me parece exatamente como a corda do intestino (ou nylon) de um cravo. Ele só precisa do thunk da pena de retorno depois para completar a ilusão.
breadbox
Depois de remover o espaço em branco e encurtamento array, length, voide signedem que o segundo cigo I tem a pontuação: 113 bytes. Tentativa muito boa. E o som é bastante bom.
Vi.