Toca una canción para mí

23

Reto

Dada la tablatura de guitarra, debe generar la canción representada por la pestaña. Esto puede ser para los altavoces de su computadora o para un archivo de audio (.wav, .mp3, .midi, .aiff, etc.). También habrá una segunda entrada para el tiempo.

Las pestañas pueden ingresarse a través de un archivo o directamente a STDIN. La pestaña estará en formato ASCII .

Especulación

Todas las pestañas son para 6 guitarras de seis cuerdas con afinación E estándar: E2 (82.41 Hz), A2 (110.00 Hz), D3 (146.83 Hz), G3 (196.00 Hz), B3 (246.94 Hz), E4 (329.63 Hz).

Las únicas técnicas (además de la recolección normal) que debe atender son:

  • Flexión (siempre será una curva de medio tono)
  • Martillando en
  • Quitándose
  • Deslizarse hacia arriba / abajo

Como no puede sintetizar el sonido de una cuerda silenciada, trátelo xcomo a -.

Al doblar, devuelva la transición completa de sin doblar a cuerda para doblar a sin doblar nuevamente.

La segunda entrada será el tiempo que cada símbolo en la pestaña representa en segundos. Por ejemplo:

Para entrada:

e|---
B|---
G|---
D|---
A|---
E|---

Con el tiempo 0.5, debido a que hay 3columnas de símbolos (pero sin notas), el archivo de audio emitido es ( 3*0.5=1.5) 1.5segundos de silencio.

Pestañas de ejemplo

1 - The Weight (Jack White, Jimmy Page + The Edge edition)

e|----3-----3---3----2---------3--------------------|
B|----3-----3---3----3--1-1----3--------------------|
G|----0-----0---0----2--0-0----0--------------------|
D|----0-----0---2-------2-2----0--------------------|          
A|----2-----0---2-------3-3----2--------------------|     
E|----3-----2---x----2--x-x----3--------------------|   

2 - Huele a espíritu adolescente

e|--------------|---------------|-------------|-------------|
B|--------------|---------------|-------------|-------------|
G|-----8h10-----|-8-8b----6--5--|-6--5--------|-------------|
D|-10--------6--|---------------|-------8-6-8-|-8b----6--5--|
A|--------------|---------------|-------------|-------------|
E|--------------|---------------|-------------|-------------|

3 - Bandera Spangled Star

e|---0-------2-5---9-7-5-----------9-7-5-4-2-4-5------|
B|-----2---2-------------2-4-5---5---------------5-2--|
G|-------2-------------------------------------------2|
D|----------------------------------------------------|
A|----------------------------------------------------|
E|----------------------------------------------------|
Decaimiento Beta
fuente
3
Agregué algunos decimales más a sus frecuencias. Dado que un semitono = 1 traste es una relación de 1.059463: 1 (es decir, una diferencia de aproximadamente 6%) la sintonía al 1Hz más cercano no es lo suficientemente precisa como para obtener un buen sonido de sintonía. Por supuesto, al ser un concurso de popularidad, una mala sintonía puede ser admisible pero no ganará.
Level River St
Concurso muy creativo! Después de mirar el enlace al formulario ASCII, pude entender el ejemplo 2 (ya que escuché la canción), pero debido a que no conozco la guitarra, creo que el desafío tiene una curva de aprendizaje alta. También tengo poca experiencia con la manipulación de audio que no sea el uso básico de Audacity.
mbomb007
¿MIDI cuenta como un 'archivo de audio'?
orlp 01 de
@orlp Sí, lo hace
Beta Decay
1
Bien para referencia futura: v * (2 ^ (f / 12)) = x; v = frecuencia de cadena; f = Traste (el número en la pestaña); x = frecuencia reproducida; Las pestañas tampoco le indican la duración de una nota; Su programa debe ser inteligente.
Grant Davis

Respuestas:

7

MATLAB

Esto es algo inacabado. Utilicé un método rápido y sucio para hacer el audio lo más fácil posible. El método que utilicé dificultó la implementación de la flexión / martilleo (nunca antes había escuchado esas palabras en este contexto).

