为我播放一首歌


23

挑战

给定吉他制表法,您必须输出该制表符代表的歌曲。这可能是计算机的扬声器或音频文件(.wav,.mp3,.midi,.aiff等)的声音。还将有第二个输入用于计时。

这些选项卡可以通过文件输入,也可以直接输入到STDIN。该选项卡将采用ASCII格式

规格

所有选项卡均适用于6支标准E调弦的六弦吉他:E2(82.41 Hz),A2(110.00 Hz),D3(146.83 Hz),G3(196.00 Hz),B3(246.94 Hz),E4(329.63 Hz)。

您必须满足的唯一技术(除了正常采摘之外)是:

  • 弯曲(永远是半色调弯曲)
  • 锤击
  • 脱下
  • 向上/向下滑动

由于您无法合成静音字符串的声音,因此请x视为-

弯曲时,再次输出从未弯曲到弦到弯曲再到未弯曲的完整过渡。

第二个输入将是选项卡上每个符号表示的时间(以秒为单位)。例如:

输入:

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

使用Timing时0.5,因为有几3列符号(但没有注释),所以输出的音频文件为(3*0.5=1.51.5秒静默。

示例标签

1- 体重(杰克·怀特(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- 闻起来像青少年的精神

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%),则调至最接近的1Hz不够精确,无法获得良好的音调声音。当然,这是一场流行竞赛,可以接受不佳的调音,但不会赢。
级圣河

非常有创意的比赛!在查看了ASCII格式的链接之后,我可以理解示例2(因为我听过这首歌),但是由于我不了解吉他,因此我认为挑战难度很大。除了Audacity的基本用法外,我对音频处理也几乎没有经验。
mbomb007

MIDI是否算作“音频文件”?
orlp

@orlp是的,它确实有效
Beta

1
好,以备将来参考:v *(2 ^(f / 12))= x; v =弦的频率;f = Fret(选项卡上的数字);x =播放频率;制表符也不会告诉您音符的长度;您的程序必须精明。
格兰特·戴维斯

Answers:


7

的MATLAB

这还没有完成。我使用了一种快速而肮脏的方法来尽可能轻松地制作音频。我使用的方法很难实现弯曲/锤打(在此之前,我也从未听过这些话)。

综上所述,此脚本将读取一个名为“ inputs.txt”的文本文件,其中包含所需的ascii-tab并播放歌曲。

%定时
t = 0.25;当然,这行可能是't = input('timing:');
        %如果您将ta的值设为t * 8192不是整数,则有些
        %stuff将失败
频率百分比和额外的变量,以便稍后再懒惰
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 = frequency和f = fret获取频率的%function
w = @(v,f)v *(2 ^(f / 12));
%获取输入并开始大循环
文件= fopen('input.txt');
行= fgetl(文件);
而ischar(line)
    行的第一个字符将为我们提供行频
    lfreqv = eval(line(1)); %频率
    lfreqN = eval([line(1),'N']); 频率水平指数
    %开始每行的小循环
    对于k = 3:(numel(line))-1
        if(strcmp(line(k),'-'))||(strcmp(line(k),'|'))||(strcmp(line(k),'h'))||(strcmp(line (k),'b'))
            歌曲(k-2,lfreqN)= 0;
        其他
            song(k-2,lfreqN)= w(lfreqv,double(line(k)));
        结束
    结束
    行= fgetl(文件);
结束
fclose(文件);
这将保留歌曲
tune = [];
vols = zeros(1,6);
playf = zeros(1,6);
对于songIndex = 1:size(song,1)
    ctune = [];
    对于k = 1:6
        如果song(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 = [tune ctune];
结束
soundsc(sum(tune));

这是第一个测试输入的声音的链接。

这是第三个测试输入的声音的链接。(星条旗或冰淇淋卡车?)

第二个测试输入听起来对我来说很糟糕,但这可能是因为它使用了脚本忽略的大量bs和hs。

如您所见,输出的质量与原始质量并不完全相同。听起来好像在后台播放节拍器。我认为这些曲调很有特色。


哇,听起来像个音乐盒……真好!
Beta Decay's

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. dm tune播放

以下是Matty的曲调(我的最爱)中的标签:

    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 BPM吗?我希望这不是最终的决定...
Beta Decay

哈哈,好吧,我有点作弊。2/3这些“节拍”实际上是破折号。
马蒂

哇,马蒂的音乐听起来很酷。吉他是什么感觉?
Beta Decay

1
感谢@BetaDecay,这是我曾经做过的调(基线),灵感来自汤米·伊曼纽尔(Tommy Emmanuel)的蓝色月亮(youtube.com/watch?v=v0IY3Ax2PkY)。但这听起来并不比他的表现好一半。
马蒂

4

Java脚本

注意:使用Web开发音频工具包;这是脱离IE联盟的方法;在谷歌浏览器中测试

您可以将选项卡放在文本区域中。IE,您可以将Matty的帖子从Matty的帖子中放到文本区域中(在注释上加上字母),它仍然可以正确解析。

点击运行程序

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

糟糕,您在编辑信息之前忘记了登录。
格兰特·戴维斯

我知道这首歌,但我不能给它起个名字……听起来听起来很酷
Beta Decay 2015年

2

爪哇

该程序将制表符转换为16位WAV格式。

首先,我编写了一大堆表格解析代码。我不确定我的解析是否完全正确,但是我认为可以。同样,它可以对数据使用更多的验证。

之后,我编写了代码以生成音频。每个字符串是单独生成的。该程序跟踪当前的频率,幅度和相位。然后,它为频率生成10个泛音,并包含相对的振幅,并将其相加。最后,将字符串进行组合,并对结果进行归一化。结果保存为WAV音频,我选择了它的超级简单格式(不使用任何库)。

它通过忽略它们来“支持”锤击(h)和拉动(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;
    }
}

我知道我没有指定它,但是您可以张贴一些人们可以听的测试用例,就像其他答案一样吗?
Beta Decay's

@BetaDecay更新了我的答案,现在进行了大量测试
PurkkaKoodari 2015年

这些链接不起作用:/
Beta Decay

@BetaDecay我以隐身模式使用了我未使用的浏览器,再次检查了另一个连接。他们至少为我工作。
PurkkaKoodari

很好,尽管有时很难听清基础知识,但我非常喜欢您的Mattys版本。
马蒂
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.