-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Version 0.1.5 vom 15.03.2017 inkl. vollständiger JavaDoc Dokumentationskommentare. Stand zur Abgabe der Facharbeit am 16.03.2017.
- Loading branch information
1 parent
77b459d
commit 43b68a1
Showing
6 changed files
with
2,044 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/** | ||
* Implementation der ADSR-Huellkurve | ||
* Fuer die Facharbeit vom 16.03.2017 | ||
* Objekte dieser Klasse fuehren die Berechnung | ||
* der Funktionswerte der Huellkurve aus. | ||
* 2017, Soeren Richter | ||
* soeren@dreieck-project.de | ||
* Version 0.1.5 | ||
* Code vollstaendig selbst geschrieben, fuer Hintergruende und | ||
* Referenzen siehe Abschnitt 3.6 der Facharbeit. | ||
* @author Soeren Richter | ||
* @version 0.1.5 | ||
*/ | ||
|
||
public class Envelope | ||
{ | ||
// konstante ADSR-Attribute der Huellkurve: | ||
private final double attack; | ||
private final double decay; | ||
private final double sustain; | ||
private final double release; | ||
|
||
// veraenderbare Hold-Zeit/Tonlaenge: | ||
private double length; | ||
|
||
/** | ||
* Konstruktor der Klasse Envelope. | ||
* @param pAttack Attack-Zeit der Huellkurve | ||
* @param pDecay Decay-Zeit der Huellkurve | ||
* @param pSustain Sustain-Amplitude der Huellkurve | ||
* @param pRelease Release-Zeit der Huellkurve | ||
*/ | ||
public Envelope(double pAttack, double pDecay, double pSustain, double pRelease) | ||
{ | ||
attack = pAttack; | ||
decay = pDecay; | ||
sustain = pSustain; | ||
release = pRelease; | ||
length = 1; | ||
} | ||
|
||
/** | ||
* Rueckgabe des Huellkurven-Funktionswertes zum Zeitpunkt time | ||
* @param time Zeitpunkt in s | ||
* @return Huellkurven-Funktionswert | ||
*/ | ||
public double getAmplitude(double time) | ||
{ | ||
double amp = 0; | ||
if (time <= attack) { | ||
amp = time/attack; | ||
} else if (time <= attack+decay && time > attack) { | ||
amp = (-1 + sustain) / decay * time + (1 - sustain) | ||
/ decay * (attack + decay) + sustain; | ||
} else if (time <= length && time > attack+decay) { | ||
amp = sustain; | ||
} else if (time > length) { | ||
amp = (-sustain) / release * time + sustain | ||
/ release * length + sustain; | ||
} | ||
return amp; | ||
} | ||
|
||
/** | ||
* Festlegen der Hold-/Tonlaenge | ||
* @param pLength Hold-/Tonlaenge in s | ||
*/ | ||
public void setLength(double pLength) | ||
{ | ||
length = pLength; | ||
} | ||
|
||
/** | ||
* Rueckgabe der vollstaendigen Huellkurvenlaenge inklusive Release-Zeit | ||
* @return vollstaendige Laenge der Huellkurve | ||
*/ | ||
public double getFullLength() | ||
{ | ||
return length+release; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/** | ||
* Implementation der Fourier-Reihen | ||
* Fuer die Facharbeit vom 16.03.2017 | ||
* Objekte dieser Klasse fuehren die Berechnung der Funktionswerte | ||
* der Fourier-Reihe ihrer entsprechnden Wellenform und Parameter aus. | ||
* 2017, Soeren Richter | ||
* soeren@dreieck-project.de | ||
* Version 0.1.5 | ||
* Code vollstaendig selbst geschrieben. Fuer Referenzen und mathematische | ||
* Grundlage siehe Kapitel 3 der Facharbeit. | ||
* @author Soeren Richter | ||
* @version 0.1.5 | ||
*/ | ||
|
||
public class Oscillator | ||
{ | ||
// Wellenform; "sine", "saw", "square" oder "triangle": | ||
private final String waveform; | ||
|
||
// Grenzfrequenz der berechnung der Fourier-Reihen-Summen | ||
private final int cutoff; | ||
|
||
/** | ||
* Konstruktor der Klasse Oscillator. | ||
* @param pWaveform Wellenform ("sine", "saw", "square" oder "triangle") | ||
* @param pCutoff Grenzfrequenz der berechnung der Fourier-Reihen-Summen | ||
*/ | ||
public Oscillator(String pWaveform, int pCutoff) | ||
{ | ||
waveform = pWaveform; | ||
cutoff = pCutoff; | ||
} | ||
|
||
/** | ||
* Berechnung des Funktionswertes einer Sinus-Funktion (Sine) der Frequenz frequency zum Zeitpunkt timeSample | ||
* @param frequency Frequenz in Hz | ||
* @param timeSample Zeitpunkt in s | ||
* @return Funktionswert | ||
*/ | ||
private double synthesizeSine(double frequency, double timeSample) | ||
{ | ||
return Math.sin(timeSample * frequency * Math.PI * 2); | ||
} | ||
|
||
// Die folgenden Funktionen nutzen die Fourier-Reihen der entsprechenden | ||
// Wellenformen zur Berechnung der Funktionswerte. Der Algorithmus ist | ||
// an das Pseudocode-Konzept (Beispiel: Rechteck/Square) angelehnt: | ||
/** | ||
* Funktion sq(t): | ||
* funktionswert = 0; | ||
* von order = 1 bis 22050/freq/2, Schritt 1: | ||
* funktionswert = funktionswert + | ||
* (sin(2 * PI * freq * (order * 2 - 1) * t) | ||
* / (order * 2 - 1)); | ||
* Rückgabe von 4 / PI * funktionswert; | ||
*/ | ||
|
||
/** | ||
* Berechnung des Funktionswertes einer Saegezahn-Funktion (Sawtooth) der Frequenz frequency zum Zeitpunkt timeSample | ||
* @param frequency Frequenz in Hz | ||
* @param timeSample Zeitpunkt in s | ||
* @return Funktionswert | ||
*/ | ||
private double synthesizeSaw(double frequency, double timeSample) | ||
{ | ||
double tempSample = 0; | ||
for (int order = 1; order <= cutoff/frequency; order++) { | ||
tempSample += (Math.sin(timeSample * frequency * order | ||
* Math.PI * 2) / order); | ||
} | ||
return (2 / Math.PI * tempSample); | ||
} | ||
|
||
/** | ||
* Berechnung des Funktionswertes einer Rechteck-Funktion (Square) der Frequenz frequency zum Zeitpunkt timeSample | ||
* @param frequency Frequenz in Hz | ||
* @param timeSample Zeitpunkt in s | ||
* @return Funktionswert | ||
*/ | ||
private double synthesizeSquare(double frequency, double timeSample) | ||
{ | ||
double tempSample = 0; | ||
for (int order = 1; order <= cutoff/frequency/2; order++) { | ||
tempSample += (Math.sin(timeSample * frequency * (order * 2 - 1) | ||
* Math.PI * 2) / (order * 2 - 1)); | ||
} | ||
return (4 / Math.PI * tempSample); | ||
} | ||
|
||
/** | ||
* Berechnung des Funktionswertes einer Dreieck-Funktion (Triangle) der Frequenz frequency zum Zeitpunkt timeSample | ||
* @param frequency Frequenz in Hz | ||
* @param timeSample Zeitpunkt in s | ||
* @return Funktionswert | ||
*/ | ||
private double synthesizeTriangle(double frequency, double timeSample) | ||
{ | ||
double tempSample = 0; | ||
for (int order = 1; order <= cutoff/frequency/2; order++) { | ||
tempSample += (Math.cos(timeSample * frequency * | ||
(order * 2 - 1) * Math.PI * 2)) | ||
/ ((order * 2 - 1)*(order * 2 - 1)); | ||
} | ||
return ((8 / (Math.PI*Math.PI)) * tempSample); | ||
} | ||
|
||
/** | ||
* Methode zur Ausgabe des Funktionswertes der festgelegten Wellenform der Frequenz frequency zum Zeitpunkt timeSample | ||
* @param frequency Frequenz in Hz | ||
* @param timeSample Zeitpunkt in s | ||
* @return Funktionswert | ||
*/ | ||
public float getSample(double frequency, double timeSample) | ||
{ | ||
if (null != waveform) switch (waveform) { | ||
case "sine": | ||
return (float)synthesizeSine(frequency, timeSample); | ||
case "saw": | ||
return (float)synthesizeSaw(frequency, timeSample); | ||
case "square": | ||
return (float)synthesizeSquare(frequency, timeSample); | ||
case "triangle": | ||
return (float)synthesizeTriangle(frequency, timeSample); | ||
default: | ||
return 0; | ||
} else return 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
/** | ||
* Synth-Klasse der Implementation von Fourier-Reihen zur Klangsynthese. | ||
* Fuer die Facharbeit vom 16.03.2017 | ||
* Dies ist "das Herz" der Anwendung. | ||
* Objekte dieser Klasse fuehren Berechnung und WAVE-Speichrung des Klanges aus. | ||
* 2017, Soeren Richter | ||
* soeren@dreieck-project.de | ||
* Version 0.1.5 | ||
* Code selbst geschrieben, WAVE-Speicherung basierend auf | ||
* http://stackoverflow.com/questions/3297749/java-reading-manipulating-and-writing-wav-files | ||
* und Abschnitt 2.2 sowie 4 der Facharbeit. | ||
* @author Soeren Richter | ||
* @version 0.1.5 | ||
*/ | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.File; | ||
import javax.sound.sampled.AudioFileFormat; | ||
import javax.sound.sampled.AudioFormat; | ||
import javax.sound.sampled.AudioInputStream; | ||
import javax.sound.sampled.AudioSystem; | ||
import java.awt.*; | ||
import java.io.IOException; | ||
import javax.swing.*; | ||
|
||
public class Synth { | ||
// Samplerate des zu erzeugenden PCM-Datenstroms: | ||
private final double samplerate; | ||
|
||
// Tonlänge exklusive Relese der Huellkurve (Hold-Zeit): | ||
private final double length; | ||
|
||
// Frequenz der zu synthetisierenden Tons: | ||
private final double freq; | ||
|
||
// relative Amplitude: | ||
private final double amplitude; | ||
|
||
// Wellenform; "sine", "saw", "square" oder "triangle": | ||
private final String waveform; | ||
|
||
// Attribute der Huellkurve: | ||
private final double attack; | ||
private final double decay; | ||
private final double sustain; | ||
private final double release; | ||
|
||
/** | ||
* Konstruktor der Synth-Klasse. | ||
* @param pSamplerate Samplerate des zu erzeugenden PCM-Datenstroms | ||
* @param pLength Tonlänge exklusive Relese der Huellkurve (Hold-Zeit) | ||
* @param pFreq Frequenz der zu synthetisierenden Tons | ||
* @param pAmp relative Amplitude | ||
* @param pWaveform Wellenform ("sine", "saw", "square" oder "triangle") | ||
* @param pAttack Attack-Zeit der Huellkurve | ||
* @param pDecay Decay-Zeit der Huellkurve | ||
* @param pSustain Sustain-Amplitude der Huellkurve | ||
* @param pRelease Release-Zeit der Huellkurve | ||
*/ | ||
public Synth(double pSamplerate, double pLength, double pFreq, double pAmp, | ||
String pWaveform, double pAttack, double pDecay, double pSustain, | ||
double pRelease) { | ||
samplerate = pSamplerate; | ||
length = pLength; | ||
freq = pFreq; | ||
amplitude = pAmp; | ||
waveform = pWaveform; | ||
attack = pAttack; | ||
decay = pDecay; | ||
sustain = pSustain; | ||
release = pRelease; | ||
} | ||
|
||
/** | ||
* Methode zur Durchfuehrung der Klangsynthese. | ||
*/ | ||
public void synthesize_standard() { | ||
// Die Zeildatei der Synthese wird nach Auswahl festgelegt. | ||
File filePath = getFilePath(); | ||
|
||
// Wenn eine Datei gewaehlt wurde, wird die Klangsynthese | ||
// durchgefuehrt, ansonsten mit entsprechendem Hinweis nicht. | ||
if (filePath != null) { | ||
// Oscillator- und Envelope-Objekt werden den jeweils | ||
// festgelegten konstanten Attributen erzeugt. | ||
Oscillator osc = new Oscillator(waveform, (int)samplerate/2); | ||
Envelope env = new Envelope(attack,decay,sustain,release); | ||
|
||
// Hold-Laenge des Tons wird an das Envelope-Objekt uebergeben. | ||
env.setLength(length); | ||
|
||
// Fliesskomma-Array buffer zur Aufnahme der | ||
// berechneten Werte wird erzeugt. | ||
float[] buffer = new float[(int)(samplerate * env.getFullLength())]; | ||
|
||
// Werteberechnung der synthetisierten Schallwelle wird | ||
// entsprechend Pseudocode-Konzept durchgefuehrt, siehe: | ||
/** | ||
* von sample = 0 bis 44100 * (length + release), Schritt 1: | ||
* schreibe amp * rect(sample/44100) * env(sample/44100); | ||
*/ | ||
for (int sample = 0; sample < buffer.length; sample++) { | ||
buffer[sample] = (float)(osc.getSample(freq, sample / samplerate) | ||
* env.getAmplitude(sample / samplerate) | ||
* amplitude); | ||
} | ||
|
||
// Der Fliesskomma-Buffer wird an die Speichermethode weitergegeben. | ||
save_as_wave(buffer, filePath); | ||
|
||
} else { | ||
JOptionPane.showMessageDialog(new JFrame("Info"), | ||
"Es wurde keine Datei geschrieben."); | ||
} | ||
|
||
} | ||
|
||
/** | ||
* Methode, die die Fliesskommareihe sampleBuffer in einen 16-Bit PCM Datenstrom konvertiert und als WAVE-Datei nach outputFile speichert. | ||
* @param sampleBuffer Umzuwandelnde Fliesskommareihe | ||
* @param outputFile Zieldatei der WAV-Speicherung | ||
*/ | ||
private void save_as_wave(float[] sampleBuffer, File outputFile) { | ||
// Erstellung eines Byte-Arrays zur Aufnahme der 16-Bit PCM-Werte. | ||
// Fuer jeden Datenwert werden 2 Byte (2*8 Bit) benötigt. Daher muss | ||
// der Byte-Buffer doppelt so lang sein, wie der bisherige Buffer. | ||
final byte[] byteBuffer = new byte[sampleBuffer.length * 2]; | ||
|
||
int bufferIndex = 0; | ||
|
||
// Umwandlung der Fliesskommawerte in einen 16-Bit Datenstrom, der | ||
// aus jeweils zwei Byte (8-Bit) Werten für jeden PCM-Wert besteht. | ||
// Jeder zweite Wert wird vor der Zuweisung um 8 Bit-Stellen verschoben. | ||
for (int i = 0; i < byteBuffer.length; i++) { | ||
final int x = (int) (sampleBuffer[bufferIndex++] * 32767.0); | ||
byteBuffer[i] = (byte) x; | ||
i++; | ||
byteBuffer[i] = (byte) (x >>> 8); | ||
} | ||
|
||
// Nutzung der javax.sound.sampled-API zum Schreiben des Byte-Buffers | ||
// in eine WAVE-Datei outputFile mit korrektem Header. Moegliche | ||
// Probleme bei der Speicherung werden abgefangen. | ||
try { | ||
AudioFormat format = new AudioFormat((float)samplerate, 16, 1, | ||
true, false); | ||
ByteArrayInputStream bais = new ByteArrayInputStream(byteBuffer); | ||
|
||
try (AudioInputStream audioInputStream = new AudioInputStream(bais, | ||
format, sampleBuffer.length)) { | ||
AudioSystem.write(audioInputStream, AudioFileFormat.Type.WAVE, | ||
outputFile); | ||
} | ||
|
||
JOptionPane.showMessageDialog(new JFrame("Info"), | ||
"Die WAV-Datei wurde erfolgreich generiert und nach " | ||
+ outputFile + " geschrieben."); | ||
|
||
} catch (HeadlessException | IOException e) { | ||
JOptionPane.showMessageDialog(new JFrame("Info"), | ||
"Es wurde keine Datei geschrieben, da folgender Fehler auftrat: " | ||
+ e); | ||
} | ||
} | ||
|
||
/** | ||
* Methode zur Auswahl einer Zieldatei | ||
* @return Pfad der WAVE-Zieldatei | ||
*/ | ||
private File getFilePath() { | ||
// Dateiwahldialog wird aufgerufen. | ||
JFileChooser fc = new JFileChooser(); | ||
int returnVal = fc.showOpenDialog(new JFrame("parent")); | ||
|
||
// Wurde eine Datei gewaehlt, wird diese zurueckgegeben. Falls die | ||
// Endung ".wav" bisher fehlte, wird diese zusaetzlich angefuegt. | ||
if (returnVal == JFileChooser.APPROVE_OPTION) { | ||
if (fc.getSelectedFile().toString().endsWith(".wav")) | ||
return fc.getSelectedFile(); | ||
else { | ||
return new File(fc.getSelectedFile().toString() + ".wav"); | ||
} | ||
} else { | ||
return null; | ||
} | ||
} | ||
} |
Oops, something went wrong.