Dicho todo esto, este script se leerá en un archivo de texto llamado "inputs.txt" que contiene la pestaña ascii según sea necesario y reproducirá la canción.

%sincronización
t = 0,25; % por supuesto, esta línea podría ser 't = input (' timing: ');
        % si realiza un valor de ajuste tal que t * 8192 no es un entero, algunos
        % de cosas fallarán
% de frecuencias y variables adicionales para permitir algo de pereza más tarde
e = 329,63; eN = 1;
B = 246,94; BN = 2;
G = 196.00; GN = 3;
D = 146.83; DN = 4;
A = 110,00; AN = 5;
E = 82.41; EN = 6;
% esto almacenará la canción de una manera más amigable con la computadora
canción = ceros (1,6);
Función% para obtener frecuencia de v = frecuencia yf = traste
w = @ (v, f) v * (2 ^ (f / 12));
% obtener entrada y comenzar el bucle grande
archivo = fopen ('input.txt');
línea = fgetl (archivo);
mientras ischar (línea)
    % el primer caracter de la línea nos dará la frecuencia de la línea
    lfreqv = eval (línea (1)); %frecuencia
    lfreqN = eval ([línea (1), 'N']); % índice horizontal de frecuencia
    % inicia el bucle pequeño sobre cada línea
    para k = 3: (numeral (línea)) - 1
        if (strcmp (línea (k), '-')) || (strcmp (línea (k), '|')) || (strcmp (línea (k), 'h')) || (strcmp (línea (k), 'b'))
            canción (k-2, lfreqN) = 0;
        más
            canción (k-2, lfreqN) = w (lfreqv, doble (línea (k)));
        fin
    fin
    línea = fgetl (archivo);
fin
fclose (archivo);
% esto mantendrá la canción
melodía = [];
vols = ceros (1,6);
playf = ceros (1,6);
para songIndex = 1: tamaño (canción, 1)
    ctune = [];
    para k = 1: 6
        if song (songIndex, k) == 0
            vols (k) = 2 * vols (k) / 4;
        más
            vols (k) = 1;
            playf (k) = canción (songIndex, k);
        fin
        ctune (k, 1: t * 8192) = vols (k) * sin (0.5 * pi * playf (k) * (1: (t * 8192)) / 8192);
    fin
    melodía = [melodía ctune];
fin
sonidosc (suma (melodía));

Aquí hay un enlace al sonido de la primera entrada de prueba.

Aquí hay un enlace al sonido de la tercera entrada de prueba. (¿Bandera estrellada o camión de helados?)

La segunda entrada de prueba sonaba bastante malo para mí, pero que puede ser debido a que utiliza una gran cantidad de bs y hs que las secuencias de comandos omite.

Como puede escuchar, la salida no tiene la misma calidad que el original. Suena como si hubiera un metrónomo tocando en el fondo. Creo que estas canciones tienen carácter.

sudo rm -rf slash
fuente
Wow, eso suena como una caja de música ... ¡Muy agradable!
Beta Decay
5

Python 3

Tuve que probar este.

Esto convierte una pestaña en un archivo midi como lo toca un piano. No tengo idea de cómo doblar una cuerda en un piano, por lo que no puede hacer eso, pero los martillos y los pull-off son sencillos.

Generé los archivos de prueba de esta manera: ¿ $ python3 tab.py The-weight.txt 0.14dónde 0.14está la duración de una sola nota en segundos?

from midiutil.MidiFile3 import MIDIFile
import sys

# Read the relevant lines of the file
lines = []
if len(sys.argv) > 1:
    filename = sys.argv[1]
    try:
        beats_per_minute = 60 / float(sys.argv[2])
    except:
        beats_per_minute = 756
else:
    filename = 'mattys-tune.txt'
    beats_per_minute = 756
with open(filename) as f:
    for line in f:
        if len(line) > 3 and (line[1] == '|' or line[2] == '|'):
            line = line.replace('\n', '')
            lines.append(line)
assert len(lines) % 6 == 0

# Initialize the MIDIFile object (with 1 track)
time = 0
duration = 10
volume = 100
song = MIDIFile(1)
song.addTrackName(0, time, "pianized_guitar_tab.")
song.addTempo(0, time, beats_per_minute)

