Java
Chương trình này chuyển đổi định dạng thành định dạng WAV 16 bit.
Đầu tiên, tôi đã viết một loạt các mã phân tích cú pháp. Tôi không chắc nếu phân tích cú pháp của tôi là hoàn toàn chính xác, nhưng tôi nghĩ rằng nó ổn. Ngoài ra, nó có thể sử dụng xác nhận nhiều hơn cho dữ liệu.
Sau đó, tôi tạo mã để tạo âm thanh. Mỗi chuỗi được tạo riêng. Chương trình theo dõi tần số, biên độ và pha hiện tại. Sau đó, nó tạo ra 10 âm bội cho tần số với biên độ tương đối được tạo thành và thêm chúng lên. Cuối cùng, các chuỗi được kết hợp và kết quả được chuẩn hóa. Kết quả được lưu dưới dạng âm thanh WAV, mà tôi đã chọn cho định dạng cực kỳ đơn giản (không sử dụng thư viện).
Nó "hỗ trợ" búa ( h
) và kéo ( p
) bằng cách bỏ qua chúng vì tôi thực sự không có thời gian để làm cho chúng nghe quá khác biệt. Mặc dù vậy, kết quả nghe hơi giống một cây đàn guitar (đã dành vài giờ để phân tích cây đàn guitar của tôi trong Audacity).
Ngoài ra, nó hỗ trợ uốn ( b
), giải phóng ( r
) và trượt ( /
và \
, có thể hoán đổi cho nhau). x
được thực hiện như tắt tiếng chuỗi.
Bạn có thể thử điều chỉnh các hằng số khi bắt đầu mã. Đặc biệt là hạ thấp silenceRate
thường dẫn đến chất lượng tốt hơn.
Kết quả ví dụ
Mật mã
Tôi muốn cảnh báo bất kỳ người mới bắt đầu Java: Bạn không cố gắng học hỏi bất cứ điều gì từ mã này, nó khủng khiếp bằng văn bản. Ngoài ra, nó được viết nhanh chóng và trong 2 phiên và nó không có nghĩa là sẽ được sử dụng một lần nữa nên nó không có ý kiến. (Có thể thêm một số sau: 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;
}
}