Грай пісню для мене


23

Виклик

З огляду на гітарну табулатуру ви повинні вивести пісню, представлену вкладкою. Це може бути в динаміках комп'ютера або в аудіофайлі (.wav, .mp3, .midi, .aiff тощо). Буде також другий вхід для синхронізації.

Вкладки можуть бути введені через файл або прямо в STDIN. Вкладка буде у формі ASCII .

Спец

Усі вкладки призначені для шести струнних гітар зі стандартною настройкою E: E2 (82,41 Гц), A2 (110,00 Гц), D3 (146,83 Гц), G3 (196,00 Гц), B3 (246,94 Гц), E4 (329,63 Гц).

Єдині прийоми (крім звичайного збирання), якими ви повинні задовольнити:

  • Згинання (це завжди буде згин напівтону)
  • Забивання
  • Стягуючи
  • Ковзання вгору / вниз

Оскільки ви не можете синтезувати звук приглушеної струни, розгляньте її xяк a -.

Під час згинання виведіть повний перехід від відігнутих до рядків до зігнутих до відхилених знову.

Другим входом буде час, який кожний символ на вкладці представляє в секундах. Наприклад:

Для введення:

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

З часом 0.5, оскільки є 3стовпці символів (але немає приміток), на вихідному аудіофайлі ( 3*0.5=1.5) 1.5секунд мовчання.

Приклади вкладок