# The root-pitches of the guitar
guitar = list(reversed([52, 57, 62, 67, 71, 76])) # Assume EADGBe tuning
def add_note(string, fret):
    song.addNote(0, string, guitar[string] + fret, time, duration, volume)

# Process the entire tab
for current in range(0, len(lines), 6):  # The current base string
    for i in range(len(lines[current])): # The position in the current string
        time += 1
        for s in range(6):               # The number of the string
            c = lines[current + s][i]
            try: next_char = lines[current + s][i + 1]
            except: next_char = ''
            if c in '-x\\/bhp':
                # Ignore these characters for now
                continue
            elif c.isdigit():
                # Special case for fret 10 and higher
                if next_char.isdigit():
                    c += next_char
                    lines[current + s] = lines[current + s][:i+1] + '-' + lines[current + s][i+2:]
                # It's a note, play it!
                add_note(s, int(c))
            else:
                # Awww
                time -= 1
                break

# And write it to disk.
def save():
    binfile = open('song.mid', 'wb')
    song.writeFile(binfile)
    binfile.close()
    print('Done')
try:
    save()
except:
    print('Error writing to song.mid, try again.')
    input()
    try:
        save()
    except:
        print('Failed!')

El código también está en github, https://github.com/Mattias1/ascii-tab , donde también cargué el resultado de los ejemplos proporcionados por el OP. También lo probé en algunas de mis pestañas. Es bastante extraño escuchar tocar un piano, pero no está mal.

Ejemplos:

Agregué algunos enlaces directos, pero no estoy seguro de cuánto tiempo se quedan, así que también conservaré los enlaces de descarga anteriores.

  1. El peso , o jugar
  2. Huele a espíritu adolescente o juega
  3. Estrella saltó pancarta , o jugar
  4. La melodía de Matty , o tocar
  5. dm tune , o play

Y la pestaña de la melodía de Matty (mi favorita) a continuación:

    Am/C        Am            F          G             Am/C        Am
e |------------------------|----------------0-------|------------------------|
B |-1--------1--1--------1-|-1--------1--3-----3----|-1--------1--1--------1-|
G |-2-----2-----2-----2----|-2-----2--------------0-|-2-----2-----2-----2----|
D |----2-----------2-------|----2-------------------|----2-----------2-------|
A |-3-----2-----0----------|-------0--------0--2----|-3-----------0----------|
E |-------------------3----|-1-----------3----------|------------------------|

    F        G               Am/C        Am           F           G
e |------------------------|------------------------|----------------0-------|
B |-1--------3-------------|-1--------1--1--------1-|-1--------1--3-----3----|
G |----------4-------------|-2-----2-----2-----2----|-2-----2--------------0-|
D |-------3--5-------------|----2-----------2-------|----2-------------------|
A |----3-----5--------0--2-|-3-----2-----0----------|-------0--------0--2----|
E |-1--------3-----3-------|-------------------3----|-1-----------3----------|

    Am/C        Am           F        G
e |------------------------|------------------------|
B |-1--------1--1--------1-|-1----------3-----------|
G |-2-----2-----2-----2----|------------4-----------|
D |----2-----------2-------|-------3---5------------|
A |-3-----------0----------|----3------5------------|
E |------------------------|-1--------3-------------|
Matty
fuente
1
Woah, 756 BPM ?! Espero que ese no sea el ritmo final ...
Beta Decay
Jaja, bueno, hago trampa un poco. 2/3de esos 'latidos' son, de hecho, guiones.
Matty
Woah, la melodía de Matty suena genial. ¿Cómo es una guitarra?
Beta Decay
1
Gracias @BetaDecay, es una canción que hice una vez (la línea de base) inspirada en la luna azul de Tommy Emmanuel ( youtube.com/watch?v=v0IY3Ax2PkY ). Pero no suena tan bien como lo hace.
Matty
4

Java Script

Nota: Utiliza el kit de audio de desarrollo web; Esto está muy lejos de IE's League; Probado en Google Chrome

Puedes poner las pestañas en el área de texto. Es decir, podrías poner la melodía de Matty en la publicación de Matty en el área de texto (con las letras sobre las notas) y aún se analizará correctamente.

