This commit is contained in:
Astatin3
2024-03-26 14:07:09 -06:00
parent f8d9da826c
commit f8d5729e52
24 changed files with 4318 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
/**
* @author Orlando Selenu
* Originally written in the Summer of 2008
* Based on the algorithms originally published by E. Oran Brigham "The Fast Fourier Transform" 1973, in ALGOL60 and FORTRAN
*/
public class FFT {
/**
* The Fast Fourier Transform (generic version, with NO optimizations).
*
* @param inputReal
* an array of length n, the real part
* @param inputImag
* an array of length n, the imaginary part
* @param DIRECT
* TRUE = direct transform, FALSE = inverse transform
* @return a new array of length 2n
*/
public static double[] fft(final double[] inputReal, double[] inputImag,
boolean DIRECT) {
// - n is the dimension of the problem
// - nu is its logarithm in base e
int n = inputReal.length;
// If n is a power of 2, then ld is an integer (_without_ decimals)
double ld = Math.log(n) / Math.log(2.0);
// Here I check if n is a power of 2. If exist decimals in ld, I quit
// from the function returning null.
// System.out.println(n);
// System.out.println(((int) ld) - ld);
if (((int) ld) - ld != 0) {
System.out.println("The number of elements is not a power of 2.");
return null;
}
// Declaration and initialization of the variables
// ld should be an integer, actually, so I don't lose any information in
// the cast
int nu = (int) ld;
int n2 = n / 2;
int nu1 = nu - 1;
double[] xReal = new double[n];
double[] xImag = new double[n];
double tReal, tImag, p, arg, c, s;
// Here I check if I'm going to do the direct transform or the inverse
// transform.
double constant;
if (DIRECT)
constant = -2 * Math.PI;
else
constant = 2 * Math.PI;
// I don't want to overwrite the input arrays, so here I copy them. This
// choice adds \Theta(2n) to the complexity.
for (int i = 0; i < n; i++) {
xReal[i] = inputReal[i];
xImag[i] = inputImag[i];
}
// First phase - calculation
int k = 0;
for (int l = 1; l <= nu; l++) {
while (k < n) {
for (int i = 1; i <= n2; i++) {
p = bitreverseReference(k >> nu1, nu);
// direct FFT or inverse FFT
arg = constant * p / n;
c = Math.cos(arg);
s = Math.sin(arg);
tReal = xReal[k + n2] * c + xImag[k + n2] * s;
tImag = xImag[k + n2] * c - xReal[k + n2] * s;
xReal[k + n2] = xReal[k] - tReal;
xImag[k + n2] = xImag[k] - tImag;
xReal[k] += tReal;
xImag[k] += tImag;
k++;
}
k += n2;
}
k = 0;
nu1--;
n2 /= 2;
}
// Second phase - recombination
k = 0;
int r;
while (k < n) {
r = bitreverseReference(k, nu);
if (r > k) {
tReal = xReal[k];
tImag = xImag[k];
xReal[k] = xReal[r];
xImag[k] = xImag[r];
xReal[r] = tReal;
xImag[r] = tImag;
}
k++;
}
// Here I have to mix xReal and xImag to have an array (yes, it should
// be possible to do this stuff in the earlier parts of the code, but
// it's here to readability).
double[] newArray = new double[xReal.length * 2];
double radice = 1 / Math.sqrt(n);
for (int i = 0; i < newArray.length; i += 2) {
int i2 = i / 2;
// I used Stephen Wolfram's Mathematica as a reference so I'm going
// to normalize the output while I'm copying the elements.
newArray[i] = xReal[i2] * radice;
newArray[i + 1] = xImag[i2] * radice;
}
return newArray;
}
/**
* The reference bit reverse function.
*/
private static int bitreverseReference(int j, int nu) {
int j2;
int j1 = j;
int k = 0;
for (int i = 1; i <= nu; i++) {
j2 = j1 / 2;
k = 2 * k + j1 - 2 * j2;
j1 = j2;
}
return k;
}
}
+34
View File
@@ -0,0 +1,34 @@
import org.code.theater.*;
import org.code.media.*;
// Utility function to make messing with code.org's libraries easier
public class Renderer extends Scene {
private Theater Theater;
private final Image image;
private final double delay = 0.5;
public final int width;
public final int height;
public Renderer() {
Theater = new Theater();
width = getWidth();
height = getHeight();
image = new Image(width, height);
}
public void refreshImage(){
this.drawImage(image, 0, 0, width);
pause(delay);
}
public void setPixel(int x, int y, Color color){
image.setPixel(x, y, color);
}
public void render() {
System.out.println("Sending video to client...");
Theater.playScenes(this);
}
}
+118
View File
@@ -0,0 +1,118 @@
import org.code.theater.*;
import org.code.media.*;
import java.util.Arrays;
public class Spectrogram {
public static void run(String filepath) {
//get raw double array containing .WAV data
//code.org has a function to get data from a .wav file, this makes things a lot easier
double[] rawData = SoundLoader.read(filepath);
int length = rawData.length;
Renderer r = new Renderer();
//initialize parameters for FFT
int WS = 512; //WS = window size
int OF = 8; //OF = overlap factor
int windowStep = WS/OF;
//initialize plotData array
int nX = (length-WS)/windowStep;
int nY = WS/2 + 1;
double[][] plotData = new double[nX][nY];
//apply FFT and find MAX and MIN amplitudes
double maxAmp = Double.MIN_VALUE;
double minAmp = Double.MAX_VALUE;
double amp_square;
double[] inputImag = new double[length];
System.out.println("Calculating Spectrogram...");
// Do the FFT Stuff
for (int i = 0; i < nX; i++){
Arrays.fill(inputImag, 0.0);
double[] WS_array = FFT.fft(Arrays.copyOfRange(rawData, i*windowStep, i*windowStep+WS), inputImag, true);
for (int j = 0; j < nY; j++){
// IDK, Stolen code. I think this converts the data from the fft
amp_square = (WS_array[2*j]*WS_array[2*j]) + (WS_array[2*j+1]*WS_array[2*j+1]);
if (amp_square == 0.0){
plotData[i][j] = amp_square;
}
else{
plotData[i][nY-j-1] = 10 * Math.log10(amp_square);
}
//find MAX and MIN amplitude
if (plotData[i][j] > maxAmp)
maxAmp = plotData[i][j];
else if (plotData[i][j] < minAmp)
minAmp = plotData[i][j];
}
}
//Normalization, to show the details in the spectrograph
final double diff = maxAmp - minAmp;
for (int i = 0; i < nX; i++){
for (int j = 0; j < nY; j++){
plotData[i][j] = (plotData[i][j]-minAmp)/diff;
}
}
//Video settings
//The docs get the sample rate wrong, by a factor of 10, lol.
final double SR = 44100;
// This is around the max framerate that code.org allows
final double maxFramerate = 2.05;
final int maxFrameCount = 120;
// Most ways to get the length of the audio require getting the bitrate of the audio. This seems to work.
final double audioLength = length/SR;
final int frameCount = Math.min((int)Math.round(audioLength*maxFramerate), maxFrameCount);
final double delay = Math.max(((audioLength/frameCount)-(double)(1/maxFramerate)), 0);
final int initialXOffset = (int)(r.width/2)*-1;
System.out.println("Rendering Video...");
System.out.println("Audio length: " + Math.round(audioLength) + " seconds");
// System.out.println(frameCount);
// System.out.println(delay);
r.playSound(filepath);
// Loop through all frames
for(int f = 0; f<=frameCount; f++){
int xOffset = initialXOffset+(int)(f*((nX+r.width)/frameCount));
// Loop through pixels in image
for(int y = 0; y<r.height; y++){
int specY = (int)((double)(((double)y/(double)r.height)*(double)nY));
for(int x = 0; x<r.width; x++){
if(x+xOffset<0 || x+xOffset+1 > nX){
// If pixel is outside image, set to black.
r.setPixel(x, y, Color.BLACK);
}else{
// Greyscale image
final int L = (int)(plotData[x+xOffset][specY]*255);
r.setPixel(x, y, new Color(L, L, L));
}
}
}
r.refreshImage();
// r.pause(delay);
}
r.render();
}
}
+19
View File
@@ -0,0 +1,19 @@
public class main {
public static void main(String[] args) {
// This is a spectrogram, that I made in-class.
// Code.org has a max framerate around 2 fps, so the visual isn't the best.
// Also, the audio doesn't sync up with the spectrograph the visual as well as it could.
///// My personal narrative: ////
// I have seen a spectrograph of a 56kb dialup connection before,
// So... Here is a spectrograph of a 56kb dialup connection.
/////////////////////
// Also, here is me using a method in the String class:
" ".trim();
Spectrogram.run("dialup.wav");
}
}