Skip to content

Commit

Permalink
Added voicebox
Browse files Browse the repository at this point in the history
  • Loading branch information
alexdcramer committed Jul 2, 2022
1 parent bdd9064 commit 84fe32e
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 2 deletions.
63 changes: 62 additions & 1 deletion src/main/java/net/oijon/algonquin/gui/GUI.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package net.oijon.algonquin.gui;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
Expand All @@ -17,6 +29,8 @@
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import net.oijon.algonquin.tts.IPA;
import net.oijon.algonquin.tts.trm.TRM;
import net.oijon.algonquin.tts.trm.Tube;


public class GUI extends Application {
Expand Down Expand Up @@ -59,7 +73,54 @@ public void handle(ActionEvent event) {
if (synthType.getValue().equals("Classic")) {
console.setText(IPA.createAudio(IPA.getFileNames(insert.getText())));
} else if (synthType.getValue().equals("TRM (highly experimental)")) {
console.setText("Sorry, but TRM isn't quite ready yet. However, you can use classic instead!");

//String consoleResult = "Generated from a value of 1: " + Float.toString(tube.generate(1)) + "\n";
//consoleResult += "Generated from a value of 2: " + Float.toString(tube.generate(2)) + "\n";
//consoleResult += "Generated from a value of 1.5: " + Float.toString(tube.generate((float) 1.5)) + "\n";
//consoleResult += "Generated from a value of 10: " + Float.toString(tube.generate(10)) + "\n";
//consoleResult += "Generated from a value of 30: " + Float.toString(tube.generate(30)) + "\n";
String message = "This is currently under development and is highly experimental!\n";
message += "However, below you will find debug information to help more easially develop this!\n";
message += "-----BEGIN TESTTUBE-----\n";
message += TRM.createTestTube() + "\n";
message += "-----END TESTTUBE-----\n";
message += "-----BEGIN SINEWAVE-----\n";
message += "Now, a .wav file will be created of a pure vocal output. This should be just a sine wave.\n";
try {
message += TRM.createTestWave();
} catch (IOException e) {
// TODO Auto-generated catch block
message += e.toString() + "\n";
}
message += "Now it will play!\n";
try {
Clip clip = AudioSystem.getClip();
AudioInputStream ais = AudioSystem.getAudioInputStream(
new File(System.getProperty("user.home") + "/AlgonquinTTS/testwave.wav").getAbsoluteFile()
);
clip.open(ais);
clip.start();
long fileLength = clip.getMicrosecondLength();
while(clip.getMicrosecondLength() != clip.getMicrosecondPosition())
{
}
}
catch (UnsupportedAudioFileException e) {
message += "Unsupported audio format: '" + System.getProperty("user.home") + "/AlgonquinTTS/testwave.wav" + "' - " + e.toString() + "\n";
}
catch (LineUnavailableException e) {
message += "Could not play '" + System.getProperty("user.home") + "/AlgonquinTTS/testwave.wav" + "' - " + e.toString() + "\n";
}
catch (IOException e) {
message += "Could not play '" + System.getProperty("user.home") + "/AlgonquinTTS/testwave.wav" + "' - " + e.toString() + "\n";
e.printStackTrace();
}
message += "If all went to plan, and you did not see any exceptions, you just heard the beep that will form the voicebox's raw output!\n";
message += "Of course, this won't be what the end product sounds like, the end product will have to go through several tubes.\n";
message += "These tubes represent parts of the mouth and nasal cavity, which should be able to approximate the sound of speaking!\n";
message += "-----END SINEWAVE-----\n";
console.setText(message);

} else {
console.setText("Unsupported synthesis type \'" + synthType.getValue() + "\'.");
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/net/oijon/algonquin/gui/GUILauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.io.IOException;

import net.oijon.algonquin.tts.TRM;
import net.oijon.algonquin.tts.trm.TRM;

public class GUILauncher {

Expand Down
1 change: 1 addition & 0 deletions src/main/java/net/oijon/algonquin/tts/IPA.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public static String createAudio(String[] fileNames) {
while(clip.getMicrosecondLength() != clip.getMicrosecondPosition())
{
}
is.close();
}
catch (UnsupportedAudioFileException e) {
exception = "unsupported audio format: '" + fileNames[i] + "' - " + e.toString() + "\n";
Expand Down
160 changes: 160 additions & 0 deletions src/main/java/net/oijon/algonquin/tts/trm/TRM.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package net.oijon.algonquin.tts.trm;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;

// Highly experimental.
public class TRM {

public static void makeTestSound() throws IOException {
final double sampleRate = 44100.0;
final double frequency = 440;
final double frequency2 = 90;
final double amplitude = 1.0;
final double seconds = 2.0;
final double twoPiF = 2 * Math.PI * frequency;
final double piF = Math.PI * frequency2;

float[] buffer = new float[(int)(seconds * sampleRate)];

for (int sample = 0; sample < buffer.length; sample++) {
double time = sample / sampleRate;
buffer[sample] = (float)(amplitude * Math.cos(piF * time) * Math.sin(twoPiF * time));
}

final byte[] byteBuffer = new byte[buffer.length * 2];

int bufferIndex = 0;
for (int i = 0; i < byteBuffer.length; i++) {
final int x = (int)(buffer[bufferIndex++] * 32767.0);

byteBuffer[i++] = (byte)x;
byteBuffer[i] = (byte)(x >>> 8);
}


if (new File(System.getProperty("user.home") + "/AlgonquinTTS").exists() == false) {
new File(System.getProperty("user.home") + "/AlgonquinTTS").mkdir();
}

File out = new File(System.getProperty("user.home") + "/AlgonquinTTS/out10.wav");

final boolean bigEndian = false;
final boolean signed = true;

final int bits = 16;
final int channels = 1;

AudioFormat format = new AudioFormat((float)sampleRate, bits, channels, signed, bigEndian);
ByteArrayInputStream bais = new ByteArrayInputStream(byteBuffer);
AudioInputStream audioInputStream = new AudioInputStream(bais, format, buffer.length);
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, out);
audioInputStream.close();
}

public static String createTestTube() {
String exception = "";
Tube testTube = new Tube(17);
Voicebox voiceBox = new Voicebox(5);

exception += "\nVoicebox wave: ";

for (int i = 0; i < testTube.getSampleRate()*2; i++) {
exception += voiceBox.frame() + ", ";
}

exception += ("outwards elements: " + Arrays.toString(testTube.out()) + "\n");
exception += "Full array: " + "\n";
for (int i = 0; i < testTube.getSampleRate(); i++) {
exception += testTube.getDelayLine()[i][0] + ", ";
}
exception += "\n";

for (int i = 0; i < testTube.getSampleRate(); i++) {
exception += testTube.getDelayLine()[i][1] + ", ";
}

return exception;
}

public static String createTestWave() throws IOException {
String exception = "";

Voicebox voiceBox = new Voicebox(1);

final double sampleRate = 44100.0;
final double seconds = 2.0;

exception += "Sample Rate: " + sampleRate + "\n";
exception += "Amplitude: 1\n";
exception += "Seconds: " + seconds + "\n";


float[] buffer = new float[(int)(seconds * sampleRate)];

for (int sample = 0; sample < buffer.length; sample++) {
buffer[sample] = voiceBox.frame();
}

final byte[] byteBuffer = new byte[buffer.length * 2];

int bufferIndex = 0;
for (int i = 0; i < byteBuffer.length; i++) {
final int x = (int)(buffer[bufferIndex++] * 32767.0);

byteBuffer[i++] = (byte)x;
byteBuffer[i] = (byte)(x >>> 8);
}


if (new File(System.getProperty("user.home") + "/AlgonquinTTS").exists() == false) {
new File(System.getProperty("user.home") + "/AlgonquinTTS").mkdir();
exception += "Created new directory in " + System.getProperty("user.home") + "/AlgonquinTTS" + "\n";
}

File out = new File(System.getProperty("user.home") + "/AlgonquinTTS/testwave.wav");
exception += "Created new file in " + System.getProperty("user.home") + "/AlgonquinTTS/testwave.wav" + "\n";

final boolean bigEndian = false;
final boolean signed = true;

exception += "Big Endian: " + bigEndian + "\n";
exception += "Signed: " + signed + "\n";

final int bits = 16;
final int channels = 1;

exception += "Bits: " + bits + "\n";
exception += "Channels: " + channels + "\n";

AudioFormat format = new AudioFormat((float)sampleRate, bits, channels, signed, bigEndian);
ByteArrayInputStream bais = new ByteArrayInputStream(byteBuffer);
AudioInputStream audioInputStream = new AudioInputStream(bais, format, buffer.length);
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, out);
audioInputStream.close();

return exception;
}

public static String createSchwaTubeTest() {
String exception = "";

int sampleRate = 2018; // L = c/F, where L is the length (17cm), c is the speed of sound (cm/s), and F is the sampling frequency
float delayLine[][] = new float[sampleRate][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < sampleRate; j++) {
delayLine[j][i] = (delayLine[j-1][i]);
}
}

return exception;
}

}
90 changes: 90 additions & 0 deletions src/main/java/net/oijon/algonquin/tts/trm/Tube.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package net.oijon.algonquin.tts.trm;

public class Tube {

private int length;
private int sampleRate;
private final int speedOfSound = 34300; //in cm/s
private double loss = 0.97; //3% loss
private float[][] delayLine;

public Tube (int length) {
this.length = length;
// L = c/F, where L is the length (17cm), c is the speed of sound (cm/s), and F is the sampling frequency
sampleRate = speedOfSound/this.length;
//Creates the delay array based off
delayLine = new float[sampleRate][2];
}

//create and manage a delay line
//returns false if there is still room before sound makes its way out
//returns true if its pushing the sound out to the next tube
//this is done by checking if the last part of the array for the sample rate is filled.
public boolean push(float pushAmount) {
for (int i = 0; i < sampleRate-1; i++) {
delayLine[i+1][0] = delayLine[i][0];
}
delayLine[0][0] = pushAmount;
delayLine[sampleRate-1][1] = -1*delayLine[sampleRate-1][0];
for (int i = sampleRate-1; i > 0; i--) {
delayLine[i-1][0] = delayLine[i][0];
}
if (Float.isNaN(delayLine[sampleRate-1][0])) {
return false;
} else {
return true;
}
}

//convert to 44.1 kHz by adding new data to follow the slope.
//i could very well be misunderstanding something about this in the paper by Manzara
//if so, this method will probably be replaced.
public float[][] standardize() {
float[][] standard = new float[44100][2];
int multiple = (int)(44100/sampleRate);
for (int h = 0; h < 2; h++) {
for (int i = 0; i < sampleRate; i++) {
float slope = 1;
if (i+1 < sampleRate) {
slope = (delayLine[i+1][h]-delayLine[i][h])/((i+1)-i);
} else {
slope = (delayLine[sampleRate-1][h]-delayLine[i][h])/((sampleRate-1)-i);
}
for (int j = 0; j < multiple; j++) {
//could be optimized more
if (j == 0) {
standard[i*multiple][h] = delayLine[i][h];
} else {
standard[j][h] = standard[j-1][h]*slope;
}
}
}
}

return standard;
}

public float[] out() {
float[] out = new float[2];
out[0] = delayLine[sampleRate-1][0];
out[1] = delayLine[sampleRate-1][1];
return out;
}

//debug only, should not be used in production
public float[][] getDelayLine() {
return delayLine;
}
public int getLength() {
return length;
}
public int getSampleRate() {
return sampleRate;
}
public double getLoss() {
return loss;
}
public void setLoss(double loss) {
this.loss = loss;
}
}
26 changes: 26 additions & 0 deletions src/main/java/net/oijon/algonquin/tts/trm/Voicebox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.oijon.algonquin.tts.trm;

public class Voicebox {

private float intensity;
private int frameCount = 0;

public Voicebox(float intensity) {
this.intensity = intensity;
}
public float frame() {
float amount = (float) ((Math.sin(frameCount*(float)(1F/8F)))*intensity);
frameCount++;
return amount;
}
public int getFrameCount() {
return frameCount;
}
public float getIntensity() {
return intensity;
}
public void setIntensity(float intensity) {
this.intensity = intensity;
}

}

0 comments on commit 84fe32e

Please sign in to comment.