Skip to content

Instantly share code, notes, and snippets.

@Jobarion
Last active July 9, 2023 21:07
Show Gist options
  • Save Jobarion/c371bf526d36f337e6128214dc10d8c7 to your computer and use it in GitHub Desktop.
Save Jobarion/c371bf526d36f337e6128214dc10d8c7 to your computer and use it in GitHub Desktop.
THX Deep Note Generator
/*
MIT License
Copyright (c) 2018 Jonas Balsfulland
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.Arrays;
import java.util.Random;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.SourceDataLine;
/**
* THX Deep Note Sheet music
* https://i.kinja-img.com/gawker-media/image/upload/s--MHW4C38s--/c_scale,f_auto,fl_progressive,q_80,w_800/p2emaasjrbxi4tbhreyr.png
* @author Jonas
*/
public class THX {
public static final int SAMPLE_RATE = 128 * 1024;
private static double state = 0.0f;
private static double state11 = -1.0;
private static double state12 = -1.0;
private static double state13 = -1.0;
private static double state2 = -1.0;
private static double sign = 1.0f;
public static void main(String[] args) throws Exception {
File out = new File("out.wav");
final AudioFormat af = new AudioFormat(SAMPLE_RATE, 16, 1, true, true);
SourceDataLine line = AudioSystem.getSourceDataLine(af);
byte[] tone = generateTHX();
line.open(af, SAMPLE_RATE);
line.start();
play(line, tone);
line.drain();
line.close();
//save(tone, af, out);
}
private static byte[] generateTHX() throws Exception {
System.out.println("Generating THX Deep Note");
int[] targetAccord = {
1480, 1480, 1480,
1175, 1175, 1175,
880, 880, 880,
587, 587, 587,
440, 440, 440,
294, 294, 294,
220, 220,
147, 147,
110, 110,
73, 73,
55, 55
};
for (int i = 0; i < 18; i++) {
double mistake = 0.01;
targetAccord[i] *= 1 + (Math.random() * mistake - (mistake / 2));
}
int lineCount = targetAccord.length;
int speed = 4000;
int min = 200;
int max = 400;
int minSteps = 3;
int maxSteps = 10;
int durationRandom = speed * 3;
int durationWalk = speed * 2;
int durationHold = speed * 1;
int durationFade = speed * 1;
int maxChange = 50;
double[][] random = new double[lineCount][];
double[][] accordWalk = new double[lineCount][];
double[][] accordHold = new double[lineCount][];
double[][] accordFade = new double[lineCount][];
int[][] randomWalks = new int[lineCount][];
for (int i = 0; i < lineCount; i++) {
int steps = new Random().nextInt(maxSteps - minSteps) + minSteps;
randomWalks[i] = randomWalk(steps, min, max, maxChange);
}
int[][] accordWalks = new int[lineCount][2];
for (int i = 0; i < lineCount; i++) {
accordWalks[i] = new int[]{randomWalks[i][randomWalks[i].length - 1], targetAccord[i]};
}
int[][] accordHolds = new int[lineCount][2];
for (int i = 0; i < lineCount; i++) {
accordHolds[i] = new int[]{targetAccord[i], targetAccord[i]};
}
//Triangle waves sound best
for (int i = 0; i < lineCount; i++) {
System.out.println("Generating line " + (i + 1) + " / " + lineCount);
state = -1.0;
sign = 1.0;
random[i] = generateTone(randomWalks[i], durationRandom);
accordWalk[i] = generateTone(accordWalks[i], durationWalk);
accordHold[i] = generateTone(accordHolds[i], durationHold);
accordFade[i] = generateTone(accordHolds[i], durationFade);
}
System.out.println("Merging lines");
double[] randomMerged = merge(random);
double[] accordWalkMerged = merge(accordWalk);
double[] accordHoldMerged = merge(accordHold);
double[] accordFadeMerged = merge(accordFade);
System.out.println("Filtering noise");
float filterStart = 2000f;
float filterEnd = 6000f;
float walkFilterPercentageStart = 0.5f;
Filter filter = new Filter(filterStart, SAMPLE_RATE, Filter.PassType.Lowpass, 1);
for (int i = 0; i < randomMerged.length; i++) {
filter.update(randomMerged[i]);
randomMerged[i] = filter.getValue();
}
for (int i = 0; i < accordWalkMerged.length; i++) {
if(i >= accordWalkMerged.length * walkFilterPercentageStart) {
int c = (int)(i - accordWalkMerged.length * walkFilterPercentageStart);
float next = filterStart + (filterEnd - filterStart) * ((float) c / (float) (accordWalkMerged.length * walkFilterPercentageStart));
filter.changeFrequency(next);
}
filter.update(accordWalkMerged[i]);
accordWalkMerged[i] = filter.getValue();
}
for (int i = 0; i < accordHoldMerged.length; i++) {
filter.update(accordHoldMerged[i]);
accordHoldMerged[i] = filter.getValue();
}
for (int i = 0; i < accordFadeMerged.length; i++) {
filter.update(accordFadeMerged[i]);
accordFadeMerged[i] = filter.getValue();
}
double[] rising = concat(randomMerged, accordWalkMerged, accordHoldMerged);
System.out.println("Scaling lines");
scaleDouble(accordFadeMerged, 0x7fff, 0);
scaleDouble(rising, 1, 0x7fff);
double[] dTone = concat(rising, accordFadeMerged);
System.out.println("Generating final audio");
byte[] tone = doubleToBytesDirect(dTone);
return tone;
}
private static void scaleDouble(double[] arr, int start, int end) {
for (int i = 0; i < arr.length; i++) {
double volume = start + (end - start) * ((double) i / (double) arr.length);
arr[i] *= volume;
}
}
private static byte[] doubleToBytesDirect(double[] arr) {
byte[] data = new byte[arr.length * 2];
for (int i = 0; i < arr.length; i++) {
int masked = (int) arr[i];
data[i * 2 + 0] = (byte) ((masked >> 8) & 0xff);
data[i * 2 + 1] = (byte) ((masked >> 0) & 0xff);
}
return data;
}
private static int[] randomWalk(int steps, int min, int max, int maxChange) {
Random rand = new Random();
int[] arr = new int[steps];
arr[0] = rand.nextInt(max - min) + min;
for (int i = 1; i < arr.length; i++) {
int change = rand.nextInt(2 * maxChange) - maxChange;
arr[i] = Math.max(min, Math.min(max, arr[i - 1] + change));
}
return arr;
}
public static double[] concat(double[]... tones) {
int sum = Arrays.stream(tones).mapToInt(a -> a.length).sum();
double[] tone = new double[sum];
int offset = 0;
for (int i = 0; i < tones.length; i++) {
System.arraycopy(tones[i], 0, tone, offset, tones[i].length);
offset += tones[i].length;
}
return tone;
}
public static double[] merge(double[]... tones) {
int min = Arrays.stream(tones).mapToInt(a -> a.length).min().orElse(0);
double[] tone = new double[min];
for (int i = 0; i < min; i++) {
for (int j = 0; j < tones.length; j++) {
tone[i] += tones[j][i];
}
tone[i] = (tone[i] / tones.length);
}
return tone;
}
public static double[] generateTone(int[] freq, int msDuration) {
double[] sin = new double[SAMPLE_RATE * (msDuration / 1000)]; // parens to avoid arithmetic overflow
int fac = 1 + sin.length / (freq.length - 1);
for (int j = -1; j < sin.length + 1; j++) {
// sawtooth oscillator 1
int i = (j - 1) / fac;
double f = freq[i] + (freq[i + 1] - freq[i]) * ((double) ((j - 1) % fac) / (double) fac);
double period = (double) SAMPLE_RATE / (2 * f);
state11 += 1 / period;
if (state11 > 1.0) {
state11 = -1.0;
}
double osc11 = state11;
// sawtooth oscillator 2
i = j / fac;
f = freq[i] + (freq[i + 1] - freq[i]) * ((double) (j % fac) / (double) fac);
period = (double) SAMPLE_RATE / (2 * f);
state12 += 1 / period;
if (state12 > 1.0) {
state12 = -1.0;
}
double osc12 = state12;
// sawtooth oscillator 5
i = j / fac;
f = freq[i] + (freq[i + 1] - freq[i]) * ((double) (j % fac) / (double) fac);
period = (double) SAMPLE_RATE / (2 * f);
state13 += 1 / period;
if (state13 > 1.0) {
state13 = -1.0;
}
double osc13 = state13;
// take average of two saw oscillator to filter high freq
double osc1 = osc11 * 0.25 + osc12 * 0.5 + osc13 * 0.25;
// sine oscillator
i = j / fac;
f = freq[i] + (freq[i + 1] - freq[i]) * ((double) (j % fac) / (double) fac);
period = (double) SAMPLE_RATE / f;
state2 += 2.0 * Math.PI / period;
Math.sin(state2);
double osc2 = Math.sin(state2);
if (j >= 0 && j < sin.length) {
sin[j] = 0.4 * osc1 + 0.6 * osc2;
}
}
return sin;
}
public static double[] generateTriangle(int[] freq, int msDuration) {
double[] sin = new double[SAMPLE_RATE * msDuration / 1000];
int fac = 1 + sin.length / (freq.length - 1);
for (int j = 0; j < sin.length; j++) {
int i = j / fac;
double f = freq[i] + (freq[i + 1] - freq[i]) * ((double) (j % fac) / (double) fac);
double period = (double) SAMPLE_RATE / (f * 4);
state += sign / period;
if (state > 1.0 || state < -1.0) {
state = sign;
sign *= -1;
}
sin[j] = state;
}
return sin;
}
public static double[] generateSawtooth(int[] freq, int msDuration) {
double[] sin = new double[SAMPLE_RATE * msDuration / 1000];
int fac = 1 + sin.length / (freq.length - 1);
for (int j = 0; j < sin.length; j++) {
int i = j / fac;
double f = freq[i] + (freq[i + 1] - freq[i]) * ((double) (j % fac) / (double) fac);
double period = (double) SAMPLE_RATE / (2 * f);
state += 1 / period;
if (state > 1.0) {
state = -1.0;
}
sin[j] = state;
}
return sin;
}
public static double[] generateSquare(int[] freq, int msDuration) {
double[] sin = new double[SAMPLE_RATE * msDuration / 1000];
int fac = 1 + sin.length / (freq.length - 1);
for (int j = 0; j < sin.length; j++) {
int i = j / fac;
double f = freq[i] + (freq[i + 1] - freq[i]) * ((double) (j % fac) / (double) fac);
double period = (double) SAMPLE_RATE / f;
state += 2.0 * Math.PI / period;
double v = Math.sin(state);
if (v < -0.5) {
v = -1;
} else if (v > 0.5) {
v = 1;
}
sin[j] = v;
}
return sin;
}
public static double[] generateSine(int[] freq, int msDuration) {
double[] sin = new double[SAMPLE_RATE * msDuration / 1000];
int fac = 1 + sin.length / (freq.length - 1);
for (int j = 0; j < sin.length; j++) {
int i = j / fac;
double f = freq[i] + (freq[i + 1] - freq[i]) * ((double) (j % fac) / (double) fac);
double period = (double) SAMPLE_RATE / f;
state += 2.0 * Math.PI / period;
sin[j] = Math.sin(state);
}
return sin;
}
public static void play(SourceDataLine line, byte[] tone) {
line.write(tone, 0, tone.length);
}
public static void save(byte[] tone, AudioFormat format, File file) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(tone);
AudioInputStream ais = new AudioInputStream(bais, format, tone.length);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, file);
}
//https://stackoverflow.com/questions/28252665/how-to-implement-a-high-pass-filter-for-an-audio-signal
private static class Filter {
private final float resonance;
private final int sampleRate;
private final PassType passType;
public double value;
private double c, a1, a2, a3, b1, b2;
private double[] inputHistory = new double[2];
private double[] outputHistory = new double[3];
public Filter(float frequency, int sampleRate, PassType passType, float resonance) {
this.sampleRate = sampleRate;
this.passType = passType;
this.resonance = resonance;
changeFrequency(frequency);
}
public enum PassType {
Highpass,
Lowpass,
}
public void changeFrequency(float frequency) {
switch (passType) {
case Lowpass:
c = 1.0f / (float) Math.tan(Math.PI * frequency / sampleRate);
a1 = 1.0f / (1.0f + resonance * c + c * c);
a2 = 2f * a1;
a3 = a1;
b1 = 2.0f * (1.0f - c * c) * a1;
b2 = (1.0f - resonance * c + c * c) * a1;
break;
case Highpass:
c = (float) Math.tan(Math.PI * frequency / sampleRate);
a1 = 1.0f / (1.0f + resonance * c + c * c);
a2 = -2f * a1;
a3 = a1;
b1 = 2.0f * (c * c - 1.0f) * a1;
b2 = (1.0f - resonance * c + c * c) * a1;
break;
}
}
public void update(double newInput) {
double newOutput = a1 * newInput + a2 * this.inputHistory[0] + a3 * this.inputHistory[1] - b1 * this.outputHistory[0] - b2 * this.outputHistory[1];
this.inputHistory[1] = this.inputHistory[0];
this.inputHistory[0] = newInput;
this.outputHistory[2] = this.outputHistory[1];
this.outputHistory[1] = this.outputHistory[0];
this.outputHistory[0] = newOutput;
}
public double getValue() {
return this.outputHistory[0];
}
}
}
@Jobarion
Copy link
Author

@JIgtiey84
Copy link

bro why it isnt made in C
and doesnt have 20k lines of code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment