Solo una nota: sintetización de instrumentos musicales [cerrado]

11

Declaración

La tarea es sintetizar el sonido (una nota tocada) de algún instrumento musical (de su elección) utilizando la función en algún lenguaje de programación de propósito general (de su elección).

Hay dos objetivos:

  • Calidad del sonido resultante. Debe parecerse al instrumento real lo más fino posible;
  • Minimalidad Se recomienda mantener el código por debajo de 1500 bytes (menos si solo hay una generación de sonido básica).

Solo se debe proporcionar la función de generación, no se cuenta el repetitivo para la puntuación.

Lamentablemente, no se puede calcular la puntuación para la fidelidad del sonido, por lo que no puede haber reglas estrictas.

Reglas:

  • Sin dependencia de bibliotecas de muestras, cosas especializadas en generación de música;
  • No descargar desde la red o intentar usar el micrófono o la tarjeta de audio MIDI o algo demasiado externo como este;
  • La unidad de medida del tamaño del código es bytes. El archivo se puede crear en el directorio actual. Pueden existir archivos preexistentes (tablas de coeficientes, etc.), pero su contenido se agrega a la partitura + deben abrirse por nombre.
  • El código repetitivo (sin contar para calificar) recibe una matriz (lista) de enteros con signo y solo trata de generarlos.
  • El formato de salida está firmado con pequeñas palabras endian de 16 bits, 44100 muestras por segundo, con encabezado WAV opcional. No intente emitir audio comprimido en lugar de wav simple;
  • Elija diferentes instrumentos para sintetizar (u otra categoría de calidad vs tamaño de código para el instrumento); pero no digas inicialmente qué estás simulando: deja que otros usuarios adivinen en los comentarios;
  • Se desaconsejan los instrumentos electrónicos;
  • El tambor es un instrumento. La voz humana es un instrumento.

Calderas

Aquí hay repeticiones para algunos idiomas. También puede escribir una caldera similar para su idioma. La función "g" comentada es solo para una demostración (1 segundo de tono sinusoidal 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

Ejemplo

Aquí hay una versión C sin golf modelada a partir del sonido del 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);
            }
        }
    }
}

Obtiene aproximadamente 1330 bytes y proporciona una calidad pobre / mediocre.

Vi.
fuente
2
Para ser un desafío de codegolf apropiado, debes definir un criterio ganador objetivo. (Dada la naturaleza de este desafío, creo que tendrá que ser un "concurso de popularidad", es decir, la mayor cantidad de votos a favor.)
breadbox
El ejemplo no parece funcionar. La salida está completamente distorsionada y tiene muchas rupturas. Compilado en MinGW con "gcc -o piano.exe piano.c" y ejecutado con "piano.exe> ​​piano.wav". Incluso el uso de la simple función de tono g de 440 Hz tiene el mismo resultado. Por cierto, puede usar M_PI en lugar de sus grandes números. Se define en matemáticas.
Mike C
@ Mike C, el comienzo de la salida de la plantilla de C con comentarios qno comentados debería verse así: pastebin.com/ZCB1v7QQ . ¿Es su anfitrión big-endian?
Vi.
No, estoy usando MinGW, así que soy x86. Lo intentaré en una de mis cajas de Linux. Sin embargo, no entiendo por qué tengo un problema. Extraño.
Mike C
lo hace $><<7.chren el recuento de Ruby? : P para 9 caracteres! o $><<?\apor 7 caracteres
Pomo de la puerta

Respuestas:

2

Java

Mi repetitivo reproduce el sonido. Podría jugar g()al golf un poco más, pero actualmente está en 273 caracteres, que está muy por debajo de 1500. Originalmente escribí esto para 16kHz para un juego de 4kB y tuve que ajustar un poco las constantes para obtener las cualidades tonales correctas en la reproducción de 44.1kHz, pero Estoy razonablemente feliz con eso.

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

Lecturas adicionales: síntesis Karplus-Strong .

Peter Taylor
fuente
Para comenzar sin PulseAudio, uso esto: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.
Suponiendo que quisieras algunas percusiones, pero no estás seguro de cuál exactamente. Suena un poco demasiado "electrónico".
Vi.
@Vi., Dejaré un tiempo para que otras personas digan a qué instrumento creen que apunto antes de desvelarlo.
Peter Taylor
Como la gente ha tenido algunos días para adivinar, voy a derramar los frijoles. El instrumento previsto es una trampa.
Peter Taylor
¿Puede dar un enlace a la muestra registrada real para comparar?
Vi.
2

C

Aquí está la g()función, sin la repetitiva.

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

Un experimento interesante es jugar con el primer ciclo que inicializa una secuencia inicial de valores aleatorios. Sustitución de la llamada a rand()la i*ique cambia el carácter del sonido de manera plausible (que es, suena como la síntesis está imitando a un miembro diferente de la misma familia de instrumentos). i*i*iy i*i*i*idar otras cualidades de sonido, aunque cada uno se acerca a sonar como rand(). Un valor como i*327584o i*571, por otro lado, suena bastante diferente (y menos como una imitación de algo real).


Otra variación menor de la misma función se acerca aún más a otro instrumento, o al menos a mi oído.

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 agregar: no había estado tratando esto como una pregunta de código de golf, ya que no está marcado como tal (más allá del límite de 1500 caracteres), pero como se mencionó en los comentarios, aquí hay una versión de golf de lo anterior ( 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();}

(Podría reducirlo a menos de 80 caracteres si pudiera cambiar la interfaz de la función para usar variables globales).

caja de pan
fuente
Cuerda Karplus-Strong. Suena como una cuerda de acero para mí.
Peter Taylor
@PeterTaylor mi pensamiento exactamente. La variante inferior, por otro lado, me suena exactamente como la cuerda de tripa (o nylon) de un clavecín. Solo necesita el sonido de la pluma que regresa después para completar la ilusión.
breadbox
Después de quitar el espacio en blanco y el acortamiento array, length, voidy signeden el segundo código que me dieron el puntaje: 113 bytes. Muy buen intento. Y el sonido es bastante bueno.
Vi.