Сыграй песню для меня

23

Вызов

Для данной гитарной табулатуры вы должны вывести песню, представленную на вкладке. Это могут быть динамики вашего компьютера или аудиофайл (.wav, .mp3, .midi, .aiff и т. Д.). Там также будет второй вход для синхронизации.

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

спекуляция

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

Единственными приемами (помимо нормального выбора), которые вам нужно обслуживать, являются:

  • Изгиб (это всегда будет полутоновый изгиб)
  • Стук по
  • Стаскивать
  • Скольжение вверх / вниз

Поскольку вы не можете синтезировать звук приглушенной струны, рассматривайте xкак -.

При изгибе выведите полный переход от разогнутого к строковому, чтобы снова отогнуть.

Вторым вводом будет время, которое каждый символ на вкладке представляет в секундах. Например:

Для ввода:

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

С учетом времени 0.5, поскольку есть 3столбцы символов (но нет примечаний), выводимый аудиофайл имеет значение ( 3*0.5=1.5) 1.5секунд молчания.

Примеры вкладок

1 - Вес (Джек Уайт, Джимми Пейдж + издание 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 - пахнет духом подростка

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 Гц не является достаточно точной, чтобы получить хороший настроенный звук. Конечно, плохой тюнинг может быть допустимым, но это не победа.
Уровень Река St
Очень креативный конкурс! После того, как я посмотрел на ссылку на форму ASCII, я смог понять пример 2 (так как я услышал песню), но, поскольку я не знаю гитару, я думаю, что у задания высокая кривая обучения. У меня также есть небольшой опыт работы с аудио, кроме базового использования Audacity.
mbomb007
Считается ли MIDI «аудиофайл»?
orlp
@orlp Да, это так
Beta Decay
1
Хорошо для дальнейшего использования: v * (2 ^ (f / 12)) = x; v = частота строки; f = Fret (номер на вкладке); х = частота воспроизведения; Вкладки также не сообщают вам длину заметки; ваша программа должна быть умной.
Грант Дэвис

Ответы:

7

MATLAB

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

Сказав все это, этот скрипт будет читать текстовый файл с именем «input.txt», содержащий вкладку ascii, и проигрывать песню.

% времени
т = 0,25; %, конечно, эта строка может быть 't = input (' timer: ');
        %, если вы сделаете значение ta wonky таким, что t * 8192 не является целым числом, некоторые
        % вещи потерпит неудачу
% частот и дополнительных переменных, чтобы потом немного лениться
е = 329,63; еН = 1;
B = 246,94; BN = 2;
G = 196,00; GN = 3;
D = 146,83; DN = 4;
А = 110,00; АН = 5;
Е = 82,41; ЕН = 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: (цифра (строка)) - 1
        if (strcmp (строка (k), '-')) || (strcmp (строка (k), '|')) || (strcmp (строка (k), 'h')) || (strcmp (строка (к), 'б'))
            песня (k-2, lfreqN) = 0;
        еще
            song (k-2, lfreqN) = w (lfreqv, double (line (k)));
        конец
    конец
    строка = fgetl (файл);
конец
fclose (файл);
% это будет держать песню
мелодия = [];
vols = нули (1,6);
playf = нули (1,6);
для songIndex = 1: размер (песня, 1)
    ctune = [];
    для k = 1: 6
        if song (songIndex, k) == 0
            объемы (к) = 2 * объемы (к) / 4;
        еще
            объем (к) = 1;
            playf (k) = песня (songIndex, k);
        конец
        ctune (к, 1: т * 8192) = тт (к) * Sin (0,5 * пи * playf (к) * (1: (т * 8192)) / 8192);
    конец
    мелодия = [мелодия мелодия];
конец
soundsc (сумма (настройка));

Вот ссылка на звук первого тестового входа.

Вот ссылка на звук третьего тестового входа. (Звездный баннер или мороженое?)

Второй тестовый ввод звучал для меня довольно плохо, но это может быть потому, что он использует много bs и hs, которые скрипт игнорирует.

Как вы можете слышать, качество печати не совсем такое же, как у оригинала. Звучит так, будто на заднем плане играет метроном. Я думаю, что эти мелодии имеют характер.

sudo rm -rf slash
источник
Вау, это звучит как музыкальная шкатулка ... Действительно приятно!
бета-распад
5

Python 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 , куда я также загрузил результаты примеров, предоставленных OP. Я также попробовал это на некоторых из моих собственных вкладок. Довольно странно слышать, как пианино играет на нем, но это неплохо.

Примеры:

Я добавил несколько прямых ссылок, но не уверен, как долго они остаются, поэтому я сохраню старые ссылки для скачивания.

  1. Вес или игра
  2. Пахнет духом подростка или игрой
  3. Звезда раскачивается баннером или игрой
  4. Мелодия Мэтти или игра
  5. дм мелодия или игра

И вкладка из мелодии Мэтти (моя любимая) ниже:

    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 ударов в минуту ?! Я надеюсь, что это не последний удар ...
Beta Decay
Ха-ха, я немного обманываю. 2/3из этих «ударов» на самом деле тире.
Мэтти
Вау, мелодия Мэтти звучит довольно круто. Каково это на гитаре?
бета-распад
1
Спасибо @BetaDecay, это однажды созданная мной мелодия (базовая линия), вдохновленная голубой луной Томми Эммануила ( youtube.com/watch?v=v0IY3Ax2PkY ). Но это звучит не так хорошо, как то, как он это делает.
Мэтти
4

Java Script

Примечание: использует аудио комплект для веб-разработки; Это выход из Лиги IE; Проверено в Google Chrome

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

Нажмите, чтобы запустить программу

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

Джава

Эта программа преобразует табуляцию в 16-битный формат WAV.

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

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

Он «поддерживает» hammering ( h) и pulling ( p), игнорируя их, поскольку у меня действительно не было времени, чтобы заставить их звучать слишком по-разному. Результат звучит немного как гитара, хотя (провел несколько часов, анализируя мою гитару в Audacity).

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

Вы можете попробовать настроить константы в начале кода. Особенно снижение silenceRateчасто приводит к улучшению качества.

Пример результатов

Код

Я хочу предупредить любого новичка в Java: не пытайтесь чему-то научиться из этого кода, он ужасно написан. Кроме того, он был написан быстро и за 2 сеанса, и его не предполагалось использовать снова, поэтому он не имеет комментариев. (Могу добавить немного позже: 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
источник
Я знаю, что не указал это, но не могли бы вы опубликовать несколько тестов, которые люди могут слушать, как в других ответах?
бета-распад
@BetaDecay Обновил мой ответ, теперь есть куча тестов
PurkkaKoodari
Эти ссылки не работают: /
Beta Decay
@BetaDecay Я дважды проверил другое соединение в режиме инкогнито браузера, которым я не пользуюсь. Они работают на меня, по крайней мере.
PurkkaKoodari
Хорошо, мне нравится, что твоя версия Мэттиса очень сильно настроена, хотя основание иногда трудно услышать.
Мэтти