Using the following library:
https://introcs.cs.princeton.edu/java/stdlib/
I am trying to Play a tone in java for .4 seconds from a file using Stdin.fromFile using an array. I can write the code and make it play using playTone (double, double) the code is as follows:
package csc402;
import stdlib.StdAudio;
import stdlib.StdIn;
public class PlaySong {
public static void playTone(double frequency, double duration) {
double[] values = StdIn.readAllDoubles();
final int sliceCount = (int) (StdAudio.SAMPLE_RATE * duration);
final double[] slices = new double[sliceCount+1];
for (int i = 0; i <= sliceCount; i++) {
slices[i] = Math.sin(2 * Math.PI * i * frequency / StdAudio.SAMPLE_RATE);
}
StdAudio.play(slices);
}
public static void main(String[] args) {
StdIn.fromFile("data/a2song.txt");
playTone (0,.4);
StdAudio.close();
System.exit(0);
}
}
My a2song.txt file is as follows
278.4375
278.4375
417.1849
417.1849
468.2742
468.2742
417.1849
My question is what am I missing ? The code runs fine but isn't playing the tone.
Here is an example from the stdlib doc to play one tone:
(you probably have seen it already as your code looks like it)
https://introcs.cs.princeton.edu/java/15inout/Tone.java.html
public class Tone {
public static void main(String[] args) {
double hz = Double.parseDouble(args[0]); // frequency in Hz
double duration = Double.parseDouble(args[1]); // duration in seconds
int n = (int) (StdAudio.SAMPLE_RATE * duration);
// build sine wave with desired frequency
double[] a = new double[n+1];
for (int i = 0; i <= n; i++) {
a[i] = Math.sin(2 * Math.PI * i * hz / StdAudio.SAMPLE_RATE);
}
// play using standard audio
StdAudio.play(a);
}
}
That plays one tone, and each value in your file is one tone, so you need to call a similar method for each value of your file.
That works for me: (I did not use the StdIn.fromFile as it does not compile, I hardcoded the values in the array instead)
public static void main(String[] args) {
//StdIn.fromFile("data/a2song.txt");
double[] values = new double[] {278.4375, 278.4375, 417.1849, 417.1849, 468.2742, 468.2742, 417.1849};
for(double value : values) {
playTone (value,.4);
}
StdAudio.close();
System.exit(0);
}
public static void playTone(double frequency, double duration) {
int n = (int) (StdAudio.SAMPLE_RATE * duration);
// build sine wave with desired frequency
double[] a = new double[n+1];
for (int i = 0; i <= n; i++) {
a[i] = Math.sin(2 * Math.PI * i * frequency / StdAudio.SAMPLE_RATE);
}
// play using standard audio
StdAudio.play(a);
}
Related
I got my wave file creator working and I split it into three classes, I also made a sinewave generator that inherits from an abstract class called waveform and I can export 8 and 16 bit mono or sterio sine waves. I am trying to make a class called TriangleWave Generator to output a triangle wave tone, but I can't get the algebra from https://en.wikipedia.org/wiki/Triangle_wave#, the first formula, to work. It will only export the highest harmonic stated and not blend them together with the fundamental
Sample length: length in seconds
point: individual sample
amp limit: the highest position possible
harmonics: the number of harmonics to use to make the waveform 1 = fundamental, 2 = 1st overtone, 3 = 2nd overtone.......
frequency: fundamental frequency (Middle C = 261.63)
sample rate = 44100; (CD quality)
triangle Samples array: sample data
This is my code
public class TriangleGenerator extends Waveform {
// constants
public static final int HARMONIC_COUNT = 16;
// instance variabls
int harmonics;
int[] triangleSample;
int addCount;
// constructor
public TriangleGenerator(double amplitude, double frequency, int bitRate, double duration, int harmonics) {
super(amplitude, frequency, bitRate, duration);
// sample data
triangleSample = new int[sampleLength];
calculateAmpLimit();
this.harmonics = harmonics;
}
// one arg cunstructor
public TriangleGenerator(double frequency) {
this(AMPLITUDE, frequency, BIT_RATE, DURATION, HARMONIC_COUNT);
}
// no args constructor
public TriangleGenerator() {
this(AMPLITUDE, FREQUENCY, BIT_RATE, DURATION, HARMONIC_COUNT);
}
#Override
public int[] generateWaveForm() {
// generate the actual waveform
for (int i = 0; i < sampleLength; i++) {
point = (int)(ampLimit * ((8 / Math.pow(Math.PI, 2)) * sumnate(harmonics - 1, Math.pow(-1, addCount))
* Math.pow(harmonics, -2) * Math.sin(2 * Math.PI * frequency * harmonics * i / SAMPLE_RATE)));
triangleSample[i] = point;
}
// return the sample data
return triangleSample;
}
public double sumnate(int n, double adder) {
double sum = 0;
for (addCount = 0; addCount <= n; addCount++) {
sum += adder;
}
return sum;
}
}
In the formula for the triangle wave:
the mode number n is dependent on the harmonic label i:
This means that it must also be summed over the components
which doesn't happen in the current implementation. One possible implementation is:
public int[] generateWaveForm() {
for (int t = 0; t < sampleLength; t++) {
triangleSample[t] = (int)(ampLimit * 8.0 / Math.pow(Math.PI, 2.0) * getDataPoint(t, N));
}
return triangleSample;
}
private double getDataPoint(int t, int N) {
double sum = 0;
for (int i = 0; i <= N - 1; i++) {
sum += getHarmonicShare(t, i);
}
return sum;
}
private double getHarmonicShare(int t, int i) {
double n = 2.0 * i + 1.0;
return Math.pow(-1.0, i) * Math.pow(n, -2.0) * Math.sin(2.0 * Math.PI * frequency * (t / SAMPLE_RATE) * n);
}
Here t, i, n and N correspond to the values from the formula. frequency denotes the frequency. The remaining values correspond to the parameters of the posted code.
The curve reaches up to the value sampleLength / SAMPLE_RATE. The period is 1 / frequency and there are (sampleLength / SAMPLE_RATE) * frequency periods in the displayed frame.
Example:
sampleLength: 500
ampLimit: 100.00
SAMPLE_RATE: 44100.00
frequency: 261.63
sampleLength / SAMPLE_RATE: 0.0113378685
1 / frequency: 0.0038221916
(sampleLength / SAMPLE_RATE) * frequency: 2.9663265306
In the following the corresponding curve is shown for different N using JFreeChart:
Is there a reason you want to use sin to do this rather than produce the straight lines directly by a linear formula? There is an advantage in using sin in that you don't get harmonic distortion from aliasing, but if that's an issue then you can oversample. The issue is that sin is much slower than basic arithmetic.
I am really struggling to figure this out. Essentially I am trying to find what frequency is being played via the mic. To my understand, I need to bruteforce the Goertzel algorithm. So essentially I just try every frequency using the Goertzel algorithm until I find the correct one. However, I do not understand how I actually know when the Goertzel algorithm has found the correct algorithm. Could someone please help me.
MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private Button recordButton;
private TextView result;
private AudioRecord recording;
private static final int RECORDER_SAMPLERATE = 10000;
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
int bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);
double[] dbSample = new double[bufferSize];
short[] sample = new short[bufferSize];
private int frequency = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recordButton = findViewById(R.id.recordButton);
result = findViewById(R.id.resultTextView);
recordButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
recording = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, RECORDER_SAMPLERATE,
RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING, bufferSize);
recording.startRecording();
int bufferReadResult = recording.read(sample, 0, bufferSize);
for (int j = 0; j < bufferSize && j < bufferReadResult; j++) {
dbSample[j] = (double) sample[j];
goertzel.processSample(dbSample[j]);
}
// Is this correct?
magnitude = Math.sqrt(goertzel.getMagnitudeSquared());
if(magnitude > maxMagnitude){
maxMagnitude = magnitude;
System.out.println("Freq is: " + Integer.toString(frequency));
}
goertzel.resetGoertzel();
frequency += 1;
}
});
}
}
Goertzel.java
public class Goertzel {
private float samplingRate;
private float targetFrequency;
private long n;
private double coeff, Q1, Q2;
private double sine, cosine;
public Goertzel(float samplingRate, float targetFrequency, long inN) {
this.samplingRate = samplingRate;
this.targetFrequency = targetFrequency;
n = inN;
}
public void resetGoertzel() {
Q1 = 0;
Q2 = 0;
}
public void initGoertzel() {
int k;
float floatN;
double omega;
floatN = (float) n;
k = (int) (0.5 + ((floatN * targetFrequency) / samplingRate));
omega = (2.0 * Math.PI * k) / floatN;
sine = Math.sin(omega);
cosine = Math.cos(omega);
coeff = 2.0 * cosine;
resetGoertzel();
}
public void processSample(double sample) {
double Q0;
Q0 = coeff * Q1 - Q2 + sample;
Q2 = Q1;
Q1 = Q0;
}
public double[] getRealImag(double[] parts) {
parts[0] = (Q1 - Q2 * cosine);
parts[1] = (Q2 * sine);
return parts;
}
public double getMagnitudeSquared() {
return (Q1 * Q1 + Q2 * Q2 - Q1 * Q2 * coeff);
}
}
You've asked about brute-forcing Goertzel specifically, so here is an annotated JUnit test that illustrates a reasonable approach:
public class TestGoertzel
{
private float[] freqs;
private Goertzel[] goertzels;
private static final int RECORDER_SAMPLERATE = 10000;
private static final int INPUT_SAMPLES = 256; //Roughly 26 ms of audio. This small array size was
//chosen b/c the number of frequency "bins" is typically related to the number of input samples,
//for engineering applications. If we only check 256 samples of audio, our "DFT" need only include
//128 output "bins". You can resize this to suit, but keep in mind that the processing time will
//increase exponentially.
#Test
public void test()
{
freqs = new float[INPUT_SAMPLES / 2]; //To prevent frequency-domain aliasing, we cannot test for 256 frequencies; only the first 128.
goertzels = new Goertzel[freqs.length];
for(int n = 0; n < freqs.length; ++n)
{
freqs[n] = n * RECORDER_SAMPLERATE / INPUT_SAMPLES; //Determine the frequency of a wave that can fit exactly n cycles in a block of audio INPUT_SAMPLES long.
//Create a Goertzel for each frequency "bin":
goertzels[n] = new Goertzel(RECORDER_SAMPLERATE, freqs[n], INPUT_SAMPLES);
goertzels[n].initGoertzel(); //Might as well create them all at the beginning, then "reset" them as necessary.
}
//This gives you an idea of the quality of output that can be had for a real signal from your
//microphone. The curve is not perfect, but shows the "smearing" characteristic of a wave
//whose frequency does not fall neatly into a single "bin":
testFrequency(1500.0f);
//Uncomment this to see a full unit test:
//for(float freq : freqs)
//{
// testFrequency(freq);
//}
}
private void testFrequency(float freqHz)
{
System.out.println(String.format("Testing input signal of frequency %5.1fHz", freqHz));
short[] audio = generateAudioWave(freqHz, (short) 1000);
short[] magnitudes = detectFrequencies(audio);
for(int i = 0; i < magnitudes.length; ++i)
{
System.out.println(String.format("%5.1fHz: %d", freqs[i], magnitudes[i]));
}
}
private short[] generateAudioWave(float freqHz, short peakAmp)
{
short[] ans = new short[INPUT_SAMPLES];
float w0 = (float) ((2 * Math.PI) * freqHz / RECORDER_SAMPLERATE);
for(int i = 0; i < ans.length; ++i)
{
ans[i] = (short) (Math.sin(w0 * i) * peakAmp);
}
return ans;
}
private short[] detectFrequencies(short[] audio)
{
short[] ans = new short[freqs.length];
for(int i = 0; i < goertzels.length; ++i)
{
Goertzel goertzel = goertzels[i];
goertzel.resetGoertzel();
for(short s : audio)
{
goertzel.processSample((double) s);
}
ans[i] = (short) (Math.sqrt(goertzel.getMagnitudeSquared()) * 2 / INPUT_SAMPLES);
}
return ans;
}
}
Basically, for every 256 samples of audio you read in, you take that array, and run it past an array of Goertzels which cover the frequencies you are interested in (each Goertzel only measures one frequency). That gives you an output spectrum. You may interpret that spectrum how you choose; I took your question to mean, "how do you find the frequency of the LOUDEST component of the input audio?". In that case, you would search the return value of detectFrequencies() for the largest magnitude. The corresponding member of freqs is your answer.
The fact is, you probably don't want Goertzel, you want an FFT, due to FFT's superior "computational efficiency". Because Goertzel is somewhat slower (to cover a spectrum as fully as an FFT), you may have trouble getting this answer to run in real time.
As an aside, I don't think a samplerate of 10000 is supported, on Android.
I have written following code for population evolution (Genetic Algorithm Implementation):
Individual.java
import java.util.Random;
public class Individual {
public static int SIZE = 500;
private int[] genes = new int[SIZE];
private double fitnessValue = 0.0;
// Getters and Setters
public void setGene(int index,int gene){
this.genes[index] = gene;
}
public int getGene(int index){
return this.genes[index];
}
public void setFitnessValue(double fitness){
this.fitnessValue = fitness;
}
public double getFitnessValue(){
return this.fitnessValue;
}
//Function to generate a new individual with random set of genes
public void generateIndividual(){
Random rand = new Random();
for(int i=0;i<SIZE;i++){
this.setGene(i, rand.nextInt(2));
}
}
//Mutation Function
public void mutate(){
Random rand = new Random();
int index = rand.nextInt(SIZE);
this.setGene(index, 1-this.getGene(index)); // Flipping value of gene
}
//Function to set Fitness value of an individual
public int evaluate(){
int fitness = 0;
for(int i=0; i<SIZE; ++i) {
fitness += this.getGene(i);
}
this.setFitnessValue(fitness);
return fitness;
}
}
Population.java
import java.util.Random;
public class Population {
final static int ELITISM = 5;
final static int POP_SIZE = 200+ELITISM; //Population size + Elitism (1)
final static int MAX_ITER = 10000;
final static double MUTATION_RATE = 0.05;
final static double CROSSOVER_RATE = 0.7;
public static int generation = 2;
private static Random rand = new Random();
private double totalFitness;
private Individual[] pop;
//Constructor
public Population(){
pop = new Individual[POP_SIZE];
//Initialising population
for(int i=0;i<POP_SIZE;i++){
pop[i] = new Individual();
pop[i].generateIndividual();
}
//Evaluating current population
this.evaluate();
}
//Storing new generation in population
public void setPopulation(Individual[] newPop) {
System.arraycopy(newPop, 0, this.pop, 0, POP_SIZE);
}
//Method to find total fitness of population
public double evaluate(){
this.totalFitness = 0.0;
for (int i = 0; i < POP_SIZE; i++) {
this.totalFitness += pop[i].evaluate();
}
return this.totalFitness;
}
//Getters
public Individual getIndividual(int index) {
return pop[index];
}
//Function to find fittest individual for elitism
public Individual getFittest() {
Individual fittest = pop[0];
for (int i = 0; i < POP_SIZE; i++) {
if (fittest.getFitnessValue() <= getIndividual(i).getFitnessValue()) {
fittest = getIndividual(i);
}
}
return fittest;
}
//CROSSOVER Function : Takes 2 individuals and returns 2 new individuals
public static Individual[] crossover(Individual indiv1,Individual indiv2) {
Individual[] newIndiv = new Individual[2];
newIndiv[0] = new Individual();
newIndiv[1] = new Individual();
int randPoint = rand.nextInt(Individual.SIZE);
int i;
for (i=0; i<randPoint; ++i) {
newIndiv[0].setGene(i, indiv1.getGene(i));
newIndiv[1].setGene(i, indiv2.getGene(i));
}
for (; i<Individual.SIZE; ++i) {
newIndiv[0].setGene(i, indiv2.getGene(i));
newIndiv[1].setGene(i, indiv1.getGene(i));
}
return newIndiv;
}
//Roulette Wheel Selection Function
public Individual rouletteWheelSelection() {
double randNum = rand.nextDouble() * this.totalFitness;
int idx;
for (idx=0; idx<POP_SIZE && randNum>0; idx++) {
randNum -= pop[idx].getFitnessValue();
}
return pop[idx-1];
}
//Main method
public static void main(String[] args) {
Population pop = new Population();
Individual[] newPop = new Individual[POP_SIZE];
Individual[] indiv = new Individual[2];
//Current Population Stats
System.out.print("Generation #1");
System.out.println("Total Fitness = "+pop.totalFitness);
System.out.println("Best Fitness = "+pop.getFittest().getFitnessValue());
int count;
for(int iter=0;iter<MAX_ITER;iter++){
count =0;
//Elitism
newPop[count] = pop.getFittest();
count++;
//Creating new population
while(count < POP_SIZE){
//Selecting parents
indiv[0] = pop.rouletteWheelSelection();
indiv[1] = pop.rouletteWheelSelection();
// Crossover
if (rand.nextDouble() < CROSSOVER_RATE ) {
indiv = crossover(indiv[0], indiv[1]);
}
// Mutation
if ( rand.nextDouble() < MUTATION_RATE ) {
indiv[0].mutate();
}
if ( rand.nextDouble() < MUTATION_RATE ) {
indiv[1].mutate();
}
// add to new population
newPop[count] = indiv[0];
newPop[count+1] = indiv[1];
count += 2;
}
// Saving new population in pop
pop.setPopulation(newPop);
//Evaluating new population
pop.evaluate();
System.out.println("Generation #"+ generation++);
System.out.print("Total Fitness = " + pop.totalFitness);
System.out.println(" ; Best Fitness = " +pop.getFittest().getFitnessValue());
}
Individual bestIndiv = pop.getFittest();
}
}
I have been asked to test my algorithm using following functions:
https://en.wikipedia.org/wiki/Test_functions_for_optimization
Test functions for single objective optimisation
Can anyone explain how it is to be done? Explanation for any one function from the list would be helpful.
What the genes should represent
I'll assume the implementation of your genetic algorithm is correct, as that is beyond the scope of this question.
Right now your fitness function is defined to be the sum of all of the genes:
double fitness = 0;
for(int i=0; i<SIZE; ++i) {
fitness += this.getGene(i);
}
this.setFitnessValue(fitness);
This is a strange thing to do: let's think about an Individual witch will have a high fitness. I hope you see that there is no real optimum, Individuals will simply tend to increase each of their genes because that will archive a higher fitness.
A second problem is that the genes should represent something: what do the doubles in the gene array actually mean? Why do we care? A possible example would be to have them represent the behavior of Individuals in a simulation. That's of course a whole other topic, so we need them to mean something simple so it's easy to calculate their fitness.
Let's let the array have size 1 and let's say x = genes[0]. The Individuals will only have one gene: the x-coordinate. Now we need to define our fitness function, we'll pick Easom with y = 0. This is how I would define the new fitness function:
double fitness = -cos(x)*cos(0)*exp(-(pow(x-PI,2)+pow(0-PI,2)));
With of course the appropriate imports at the top of the class:
import static java.lang.Math.*;
If your program does indeed optimize for fitness it should converge to x = PI. I quickly wrote my own (admittedly very ugly) implementation and it does indeed converge correctly.
One more thing: the genes should be a double[] instead of an int[], because incrementally optimizing a function doesn't really work when x can only be an int.
Why a gene array?
I think your assignment wants you to use an double array as the genes so you end up with a program that can optimize any function with any amount of variables. In programming it is always a good idea to write code that can be reused for multiple different things.
Feel free to ask any questions!
I tried to explain everything as clear as possible, but if you don't understand something feel free to ask!
So my code is printing out estimations of Pi using a for and while loop and a recursive method. It's all working except my compiler is saying there's a stack overflow error for the if statement in my recursive method.
public static final double REAL_PI = 3.14159;//PI is the value Mr.B gave us on the handout
public static double Pi = 0; //Pi is the value of Pi that this program calculates
public static int m = 0;
public static int c = 0;
public static void main (String [] args)
{
Algorithm(); //calls on method of calculating pi
System.out.println("Calculated pi: " + Pi); //prints out pi
countDigits(Pi); //calls on countdigits method
System.out.println("Number of digits: " + c); //has the computer print out the count because that's how many digits are the same
While();
Recursive(1, 0.0); //calls on estimate digits method
}
public static double Algorithm() //should return a double (pi)
{
for(m=1; m<=100000; m++)
{
Pi += 4*(Math.pow(-1, m-1)/((2*m)-1));//Math.pow uses math package to calculate a power to use the algorithm
}
return Pi;
}
public static int countDigits (double Pi)
{
int a = (int) Pi; //the int cast makes Pi and REAL_PI into integers so the program can compare each digit separately
int b = (int) REAL_PI;
int c = 0;
int count = 0;
while(a == b)//if m less then or equal to 100,000 then while loop runs
{
count ++;
a = (int) (Pi*(Math.pow(10,count))); //if a=b then the computer will multiply Pi and REAL_PI by 10
b = (int) (REAL_PI*(Math.pow(10,count)));
/*when you input a and b
* while loop compares them
* if a = b then loop continues until a doesn't equal b and loop ends
*/
}
c = count; //gives c the value of the count so it can be used outside the method
return count;
}
public static double While()
{
int m = 1;
Pi = 0.0;
while (countDigits(Pi) < 6)
{
Pi += 4*(Math.pow(-1, m-1)/((2*m)-1));
m++;
}
Pi = (int)(Pi * 1000000);
Pi = (double)(Pi/1000000);
System.out.println("Pi using while loop: " + Pi);
return Pi;
}
public static double Recursive(int m,double Pi)
{
Pi += 4*(Math.pow(-1, m-1)/((2*m)-1));
if (countDigits(Pi) < 6)
{
return Pi += Recursive(m+1,Pi);
}
Pi = (int)(Pi * 1000000);
Pi = (double)(Pi/1000000);
System.out.println("Pi using recursive: " + Pi);
return Pi;
}
}
The problem is that the Leibniz series for computing π converges EXTREMELY slowly. Using your program, I found that after 3663 iterations (when I killed the program), the values looked like this:
pi=3.141865802997432
pi=3.1413195787723875
pi=3.1418656538577117
pi=3.1413197278306884
Still only 3 decimal places, and it is going to take a long time even to be accurate to 4. The stack is not big enough to hold so many recursive calls, and eventually it will overflow.
I know about the Math.sin() and Math.cos() functions, but I'm wondering if there's a way I can create (or use an already-existing) a faster function, given that I don't care about pinpoint accuracy. I'm looking to execute a basic sin or cos calculation, and have it perform essentially as fast as possible. Would simply iterating the sigma a few times be any faster than Math.sin()?
Since you don't care much about accuracy store it in a table that is precomputed or only computed once, this is what I do when I want to avoid calls to Math which can be expensive when done alot.
Roughly
public class CosSineTable {
double[] cos = new double[361];
double[] sin = new double[361];
private static CosSineTable table = new CosSineTable();
private CosSineTable() {
for (int i = 0; i <= 360; i++) {
cos[i] = Math.cos(Math.toRadians(i));
sin[i] = Math.sin(Math.toRadians(i));
}
}
public double getSine(int angle) {
int angleCircle = angle % 360;
return sin[angleCircle];
}
public double getCos(int angle) {
int angleCircle = angle % 360;
return cos[angleCircle];
}
public static CosSineTable getTable() {
return table;
}
}
I leave the optimization of the loop and methods to you.
A pre-calculated table's the way to go. Here's an implementation:
static final int precision = 100; // gradations per degree, adjust to suit
static final int modulus = 360*precision;
static final float[] sin = new float[modulus]; // lookup table
static {
// a static initializer fills the table
// in this implementation, units are in degrees
for (int i = 0; i<sin.length; i++) {
sin[i]=(float)Math.sin((i*Math.PI)/(precision*180));
}
}
// Private function for table lookup
private static float sinLookup(int a) {
return a>=0 ? sin[a%(modulus)] : -sin[-a%(modulus)];
}
// These are your working functions:
public static float sin(float a) {
return sinLookup((int)(a * precision + 0.5f));
}
public static float cos(float a) {
return sinLookup((int)((a+90f) * precision + 0.5f));
}
On my laptop, these were about 6x faster than Math.sin.
I only used one table -- the cost of shifting a cosine into a sine wasn't really discernible.
I used floats, assuming that's what you'll likely use in your calculations, given your preference for performance over precision. It doesn't make much difference here, since the bottleneck is really just the array lookup.
Here are my benchmarks:
public static void main(String[] args) {
int reps = 1<<23;
int sets = 4;
Q.pl(" Trial sinTab cosTab sinLib");
for(int i = 0; i<sets; i++) {
Q.pf("%7d\t%7.2f\t%7.2f\t%7.2f\n", i, testSinTab(reps), testCosTab(reps), testSinLib(reps));
}
}
private static float[] sample(int n) {
Random rand = new Random();
float[] values = new float[n];
for (int i=0; i<n; i++) {
values[i] = 400*(rand.nextFloat()*2-1);
}
return values;
}
private static float testSinTab(int n) {
float[] sample = sample(n);
long time = -System.nanoTime();
for (int i=0; i<n; i++) {
sample[i] = sin(sample[i]);
}
time += System.nanoTime();
return (time/1e6f);
}
private static float testCosTab(int n) {
float[] sample = sample(n);
long time = -System.nanoTime();
for (int i=0; i<n; i++) {
sample[i] = cos(sample[i]);
}
time += System.nanoTime();
return time/1e6f;
}
private static float testSinLib(int n) {
float[] sample = sample(n);
long time = -System.nanoTime();
for (int i=0; i<n; i++) {
sample[i] = (float) Math.sin(sample[i]);
}
time += System.nanoTime();
return time/1e6f;
}
output:
Trial sinTab cosTab sinLib
0 102.51 111.19 596.57
1 93.72 92.20 578.22
2 100.06 107.20 600.68
3 103.65 102.67 629.86
You can try
http://sourceforge.net/projects/jafama/
It uses look-up tables, so it might actually be slower
than Math, especially if the tables are often evicted from CPU cache,
but for thousands of successive calls it can be quite faster.
It also seems slower during class load (maybe the JIT doesn't kicks in then yet),
so you might want to avoid it in that particular use-case.
I know this question is old, but I think it's the fastest java implementation sintable with precision to 65536 elements.
public class MathHelper {
private static double[] a = new double[65536];
public static final double sin(float f) {
return a[(int) (f * 10430.378F) & '\uffff'];
}
public static final double cos(float f) {
return a[(int) (f * 10430.378F + 16384.0F) & '\uffff'];
}
static {
for (int i = 0; i < 65536; ++i) {
a[i] = Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D);
}
}
}
Source: https://github.com/Bukkit/mc-dev/blob/master/net/minecraft/server/MathHelper.java