Odtwórz piosenkę dla mnie

23

Wyzwanie

Biorąc pod uwagę tabulaturę gitarową, musisz wydać utwór reprezentowany przez zakładkę. Może to dotyczyć głośników komputera lub pliku audio (.wav, .mp3, .midi, .aiff itp.). Będzie także drugie wejście dla pomiaru czasu.

Zakładki mogą być wprowadzane przez plik lub bezpośrednio do STDIN. Zakładka będzie w formie ASCII .

Spec

Wszystkie zakładki dotyczą 6 sześciostrunowych gitar ze standardowym strojeniem E: E2 (82,41 Hz), A2 (110,00 Hz), D3 (146,83 Hz), G3 (196,00 Hz), B3 (246,94 Hz), E4 (329,63 Hz).

Jedynymi technikami (oprócz normalnego wybierania), które musisz uwzględnić, są:

  • Gięcie (zawsze będzie to zgięcie półtonowe)
  • Kucie na
  • Zrywam
  • Przesuwanie w górę / w dół

Ponieważ nie można zsyntetyzować dźwięku wyciszonego łańcucha, należy traktować go xjako -.

Podczas gięcia, ponownie wypisz pełne przejście z giętego na ciąg do giętego na nierozgięty.

Drugim wejściem będzie czas, który każdy symbol na karcie reprezentuje w sekundach. Na przykład:

Do wprowadzenia:

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

W przypadku taktowania 0.5, ponieważ istnieją 3kolumny symboli (ale bez nut), wyprowadzony plik audio ma ( 3*0.5=1.5) 1.5sekundy ciszy.

Przykładowe zakładki

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

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 - Pachnie Nastoletnim Duchem

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

3 - Star Spangled Banner

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|----------------------------------------------------|
Rozpad beta
źródło
3
Dodałem jeszcze kilka miejsc po przecinku do twoich częstotliwości. Biorąc pod uwagę, że jeden półton = 1 próg jest stosunkiem 1.059463: 1 (tj. Różnica około 6%), dostrojenie do najbliższej 1 Hz nie jest wystarczająco precyzyjne, aby uzyskać dobry dźwięk melodii. Oczywiście będąc konkursem popularności, słabe strojenie może być dopuszczalne, ale nie wygra.
Level River St
Bardzo kreatywny konkurs! Kiedy spojrzałem na link do formularza ASCII, mogłem zrozumieć przykład 2 (odkąd słyszałem piosenkę), ale ponieważ nie znam gitary, myślę, że wyzwanie ma wysoką krzywą uczenia się. Mam również niewielkie doświadczenie w manipulowaniu dźwiękiem inne niż podstawowe użycie Audacity.
mbomb007
Czy MIDI liczy się jako „plik audio”?
orlp
@orlp Tak, tak
Beta Decay
1
Cóż dla przyszłych odniesień: v * (2 ^ (f / 12)) = x; v = częstotliwość łańcucha; f = Fret (liczba na zakładce); x = odtwarzana częstotliwość; Karty również nie informują o długości nuty; twój program musi być inteligentny.
Grant Davis,

Odpowiedzi:

7

MATLAB

To jest trochę niedokończone. Użyłem szybkiej i brudnej metody, aby dźwięk był jak najłatwiejszy. Zastosowana przeze mnie metoda utrudniła wdrożenie gięcia / wbijania (nigdy wcześniej nie słyszałem tych słów w tym kontekście).

Powiedziawszy to wszystko, ten skrypt odczyta plik tekstowy o nazwie „inputs.txt” zawierający zakładkę ascii zgodnie z wymaganiami i odtworzy utwór.

%wyczucie czasu
t = 0,25; % oczywiście, wiersz ten może mieć postać „t = input (” timing: ”);
        %, jeśli sprawisz, że ta wartość wygłupienia będzie taka, że ​​t * 8192 nie jest liczbą całkowitą, niektóre
        % rzeczy zawiedzie
