Mainkan Song for Me

23

Tantangan

Diberikan tablature gitar Anda harus menampilkan lagu yang diwakili oleh tab. Ini mungkin ke pengeras suara komputer Anda atau ke file audio (.wav, .mp3, .midi, .aiff, dll.). Juga akan ada input kedua untuk penentuan waktu.

Tab dapat diinput melalui file atau langsung ke STDIN. Tab akan dalam bentuk ASCII .

Spec

Semua tab adalah untuk 6 enam senar gitar dengan penyetelan E standar: E2 (82,41 Hz), A2 (110,00 Hz), D3 (146,83 Hz), G3 (196,00 Hz), B3 (246,94 Hz), E4 (329,63 Hz).

Satu-satunya teknik (selain pemetikan normal) yang harus Anda penuhi adalah:

  • Membungkuk (ini akan selalu menjadi tikungan setengah nada)
  • Memalu
  • Menarik
  • Geser ke atas / bawah

Karena Anda tidak dapat mensintesis suara string yang diredam, perlakukan xsebagai -.

Ketika menekuk, mengeluarkan transisi penuh dari tidak mengikat ke tali ke ditekuk ke tidak lagi.

Input kedua adalah waktu yang dilambangkan oleh masing-masing simbol pada tab dalam detik. Sebagai contoh:

Untuk input:

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

Dengan pengaturan waktu 0.5, karena ada 3kolom simbol (tetapi tidak ada catatan), file audio yang dihasilkan adalah ( 3*0.5=1.5) 1.5detik hening.

Tab contoh