Haga clic para ejecutar el programa

JavaScript:

context = new AudioContext;
gainNode = context.createGain();
gainNode.connect(context.destination);

gain= 2;

function getValue(i) {
    return document.getElementById(i).value;
}

function addValue(i, d) {
    document.getElementById(i).value += d;
}

function setValue(i, d) {
    document.getElementById(i).value = d;
}

document.getElementById("tada").onclick = updateLines;

function updateLines(){
    var e=getValue("ta").replace(/[^-0-9\n]/g,'').replace("\n\n","\n").split("\n");
    for(var l=0;l<e.length;l+=6){
        try{
        addValue("littleE",e[l]);
        addValue("B",e[l+1]);
        addValue("G",e[l+2]);
        addValue("D",e[l+3]);
        addValue("A",e[l+4]);
        addValue("E",e[l+5]);
        }catch(err){}
    }
    updateDash();
}

document.getElementById("littleE").oninput = updateDash;
document.getElementById("B").oninput = updateDash;
document.getElementById("G").oninput = updateDash;
document.getElementById("D").oninput = updateDash;
document.getElementById("A").oninput = updateDash;
document.getElementById("E").oninput = updateDash;


function updateDash() {
    max = 10;
    findDashMax("littleE");
    findDashMax("B");
    findDashMax("G");
    findDashMax("D");
    findDashMax("A");
    findDashMax("E");
    applyMax();
    i = "littleE";
    dash = new Array();
    for (var l = 0; l < getValue(i).length; l++) {
        if (getValue(i).charCodeAt(l) == 45) {
            dash[l] = true;
        } else {
            dash[l] = false;
        }
    }
    /*applyDash("B");
    applyDash("G");
    applyDash("D");
    applyDash("A");
    applyDash("E");*/
}

function findDashMax(i) {
    if (getValue(i).length > max) {
        max = getValue(i).length;
    }
}

function applyMax() {
    if (max < 50) {
        document.getElementById("stepe").size = 50;
        document.getElementById("littleE").size = 50;
        document.getElementById("B").size = 50;
        document.getElementById("G").size = 50;
        document.getElementById("D").size = 50;
        document.getElementById("A").size = 50;
        document.getElementById("E").size = 50;
    } else {
        document.getElementById("stepe").size = max + 1;
        document.getElementById("littleE").size = max + 1;
        document.getElementById("B").size = max + 1;
        document.getElementById("G").size = max + 1;
        document.getElementById("D").size = max + 1;
        document.getElementById("A").size = max + 1;
        document.getElementById("E").size = max + 1;
    }
}

function applyDash(i) {
    var old = getValue(i);
    setValue(i, "");
    for (var l = 0; l < old.length || dash[l] == true; l++) {
        if (dash[l] == true) {
            addValue(i, "-");
        } else {
            if (old.charCodeAt(l) != 45) {
                addValue(i, old.charAt(l));
            }
        }
    }
}
document.getElementById("next").onclick = begin;

function addDash(i) {
    while (getValue(i).length < max) {
        addValue(i, "-");
    }
}

function begin() {
    setValue("littleE",getValue("littleE").replace(/[^-0-9]/g,''));
    setValue("B",getValue("B").replace(/[^-0-9]/g,''));
    setValue("G",getValue("G").replace(/[^-0-9]/g,''));
    setValue("D",getValue("D").replace(/[^-0-9]/g,''));
    setValue("A",getValue("A").replace(/[^-0-9]/g,''));
    setValue("E",getValue("E").replace(/[^-0-9]/g,''));
    addDash("littleE");
    addDash("B");
    addDash("G");
    addDash("D");
    addDash("A");
    addDash("E");
    setValue("next", "Stop");
    //playing = true;
    findLength();
    document.getElementById("next").onclick = function () {
        clearInterval(playingID);
        oscillator["littleE"].stop(0);
        oscillator["B"].stop(0);
        oscillator["G"].stop(0);
        oscillator["D"].stop(0);
        oscillator["A"].stop(0);
        oscillator["E"].stop(0);
        setValue("next", "Play");
        document.getElementById("next").onclick = begin;
    }
    step = -1;
    playingID = setInterval(function () {
        step++;
        setValue("stepe", "");
        for (var l = 0; l < step; l++) {
            addValue("stepe", " ");
        }
        addValue("stepe", "V");
        if (lg[step]) {
            oscillator["littleE"].stop(0);
            oscillator["B"].stop(0);
            oscillator["G"].stop(0);
            oscillator["D"].stop(0);
            oscillator["A"].stop(0);
            oscillator["E"].stop(0);
        }
        qw=0
        doSound("littleE");
        doSound("B");
        doSound("G");
        doSound("D");
        doSound("A");
        doSound("E");

    }, getValue("s") * 1000);
}