1 - Вага (Джек Вайт, Джиммі Сторінка + Видання 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 - пахне духом підлітка

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

3 - Зірчастий сплетений банер

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

3
Я додав ще кілька знаків після коми до ваших частот. Зважаючи на те, що один напівтон = 1 лад - це відношення 1.059463: 1 (тобто різниця приблизно в 6%), настройка на найближчу 1 Гц не є достатньо точною, щоб отримати гарний тон звучання. Звичайно, що це конкурс на популярність, погана настройка може бути допустимою, але вона не виграє.
Рівень річки Св.

Дуже креативний конкурс! Після того, як я переглянув посилання на форму ASCII, я міг зрозуміти приклад 2 (оскільки я чув пісню), але оскільки я не знаю гітари, я думаю, що завдання має високу криву навчання. У мене також мало досвіду роботи зі звуковими маніпуляціями, крім основного використання Audacity.
mbomb007

Чи MIDI вважається "аудіофайлом"?
orlp

@orlp Так, так
бета-розпад

1
Ну для подальшої довідки: v * (2 ^ (f / 12)) = x; v = частота рядка; f = Fret (число на вкладці); x = відтворювана частота; Вкладки також не вказують вам довжину нотатки; ваша програма повинна бути розумною.
Грант Девіс

Відповіді:


7

MATLAB

Це свого роду незавершене. Я використовував швидкий і брудний метод, щоб зробити звук максимально легким. Метод, який я застосував, ускладнив виконання згинання / забивання (я також ніколи раніше не чув цих слів у цьому контексті).

Сказавши все це, цей сценарій буде прочитаний у текстовому файлі під назвою "inputs.txt", що містить вкладку ascii, як потрібно, і відтворить пісню.

% часу
t = 0,25; % звичайно, цей рядок може бути 't = input (' timing: ');
        %, якщо ви зробите ta winky значення таким, що t * 8192 не є цілим числом, деяким
        % матеріалів не вдасться
% частот та додаткових змінних, щоб пізніше встигнути трохи лінь
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;
%, це збереже пісню більш зручним для комп'ютера способом
пісня = нулі (1,6);
% функція для отримання частоти з v = частоти і f = ладу
w = @ (v, f) v * (2 ^ (f / 12));
% отримати вхід і запустити великий цикл
file = fopen ('input.txt');
рядок = fgetl (файл);
а ischar (лінія)
    % перший символ рядка дасть нам частоту лінії
    lfreqv = eval (рядок (1)); % частоти
    lfreqN = eval ([рядок (1), 'N']); % горизонтальний показник частоти
    % запустити невелику петлю над кожним рядком
    для k = 3: (numel (рядок)) - 1
        if (strcmp (рядок (k), '-')) || (strcmp (рядок (k), '|')) || (strcmp (рядок (k), 'h')) || (strcmp (рядок (k), 'b'))
            пісня (k-2, lfreqN) = 0;
        ще
            пісня (k-2, lfreqN) = w (lfreqv, подвійна (рядок (k)));
        кінець
    кінець
    рядок = fgetl (файл);
кінець
fclose (файл);
% ця пісня утримуватиме
настроювати = [];
vols = нулі (1,6);
playf = нулі (1,6);
для songIndex = 1: розмір (пісня, 1)
    ctune = [];
    для k = 1: 6
        якщо пісня (songIndex, k) == 0
            vols (k) = 2 * vols (k) / 4;
        ще
            vols (k) = 1;
            playf (k) = пісня (songIndex, k);
        кінець
        ctune (k, 1: t * 8192) = vols (k) * sin (0,5 * pi * playf (k) * (1: (t * 8192)) / 8192);
    кінець
    tune = [настроювати ctune];
кінець
soundc (сума (мелодія));

Ось посилання на звук першого тестового входу.

Ось посилання на звук третього тестового входу. (Зіркований банер чи вантажівка морозива?)

Другий тестовий вклад звучав для мене досить погано, але це може бути тому, що він використовує багато bs і hs, які сценарій ігнорує.

Як ви чуєте, вихід має не зовсім таку якість, як оригінал. Це ніби звучить так, ніби у фоновому режимі грає метроном. Я думаю, що ці мелодії мають характер.


Нічого собі, це звучить як музична скринька ... Дійсно приємно!
Бета-розпад

5

Пітон 3

Мені довелося спробувати цю.

Це перетворює вкладку у файл midi, який відтворюється на фортепіано. Я не маю уявлення, як зробити згинання струни на фортепіано, так що не може цього зробити, але забивання та вибивання є простими.

Я генерував тестові файли так: $ python3 tab.py The-weight.txt 0.14де 0.14довжина однієї ноти в секундах.

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

Код також є на github, https://github.com/Mattias1/ascii-tab , куди я також завантажив результат прикладів, наданих ОП. Я також спробував це на деяких власних вкладках. Досить дивно чути, як грає на фортепіано, але це непогано.

Приклади:

Я додав кілька прямих посилань, але не впевнений, як довго вони залишаються, тому я також зберігатиму старі посилання для завантаження.

  1. Вага , або гра
  2. Пахне духом підлітка , або грає
  3. Зірка сплете банер , або грай
  4. Мелодія Матті , або гра
  5. dm мелодію або грати

А вкладка з мелодії Матті (моя улюблена) нижче:

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

1
Вау, 756 БПМ ?! Я сподіваюся, що це не фінальний ритм ...
бета-розпад

Ха-ха, ну, я трохи обманюю. 2/3з цих "ударів" насправді є тире.
Матті

Вуха, мелодія Матті звучить досить круто. Як це на гітарі?
Бета-розпад

1
Дякую @BetaDecay, колись я зробив мелодію (базову лінію), натхненну синім місяцем Томмі Еммануїла ( youtube.com/watch?v=v0IY3Ax2PkY ). Але це не звучить наполовину так добре, як це він робить.
Матті

4

Java Script

Примітка: використовує аудіо-комплект для веб-розробки Це вихід з Ліги IE; Тестується в Google Chrome

Ви можете розмістити вкладки в текстовій області. IE ви можете помістити мелодію Матті з поста Матті в текстовій області (з літерами над нотами), і вона все одно буде правильно розбиратися.

Клацніть для запуску програми

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

Чи можете ви визначити цю пісню?


1
Він виходить з ладу на таких персонажів | / b h p. Чому б просто не зробити невеликий розбір рядків, щоб замінити їх -? Це буде звучати цілком нормально, і це працює. (І, можливо, розділити на нові рядки за допомогою одного поля введення.) Це зробить цей сценарій веселим для гри.
Матті

Що я планував робити, я просто ніколи цього не обійшов.
Грант Девіс

Я погоджуюся, різний рядок для кожного рядка - це біль, але в іншому випадку це звучить добре
Beta Decay

На жаль, забувши увійти до редагування публікації.
Грант Девіс

Я розпізнаю мелодію, але не можу поставити її ім'я ... Хоча звучить круто
Beta Decay

2

Java

Ця програма перетворює табулатуру в 16-бітний формат WAV.

По-перше, я написав цілу купу коду для розбору табулятури. Я не впевнений, чи правильний мій аналіз, але думаю, що це нормально. Крім того, він може використовувати більше перевірки даних.

Після цього я зробив код для створення аудіо. Кожен рядок генерується окремо. Програма відстежує поточну частоту, амплітуду та фазу. Потім він генерує 10 обертонів для частоти із складеними відносними амплітудами і додає їх. Нарешті, рядки поєднуються і результат нормалізується. Результат зберігається як WAV-аудіо, яке я вибрав для його надпростого формату (не використовуються бібліотеки).

Він "підтримує" забивання ( h) і потягнення ( p), ігноруючи їх, оскільки я дійсно не мав часу зробити так, щоб вони звучали занадто інакше. Результат здається трохи схожим на гітару (витративши кілька годин на спектр аналізу моєї гітари в Audacity).

Крім того, він підтримує згинання ( b), звільнення ( r) і ковзання ( /і \, взаємозамінний). xреалізується як вимкнення рядка.

Ви можете спробувати налаштувати константи на початку коду. Особливо зниження silenceRateчасто призводить до кращої якості.

Приклад результатів

Кодекс

Хочу попередити будь-яких початківців Java: не намагайтеся нічого дізнатися з цього коду, це жахливо написано. Крім того, він був написаний швидко та за два сеанси, і він не мав на увазі використовуватись знову, тому він не має коментарів. (Можна додати трохи пізніше: 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;
    }
}

Я знаю, що я цього не вказав, але чи можете ви розмістити кілька тестових випадків, які люди можуть слухати, як в інших відповідях?
Бета-розпад

@BetaDecay Оновлено мою відповідь, тепер є маса тестів
PurkkaKoodari

Ці посилання не працюють: /
Beta Decay

@BetaDecay Я двічі перевірив інше з'єднання в режимі анонімного перегляду браузера, який я не використовую. Вони принаймні працюють на мене.
PurkkaKoodari

Приємно, мені подобається, що твій варіант Меттіса дуже налаштований, хоча основу іноді важко чути.
Матті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.