% częstotliwości i dodatkowe zmienne, aby pozwolić później na pewne lenistwo
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;
% to zapisze utwór w sposób bardziej przyjazny komputerowi
piosenka = zera (1,6);
Funkcja%, aby uzyskać częstotliwość od v = częstotliwość if = próg
w = @ (v, f) v * (2 ^ (f / 12));
% pobiera dane wejściowe i rozpoczyna dużą pętlę
file = fopen ('input.txt');
linia = fgetl (plik);
while ischar (linia)
    % pierwszy znak linii podaje częstotliwość linii
    lfreqv = eval (linia (1)); %częstotliwość
    lfreqN = eval ([linia (1), „N”]); % poziomy wskaźnik częstotliwości
    % rozpocząć małą pętlę nad każdą linią
    dla k = 3: (numel (linia)) - 1
        if (strcmp (linia (k), '-')) || (strcmp (linia (k), '|')) || (strcmp (linia (k), 'h')) || (strcmp (linia (k), „b”))
            piosenka (k-2, lfreqN) = 0;
        jeszcze
            piosenka (k-2, lfreqN) = w (lfreqv, double (linia (k)));
        koniec
    koniec
    linia = fgetl (plik);
koniec
fclose (plik);
% zatrzyma utwór
melodia = [];
vols = zera (1,6);
playf = zera (1,6);
dla songIndex = 1: rozmiar (piosenka, 1)
    ctune = [];
    dla k = 1: 6
        if song (songIndex, k) == 0
            vols (k) = 2 * vols (k) / 4;
        jeszcze
            vols (k) = 1;
            playf (k) = piosenka (songIndex, k);
        koniec
        ctune (k, 1: t * 8192) = vols (k) * sin (0,5 * pi * playf (k) * (1: (t * 8192)) / 8192);
    koniec
    tune = [dostroić ctune];
koniec
soundsc (suma (melodia));

Oto link do dźwięku pierwszego wejścia testowego.

Oto link do dźwięku trzeciego wejścia testowego. (Star Spangled Banner czy Ice Cream Truck?)

Drugie wejście testowe zabrzmiało dla mnie dość źle, ale może to być spowodowane tym, że używa wielu bs i hs, które skrypt ignoruje.

Jak można usłyszeć, wyjście nie jest tej samej jakości co oryginał. Brzmi to tak, jakby w tle grał metronom. Myślę, że te melodie mają charakter.

sudo rm -rf slash
źródło
Wow, to brzmi jak pozytywka ... Naprawdę fajnie!
Beta Decay
5

Python 3

Musiałem spróbować tego.

Konwertuje to tabulator na plik midi odtwarzany przez fortepian. Nie mam pojęcia, jak zrobić zginanie struny na pianinie, więc nie może tego zrobić, ale młoty i ściągnięcia są proste.

Wygenerowałem pliki testowe w ten sposób: $ python3 tab.py The-weight.txt 0.14gdzie 0.14jest długość pojedynczej nuty w sekundach.

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!')

Kod znajduje się również na githubie, https://github.com/Mattias1/ascii-tab , gdzie przesłałem również wyniki przykładów dostarczonych przez PO. Próbowałem też na niektórych własnych kartach. Dziwnie jest słyszeć grę na pianinie, ale nie jest źle.

Przykłady:

Dodałem kilka bezpośrednich linków, ale nie jestem pewien, jak długo one pozostają, więc zachowam również stare linki do pobierania.

  1. Waga lub gra
  2. Pachnie jak dusza nastolatka lub gra
  3. Sztandar wygięty gwiazdą lub zagraj
  4. Melodia Matty lub gra
  5. dostroić dm lub grać

I zakładka z melodii Matty'ego (moja ulubiona) poniżej:

    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