function doSound(i) {
    switch (getValue(i).charAt(step)) {
        case ("-"):
        case ("x"):
        case (""):
        case (" "):
            break;
        default:
            qw++;
            makeSound(fretToHz(getHz(i), getValue(i).charAt(step)), i);

    }
    checkTop();
}

function checkTop(){
    switch(qw){
        case 0:
            break;
        case 1:
            gain=2;
            break;
        case 2:
            gain=1;
            break;
        case 3:
            gain=.5;
            break;
        default:
            gain=.3;
            break;
    }
}

function getHz(i) {
    switch (i) {
        case "littleE":
            return 329.63;
        case "B":
            return 246.94;
        case "G":
            return 196;
        case "D":
            return 146.83;
        case "A":
            return 110;
        case "E":
            return 82.41;
    }
}

function fretToHz(v, f) {
    return v * (Math.pow(2, (f / 12)));
}

/*function getTime() {
    var u = 1;
    while (lg[step + u] == false) {
        u++;
    }
    return u;
}*/

function findLength() {
    lg = new Array();
    for (var h = 0; h < getValue("littleE").length; h++) {
        lg[h] = false;
        fl(h, "littleE");
        fl(h, "B");
        fl(h, "G");
        fl(h, "D");
        fl(h, "A");
        fl(h, "E");
    }
    console.table(lg);
}

function fl(h, i) {
    var l = getValue(i).charAt(h);
    switch (l) {
        case "-":
        case "|":
            break;
        default:
            lg[h] = true;
    }
}

oscillator = new Array();

function makeSound(hz, i) {
    console.log("playing " + hz + " Hz" + i);
    oscillator[i] = context.createOscillator();
    oscillator[i].connect(gainNode);
    oscillator[i].frequency.value = hz;
    oscillator[i].start(0);
}

soundInit("littleE");
soundInit("B");
soundInit("G");
soundInit("D");
soundInit("A");
soundInit("E");

function soundInit(i) {
    makeSound(440, i);
    oscillator[i].stop(0);
}
setInterval(function () {
    gainNode.gain.value = .5 * getValue("v") * gain;
    document.getElementById("q").innerHTML = "Volume:" + Math.round(getValue("v") * 100) + "%";
}, 100);

¿Puedes identificar esta canción?

Grant Davis
fuente
1
Se estrella en personajes como | / b h p. ¿Por qué no hacer un pequeño análisis de cadenas para reemplazarlos -? Eso sonará bastante bien y funciona. (Y tal vez dividir en líneas nuevas usando un cuadro de entrada). Eso hará que este sea un guión divertido para jugar.
Matty
Que lo que estaba planeando hacer, nunca lo logré.
Grant Davis
Estoy de acuerdo, la línea diferente para cada cadena es un dolor, pero de lo contrario suena bien
Beta Decay
¡Vaya! Olvidé iniciar sesión antes de editar la publicación.
Grant Davis
Reconozco la melodía pero no puedo ponerle un nombre ... Sin embargo, suena genial
Beta Decay
2

Java

Este programa convierte una tablatura a formato WAV de 16 bits.

En primer lugar, escribí un montón de código de análisis de tablaturas. No estoy seguro de si mi análisis es completamente correcto, pero creo que está bien. Además, podría usar más validación para los datos.