1 - Berat (Edisi Jack White, Jimmy Page + The 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 - Smells Like Teen Spirit

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|----------------------------------------------------|
Peluruhan Beta
sumber
3
Saya menambahkan beberapa tempat desimal ke frekuensi Anda. Mengingat bahwa satu nada semi = 1 fret adalah rasio 1,059463: 1 (yaitu perbedaan sekitar 6%) penyetelan ke 1Hz terdekat tidak cukup tepat untuk mendapatkan suara in-tune yang baik. Tentu saja menjadi kontes popularitas, penyetelan yang buruk dapat diterima tetapi tidak akan menang.
Level River St
Kontes yang sangat kreatif! Setelah saya melihat tautan ke bentuk ASCII, saya bisa mengerti contoh 2 (karena saya sudah mendengar lagunya), tetapi karena saya tidak tahu gitar, saya pikir tantangannya memiliki kurva belajar yang tinggi. Saya juga memiliki sedikit pengalaman dengan manipulasi audio selain penggunaan dasar Audacity.
mbomb007
Apakah MIDI dianggap sebagai 'file audio'?
orlp
@ orlp Ya, itu benar
Beta Decay
1
Nah untuk referensi di masa mendatang: v * (2 ^ (f / 12)) = x; v = frekuensi string; f = Fret (angka pada tab); x = frekuensi dimainkan; Tab juga tidak memberi tahu Anda panjang catatan; program Anda harus pintar.
Grant Davis

Jawaban:

7

MATLAB

Ini semacam belum selesai. Saya menggunakan metode cepat dan kotor untuk membuat audio semudah mungkin. Metode yang saya gunakan membuatnya sulit untuk menerapkan tekukan / palu (saya juga belum pernah mendengar kata-kata itu dalam konteks ini sebelumnya).

Setelah mengatakan semua itu, skrip ini akan membaca dalam file teks yang disebut "inputs.txt" yang berisi tab ascii seperti yang diperlukan dan memainkan lagunya.

% waktu
t = 0,25; % tentu saja, baris ini bisa menjadi 't = input (' timing: ');
        % jika Anda membuat nilai ta wonky sehingga t * 8192 bukan bilangan bulat, beberapa
        % barang akan gagal
% frekuensi dan variabel tambahan untuk memungkinkan kemalasan nanti
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;
% ini akan menyimpan lagu dengan cara yang lebih ramah komputer
lagu = nol (1,6);
% berfungsi untuk mendapatkan frekuensi dari v = frekuensi dan f = fret
w = @ (v, f) v * (2 ^ (f / 12));
% dapatkan input dan mulai loop besar
file = fopen ('input.txt');
line = fgetl (file);
sementara ischar (baris)
    % karakter pertama dari garis akan memberi kita frekuensi garis
    lfreqv = eval (baris (1)); %frekuensi
    lfreqN = eval ([baris (1), 'N']); % indeks frekuensi horisontal
    % memulai loop kecil di atas setiap baris
    untuk k = 3: (numel (baris)) - 1
        if (strcmp (line (k), '-')) || (strcmp (line (k), '|')) || (strcmp (line (k), 'h')) || (strcmp (line (k), 'b'))
            lagu (k-2, lfreqN) = 0;
        lain
            lagu (k-2, lfreqN) = w (lfreqv, dobel (baris (k)));
        akhir
    akhir
    line = fgetl (file);
akhir
fclose (file);
% ini akan menampung lagu
tune = [];
vols = nol (1,6);
playf = nol (1,6);
untuk songIndex = 1: ukuran (lagu, 1)
    ctune = [];
    untuk k = 1: 6
        if song (songIndex, k) == 0
            vols (k) = 2 * vols (k) / 4;
        lain
            vols (k) = 1;
            playf (k) = lagu (songIndex, k);
        akhir
        ctune (k, 1: t * 8192) = vols (k) * sin (0,5 * pi * playf (k) * (1: (t * 8192)) / 8192);
    akhir
    tune = [tune ctune];
akhir
soundsc (sum (tune));

Berikut ini tautan ke suara input tes pertama.

Berikut ini tautan ke suara input tes ketiga. (Star Spangled Banner atau Ice Cream Truck?)

Input tes kedua terdengar sangat buruk bagi saya, tapi itu mungkin karena menggunakan banyak bs dan hs yang diabaikan skrip.

Seperti yang Anda dengar, kualitas outputnya tidak sama dengan aslinya. Agak terdengar seperti ada metronom yang bermain di latar belakang. Saya pikir lagu-lagu ini memiliki karakter.

sudo rm-rf slash
sumber
Wow, bahwa suara seperti kotak musik ... benar-benar bagus!
Beta Decay
5

Python 3

Saya harus mencoba yang ini.

Ini mengkonversi tab ke file midi sebagai dimainkan oleh piano. Saya tidak tahu bagaimana cara membuat senar membungkuk pada piano, jadi tidak bisa melakukan itu, tetapi palu dan pull-off mudah.

Saya membuat file tes seperti ini: di $ python3 tab.py The-weight.txt 0.14mana 0.14panjang satu not dalam hitungan detik.

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

Kode ini juga ada di github, https://github.com/Mattias1/ascii-tab , di mana saya juga mengunggah hasil dari contoh yang diberikan oleh OP. Saya juga mencobanya di beberapa tab saya sendiri. Cukup aneh mendengar piano memainkannya, tapi itu tidak buruk.

Contoh:

Saya menambahkan beberapa tautan langsung, tetapi tidak yakin berapa lama mereka tinggal, jadi saya akan menyimpan tautan unduhan yang lama juga.

  1. Berat , atau bermain
  2. Baunya seperti semangat remaja , atau bermain
  3. Bintang spanduk spanduk , atau bermain
  4. Lagu Matty , atau permainan
  5. dm tune , atau mainkan

Dan tab dari lagu Matty (favorit saya) di bawah:

    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
sumber
1
Woah, 756 BPM ?! Saya harap itu bukan beat terakhir ...
Beta Decay
Haha, well, saya sedikit curang. 2/3dari 'ketukan' itu sebenarnya putus-putus.
Matty
Woah, nada Matty terdengar sangat keren. Seperti apa di gitar?
Beta Decay
1
Terima kasih @BetaDecay, ini adalah lagu yang pernah saya buat, (garis dasar) yang terinspirasi oleh bulan biru Tommy Emmanuel ( youtube.com/watch?v=v0IY3Ax2PkY ). Tapi itu kedengarannya tidak sebagus bagaimana dia melakukannya.
Matty
4

Java Script

Catatan: Menggunakan Kit Audio Pengembangan Web; Ini adalah jalan keluar dari Liga IE; Diuji di Google Chrome

Anda dapat menempatkan tab di textarea. Yaitu Anda bisa meletakkan lagu Matty dari posting Matty di textarea (dengan huruf di atas catatan) dan itu masih akan diurai dengan benar.

Klik untuk Menjalankan 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);