źródło
1
Woah, 756 BPM ?! Mam nadzieję, że to nie ostatni rytm ...
Beta Decay
Haha, cóż, trochę oszukuję. 2/3z tych „uderzeń” są w rzeczywistości kreski.
Matty
Woah, melodia Matty brzmi całkiem fajnie. Jak to jest na gitarze?
Rozpad
1
Dzięki @BetaDecay, to utwór, który kiedyś stworzyłem (linia bazowa) inspirowany błękitnym księżycem Tommy'ego Emmanuela ( youtube.com/watch?v=v0IY3Ax2PkY ). Ale nie brzmi to tak dobrze, jak to robi.
Matty,
4

Skrypt Java

Uwaga: używa zestawu audio do tworzenia stron internetowych; To jest wyjście z ligi IE; Testowany w Google Chrome

Możesz umieścić zakładki w obszarze tekstowym. IE możesz umieścić melodię Matty'ego z posta Matty w polu tekstowym (z literami nad notatkami) i nadal będzie poprawnie parsowana.

Kliknij, aby uruchomić program

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

Czy potrafisz zidentyfikować tę piosenkę?

Grant Davis
źródło
1
Rozbija się na postaciach takich jak | / b h p. Dlaczego po prostu nie przeanalizować ciągu znaków, aby je zastąpić -? To zabrzmi całkiem dobrze i działa. (I może podzielić na nowe wiersze za pomocą jednego pola wprowadzania). To sprawi, że będzie to zabawny skrypt do zabawy.
Matty,
To, co planowałem zrobić, po prostu nigdy tego nie załatwiłem.
Grant Davis,
Zgadzam się, inna linia dla każdej struny jest bólem, ale poza tym brzmi dobrze
Beta Decay
Ups, zapomniałem zalogować się przed edycją postu.
Grant Davis,
Rozumiem melodię, ale nie umiem jej nazwać ... Brzmi nieźle
Beta Decay
2

Jawa

Ten program konwertuje tabulatury do 16-bitowego formatu WAV.

Po pierwsze, napisałem całą masę kodu do analizy tabulatury. Nie jestem pewien, czy parsowanie jest całkowicie poprawne, ale myślę, że jest w porządku. Ponadto może użyć większej weryfikacji danych.

Następnie stworzyłem kod do generowania dźwięku. Każdy ciąg jest generowany osobno. Program śledzi aktualną częstotliwość, amplitudę i fazę. Następnie generuje 10 nadtonów dla częstotliwości z utworzonymi amplitudami względnymi i sumuje je. Na koniec ciągi są łączone, a wynik jest znormalizowany. Rezultat jest zapisywany jako dźwięk WAV, który wybrałem ze względu na jego bardzo prosty format (bez bibliotek).

„Obsługuje” hammering ( h) i pull ( p), ignorując je, ponieważ tak naprawdę nie miałem czasu, aby brzmiały zbyt odmiennie. Rezultat brzmi trochę jak gitara (spędziłem kilka godzin na analizie mojej gitary w Audacity).

Obsługuje również gięcie ( b), zwalnianie ( r) i przesuwanie ( /i \, wymiennie). xjest implementowany jako wyciszenie łańcucha.

Możesz spróbować ulepszyć stałe na początku kodu. Szczególnie obniżenie silenceRateczęsto prowadzi do lepszej jakości.

Przykładowe wyniki

Kod

Chcę ostrzec wszystkich początkujących w Javie: nie próbuj niczego się uczyć z tego kodu, jest strasznie napisany. Ponadto został napisany szybko i w 2 sesjach i nie miał być ponownie używany, więc nie ma komentarzy. (Może dodać trochę później: 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
źródło
Wiem, że tego nie określiłem, ale czy możesz opublikować kilka przypadków testowych, których ludzie mogą słuchać w innych odpowiedziach?
Rozpad
@BetaDecay Zaktualizowałem moją odpowiedź, teraz mam kilka testów
PurkkaKoodari
Te linki nie działają: /
Beta Decay
@BetaDecay Sprawdziłem dwukrotnie inne połączenie w trybie incognito przeglądarki, której nie używam. Przynajmniej dla mnie działają.
PurkkaKoodari
Fajnie, podoba mi się twoja wersja Mattys, ale czasem baza jest trudna do usłyszenia.
Matty,