Después de eso, hice el código para generar el audio. Cada cadena se genera por separado. El programa realiza un seguimiento de la frecuencia actual, amplitud y fase. Luego genera 10 sobretonos para la frecuencia con amplitudes relativas inventadas, y los suma. Finalmente, las cadenas se combinan y el resultado se normaliza. El resultado se guarda como audio WAV, que elegí por su formato ultra simple (no se usan bibliotecas).

Es "compatible" con martillar ( h) y tirar ( p) al ignorarlos, ya que realmente no tuve tiempo para hacerlos sonar muy diferentes. Sin embargo, el resultado suena un poco como una guitarra (pasé algunas horas analizando mi guitarra en Audacity).

Además, admite flexión ( b), liberación ( r) y deslizamiento ( /y \, intercambiable). xse implementa como silenciar la cadena.

Puede intentar ajustar las constantes al comienzo del código. Especialmente la reducción a silenceRatemenudo conduce a una mejor calidad.

Resultados de ejemplo

El código

Quiero advertir a los principiantes de Java: no intentes aprender nada de este código, está terriblemente escrito. Además, se escribió rápidamente y en 2 sesiones y no se debe volver a utilizar, por lo que no tiene comentarios. (Podría agregar algo más tarde: P)

import java.io.*;
import java.util.*;

public class TablatureSong {

    public static final int sampleRate = 44100;

    public static final double silenceRate = .4;

    public static final int harmonies = 10;
    public static final double harmonyMultiplier = 0.3;

    public static final double bendDuration = 0.25;

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        System.out.println("Output file:");
        String outfile = in.nextLine();
        System.out.println("Enter tablature:");
        Tab tab = parseTablature(in);
        System.out.println("Enter tempo:");
        int tempo = in.nextInt();
        in.close();

        int samples = (int) (60.0 / tempo * tab.length * sampleRate);
        double[][] strings = new double[6][];
        for (int i = 0; i < 6; i++) {
            System.out.printf("Generating string %d/6...\n", i + 1);
            strings[i] = generateString(tab.actions.get(i), tempo, samples);
        }

        System.out.println("Combining...");
        double[] combined = new double[samples];
        for (int i = 0; i < samples; i++)
            for (int j = 0; j < 6; j++)
                combined[i] += strings[j][i];

        System.out.println("Normalizing...");
        double max = 0;
        for (int i = 0; i < combined.length; i++)
            max = Math.max(max, combined[i]);
        for (int i = 0; i < combined.length; i++)
            combined[i] = Math.min(1, combined[i] / max);