Bisakah kamu mengidentifikasi lagu ini?

Grant Davis
sumber
1
Itu crash pada karakter seperti | / b h p. Mengapa tidak hanya melakukan parsing string kecil untuk menggantinya -? Itu akan terdengar cukup baik dan berfungsi. (Dan mungkin terpecah pada baris baru menggunakan satu kotak input.). Itu akan membuat skrip ini menyenangkan untuk dimainkan.
Matty
Bahwa apa yang saya rencanakan untuk dilakukan, saya tidak pernah berhasil melakukannya.
Grant Davis
Saya setuju, garis yang berbeda untuk setiap senar adalah menyebalkan tetapi sebaliknya kedengarannya bagus
Beta Decay
Ups lupa untuk masuk sebelum mengedit posting.
Grant Davis
Saya mengenali lagunya tetapi saya tidak dapat menyebutkan namanya ... Kedengarannya keren
Beta Decay
2

Jawa

Program ini mengonversi tablature ke format WAV 16-bit.

Pertama, saya menulis sejumlah besar kode parsing tablature. Saya tidak yakin apakah parsing saya sepenuhnya benar, tetapi saya pikir tidak apa-apa. Selain itu, bisa menggunakan lebih banyak validasi untuk data.

Setelah itu, saya membuat kode untuk menghasilkan audio. Setiap string dihasilkan secara terpisah. Program ini melacak frekuensi, amplitudo, dan fase saat ini. Ini kemudian menghasilkan 10 nada untuk frekuensi dengan amplitudo relatif yang dibuat, dan menambahkannya. Akhirnya, string digabungkan dan hasilnya dinormalisasi. Hasilnya disimpan sebagai audio WAV, yang saya pilih untuk format ultra-sederhana (tidak ada perpustakaan yang digunakan).

Ini "mendukung" memalu ( h) dan menarik ( p) dengan mengabaikan mereka karena saya benar-benar tidak punya waktu untuk membuat mereka terdengar terlalu berbeda. Hasilnya terdengar agak seperti gitar, (menghabiskan beberapa jam spektrum menganalisis gitar saya di Audacity).

Selain itu, mendukung tekukan ( b), pelepasan ( r) dan geser ( /dan \, dapat ditukar).xdiimplementasikan sebagai mematikan string.

Anda dapat mencoba mengubah konstanta di awal kode. Terutama menurunkan silenceRatesering mengarah pada kualitas yang lebih baik.

Contoh hasil

Kode

Saya ingin memperingatkan setiap pemula Java: jangan tidak mencoba untuk belajar sesuatu dari kode ini, itu sangat tertulis. Juga, itu ditulis dengan cepat dan dalam 2 sesi dan itu tidak dimaksudkan untuk digunakan lagi sehingga tidak memiliki komentar. (Bisa tambahkan nanti: 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
sumber
Saya tahu saya belum menentukannya, tetapi dapatkah Anda memposting beberapa test case yang dapat didengarkan orang seperti di jawaban yang lain?
Beta Decay
@BetaDecay Memperbarui jawaban saya, sekarang memiliki banyak tes
PurkkaKoodari
Tautan-tautan itu tidak berfungsi: /
Beta Decay
@ BetaDecay, saya mengecek koneksi lain dalam mode penyamaran browser yang tidak saya gunakan. Mereka bekerja untuk saya, setidaknya.
PurkkaKoodari
Bagus, saya sangat menyukai versi Mattys Anda, meskipun basisnya terkadang sulit didengar.
Matty