        System.out.println("Writing file...");
        writeWaveFile(combined, outfile);
        System.out.println("Done");
    }

    private static double[] generateString(List<Action> actions, int tempo, int samples) {
        double[] harmonyPowers = new double[harmonies];
        for (int harmony = 0; harmony < harmonyPowers.length; harmony++) {
            if (Integer.toBinaryString(harmony).replaceAll("[^1]", "").length() == 1)
                harmonyPowers[harmony] = 2 * Math.pow(harmonyMultiplier, harmony);
            else
                harmonyPowers[harmony] = Math.pow(harmonyMultiplier, harmony);
        }
        double actualSilenceRate = Math.pow(silenceRate, 1.0 / sampleRate);

        double[] data = new double[samples];

        double phase = 0.0, amplitude = 0.0;
        double slidePos = 0.0, slideLength = 0.0;
        double startFreq = 0.0, endFreq = 0.0, thisFreq = 440.0;
        double bendModifier = 0.0;
        Iterator<Action> iterator = actions.iterator();
        Action next = iterator.hasNext() ? iterator.next() : new Action(Action.Type.NONE, Integer.MAX_VALUE);

        for (int sample = 0; sample < samples; sample++) {
            while (sample >= toSamples(next.startTime, tempo)) {
                switch (next.type) {
                case NONE:
                    break;
                case NOTE:
                    amplitude = 1.0;
                    startFreq = endFreq = thisFreq = next.value;
                    bendModifier = 0.0;
                    slidePos = 0.0;
                    slideLength = 0;
                    break;
                case BEND:
                    startFreq = addHalfSteps(thisFreq, bendModifier);
                    bendModifier = next.value;
                    slidePos = 0.0;
                    slideLength = toSamples(bendDuration);
                    endFreq = addHalfSteps(thisFreq, bendModifier);
                    break;
                case SLIDE:
                    slidePos = 0.0;
                    slideLength = toSamples(next.endTime - next.startTime, tempo);
                    startFreq = thisFreq;
                    endFreq = thisFreq = next.value;
                    break;
                case MUTE:
                    amplitude = 0.0;
                    break;
                }
                next = iterator.hasNext() ? iterator.next() : new Action(Action.Type.NONE, Integer.MAX_VALUE);
            }

            double currentFreq;
            if (slidePos >= slideLength || slideLength == 0)
                currentFreq = endFreq;
            else
                currentFreq = startFreq + (endFreq - startFreq) * (slidePos / slideLength);

            data[sample] = 0.0;
            for (int harmony = 1; harmony <= harmonyPowers.length; harmony++) {
                double phaseVolume = Math.sin(2 * Math.PI * phase * harmony);
                data[sample] += phaseVolume * harmonyPowers[harmony - 1];
            }

            data[sample] *= amplitude;
            amplitude *= actualSilenceRate;
            phase += currentFreq / sampleRate;
            slidePos++;
        }
        return data;
    }

    private static int toSamples(double seconds) {
        return (int) (sampleRate * seconds);
    }

    private static int toSamples(double beats, int tempo) {
        return (int) (sampleRate * beats * 60.0 / tempo);
    }

    private static void writeWaveFile(double[] data, String outfile) {
        try (OutputStream out = new FileOutputStream(new File(outfile))) {
            out.write(new byte[] { 0x52, 0x49, 0x46, 0x46 }); // Header: "RIFF"
            write32Bit(out, 44 + 2 * data.length, false); // Total size
            out.write(new byte[] { 0x57, 0x41, 0x56, 0x45 }); // Header: "WAVE"
            out.write(new byte[] { 0x66, 0x6d, 0x74, 0x20 }); // Header: "fmt "
            write32Bit(out, 16, false); // Subchunk1Size: 16
            write16Bit(out, 1, false); // Format: 1 (PCM)
            write16Bit(out, 1, false); // Channels: 1
            write32Bit(out, 44100, false); // Sample rate: 44100
            write32Bit(out, 44100 * 1 * 2, false); // Sample rate * channels *
                                                    // bytes per sample
            write16Bit(out, 1 * 2, false); // Channels * bytes per sample
            write16Bit(out, 16, false); // Bits per sample
            out.write(new byte[] { 0x64, 0x61, 0x74, 0x61 }); // Header: "data"
            write32Bit(out, 2 * data.length, false); // Data size
            for (int i = 0; i < data.length; i++) {
                write16Bit(out, (int) (data[i] * Short.MAX_VALUE), false); // Data
            }
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void write16Bit(OutputStream stream, int val, boolean bigEndian) throws IOException {
        int a = (val & 0xFF00) >> 8;
        int b = val & 0xFF;
        if (bigEndian) {
            stream.write(a);
            stream.write(b);
        } else {
            stream.write(b);
            stream.write(a);
        }
    }

    private static void write32Bit(OutputStream stream, int val, boolean bigEndian) throws IOException {
        int a = (val & 0xFF000000) >> 24;
        int b = (val & 0xFF0000) >> 16;
        int c = (val & 0xFF00) >> 8;
        int d = val & 0xFF;
        if (bigEndian) {
            stream.write(a);
            stream.write(b);
            stream.write(c);
            stream.write(d);
        } else {
            stream.write(d);
            stream.write(c);
            stream.write(b);
            stream.write(a);
        }
    }

    private static double[] strings = new double[] { 82.41, 110.00, 146.83, 196.00, 246.94, 329.63 };

    private static Tab parseTablature(Scanner in) {
        String[] lines = new String[6];
        List<List<Action>> result = new ArrayList<>();
        int longest = 0;
        for (int i = 0; i < 6; i++) {
            lines[i] = in.nextLine().trim().substring(2);
            longest = Math.max(longest, lines[i].length());
        }
        int skipped = 0;
        for (int i = 0; i < 6; i++) {
            StringIterator iterator = new StringIterator(lines[i]);
            List<Action> actions = new ArrayList<Action>();
            while (iterator.index() < longest) {
                if (iterator.get() < '0' || iterator.get() > '9') {
                    switch (iterator.get()) {
                    case 'b':
                        actions.add(new Action(Action.Type.BEND, 1, iterator.index(), iterator.index()));
                        iterator.next();
                        break;
                    case 'r':
                        actions.add(new Action(Action.Type.BEND, 0, iterator.index(), iterator.index()));
                        iterator.next();
                        break;
                    case '/':
                    case '\\':
                        int startTime = iterator.index();
                        iterator.findNumber();
                        int endTime = iterator.index();
                        int endFret = iterator.readNumber();
                        actions.add(new Action(Action.Type.SLIDE, addHalfSteps(strings[5 - i], endFret), startTime,
                                endTime));
                        break;
                    case 'x':
                        actions.add(new Action(Action.Type.MUTE, iterator.index()));
                        iterator.next();
                        break;
                    case '|':
                        iterator.skip(1);
                        iterator.next();
                        break;
                    case 'h':
                    case 'p':
                    case '-':
                        iterator.next();
                        break;
                    default:
                        throw new RuntimeException(String.format("Unrecognized character: '%c'", iterator.get()));
                    }
                } else {
                    StringBuilder number = new StringBuilder();
                    int startIndex = iterator.index();
                    while (iterator.get() >= '0' && iterator.get() <= '9') {
                        number.append(iterator.get());
                        iterator.next();
                    }
                    int fret = Integer.parseInt(number.toString());
                    double freq = addHalfSteps(strings[5 - i], fret);
                    actions.add(new Action(Action.Type.NOTE, freq, startIndex, startIndex));
                }
            }
            result.add(actions);
            skipped = iterator.skipped();
        }
        return new Tab(result, longest - skipped);
    }

    private static double addHalfSteps(double freq, double halfSteps) {
        return freq * Math.pow(2, halfSteps / 12.0);
    }

}

class StringIterator {
    private String string;
    private int index, skipped;

    public StringIterator(String string) {
        this.string = string;
        index = 0;
        skipped = 0;
    }

    public boolean hasNext() {
        return index < string.length() - 1;
    }

    public void next() {
        index++;
    }

    public void skip(int length) {
        skipped += length;
    }

    public char get() {
        if (index < string.length())
            return string.charAt(index);
        return '-';
    }

    public int index() {
        return index - skipped;
    }

    public int skipped() {
        return skipped;
    }

    public boolean findNumber() {
        while (hasNext() && (get() < '0' || get() > '9'))
            next();
        return get() >= '0' && get() <= '9';
    }

    public int readNumber() {
        StringBuilder number = new StringBuilder();
        while (get() >= '0' && get() <= '9') {
            number.append(get());
            next();
        }
        return Integer.parseInt(number.toString());
    }
}

class Action {
    public static enum Type {
        NONE, NOTE, BEND, SLIDE, MUTE;
    }

    public Type type;
    public double value;
    public int startTime, endTime;

    public Action(Type type, int time) {
        this(type, time, time);
    }

    public Action(Type type, int startTime, int endTime) {
        this(type, 0, startTime, endTime);
    }

    public Action(Type type, double value, int startTime, int endTime) {
        this.type = type;
        this.value = value;
        this.startTime = startTime;
        this.endTime = endTime;
    }
}

class Tab {
    public List<List<Action>> actions;
    public int length;

    public Tab(List<List<Action>> actions, int length) {
        this.actions = actions;
        this.length = length;
    }
}
PurkkaKoodari
fuente
Sé que no lo he especificado, pero ¿podría publicar algunos casos de prueba que la gente pueda escuchar en las otras respuestas?
Beta Decay
@BetaDecay Actualizó mi respuesta, ahora tiene un montón de pruebas
PurkkaKoodari
Esos enlaces no funcionan: /
Beta Decay
@BetaDecay Comprobé dos veces otra conexión en modo incógnito de un navegador que no uso. Ellos trabajan para mí, al menos.
PurkkaKoodari
Bien, me gusta mucho tu versión de Mattys, aunque a veces la base es difícil de escuchar.
Matty