Audio Rechteck Generator

atiemark

Mitglied
Hallo,

ich möchte gerne einen Signal Generator schreiben der ein Rechteck-Signal mit variabler Frequenz erzeugt.
Die Frequenz soll vom Benutzer per Maus in Echtzeit verändert werden können.

Mein Problem ist der Signalgenerator selbst, ich habe eine Methode implementiert (setFrequency) die die Frequenz bei Aufruf ändern soll, jedoch geschieht dies nur mit einer starken Verzögerung.

Da ich das meiste aus dieser Klasse aus Codesnippets gesammelt habe, bin ich mir nicht ganz sicher wo genau der Fehler liegen könnte oder wie dies zu beheben wäre.
Leider habe ich auch nach intensiver Suche nicht viel zu diesem Thema gefunden und hoffe nun auf Hilfe von außen!

Hier mal der Code (leider sehr lang):

Java:
package csig;

import javax.sound.sampled.*;


public class SQUPlayer extends Thread{

    
// AudioFormat Parameter
public static final int SAMPLE_RATE = 22050;
private static final int SAMPLE_SIZE = 16;
private static final int CHANNELS = 1;
private static final boolean SIGNED = true;
private static final boolean BIG_ENDIAN = true;


// Buffer Größe, verarbeiteter Chunk
public static final int BUFFER_SIZE = 1000;
public static final int SAMPLES_PER_BUFFER = BUFFER_SIZE / 2;

//Variablen
private AudioFormat format;
private Line.Info info;
private SourceDataLine auline;
private boolean done;
private byte [] sampleData = new byte[BUFFER_SIZE];
private long periodSamples;
private long sampleNumber;



//Konstruktor
public SQUPlayer() {

    // Create the audio format we wish to use
format = new AudioFormat(SAMPLE_RATE, SAMPLE_SIZE, CHANNELS, SIGNED, BIG_ENDIAN);

info = new DataLine.Info(SourceDataLine.class, format);


}

//Frequency Setter
public void setFrequency(double frequency) 
    {
    periodSamples = (long)(SQUPlayer.SAMPLE_RATE / frequency);
    }


//Erzeugt Rechteck
protected double getSample() {
    
    double value;  
    double x = sampleNumber / (double) periodSamples;

        if (sampleNumber < (periodSamples / 2)) {
          value = 1.0;
        }  else  {
          value = -1.0;
        }
  
    sampleNumber = (sampleNumber + 1) % periodSamples;
    return value;
  }





  public int getSamples(byte [] buffer) {
    int index = 0;
    for (int i = 0; i < SQUPlayer.SAMPLES_PER_BUFFER; i++) {
      double ds = getSample() * Short.MAX_VALUE;
      short ss = (short) Math.round(ds);
      buffer[index++] = (byte)(ss >> 8);
      buffer[index++] = (byte)(ss & 0xFF);      
    }
    return SQUPlayer.BUFFER_SIZE;
  }





    @Override
public void run() {

        
int nBytesRead = 0;

try {

    // Get line to write data to
auline = (SourceDataLine) AudioSystem.getLine(info);
auline.open(format);
auline.start();

while ((nBytesRead != -1)) 
    {
    nBytesRead = getSamples(sampleData);

    if (nBytesRead > 0) 
        {
         auline.write(sampleData, 0, nBytesRead);
        }

    }

} catch(Exception e) {

    e.printStackTrace(); 
} 

      
finally 
    {
        auline.drain();
        auline.close(); 
    }

}

public void startPlayer() 

{
      start();
}

}


in meiner Main methode erzeuge ich ein neues Objekt meines SQUPlayers [Java]SQUPlayer player = new SQUPlayer()[/Java]
und rufe die Methode
Java:
player.setFrequency(
//Hier der Wert eines Sliders zB
jSlider1.getValue()
)

//und

player.startPlayer();

auf

Wenn ich nun den Slider und somit die Frequenz verändere geschieht dies mit großer Verzögerung.

Ich habe bereits versucht den Buffer (zeile 18 in der SQUPlayer Klasse) kleiner zu machen, das ändert jedoch garnichts.

Bin für jeden Vorschlag dankbar!
 
Ein ähnliches Problem hatten wir schon mal, worauf ich einige Verusche angestellt habe.
Der Code löst dein Problem nicht direkt (du musst sicher noch "sinus" durch "rechteck" ersetzen), aber es tönt ;-)

hth

Java:
import javax.sound.sampled.Clip;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Line;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.AudioFormat;
 
public class ToneGeneratorExample {
 
    public byte[] getMonoSinusTone(int frequency, AudioFormat af) {
        byte sampleSize = (byte) (af.getSampleSizeInBits() / 8);
        byte[] data = new byte[(int) af.getSampleRate() * sampleSize];
        double step_width = (2 * Math.PI) / af.getSampleRate();
        double x = 0;
        for (int i = 0; i < data.length; i += sampleSize) {
            int sample_max_value = (int) Math.pow(2, af.getSampleSizeInBits()) / 2 - 1;
            int value = (int) (sample_max_value * Math.sin(frequency * x));
            for (int j = 0; j < sampleSize; j++) {
                byte sample_byte = (byte) ((value >> (8 * j)) & 0xff);
                data[i + j] = sample_byte;
            }
            x += step_width;
        }
        return data;
    }
 
    public byte[] getStereoSinusTone(int frequency1, int frequency2, AudioFormat af) {
        byte sampleSize = (byte) (af.getSampleSizeInBits() / 8);
        byte[] data = new byte[(int) af.getSampleRate() * sampleSize  * 2];
        double stepWidth = (2 * Math.PI) / af.getSampleRate();
        double x = 0;
        for (int i = 0; i < data.length; i += sampleSize * 2) {
            int sample_max_value = (int) Math.pow(2, af.getSampleSizeInBits()) / 2 - 1;
            int value = (int) (sample_max_value * Math.sin(frequency1 * x));
            for (int j = 0; j < sampleSize; j++) {
                byte sampleByte = (byte) ((value >> (8 * j)) & 0xff);
                data[i + j] = sampleByte;
            }
            value = (int) (sample_max_value * Math.sin(frequency2 * x));
            for (int j = 0; j < sampleSize; j++) {
                byte sampleByte = (byte) ((value >> (8 * j)) & 0xff);
                int index = i + j + sampleSize;
                data[index] = sampleByte;
            }
            x += stepWidth;
        }
        return data;
    }
 
    public void play(int frequenzy) {
        AudioFormat af = new AudioFormat(44100, 16, 1, true, false);
        byte[] data = getMonoSinusTone(frequenzy, af);
        try {
            Clip c = (Clip) AudioSystem.getLine(new Line.Info(Clip.class));
            c.open(af, data, 0, data.length);
            c.loop(1);
            while(c.isRunning()) {
                try {
                    Thread.sleep(50);
                } catch (Exception ex) {}
            }
        } catch (LineUnavailableException ex) {
            ex.printStackTrace();
        }
    }
 
    public void play(int frequenzyLeft, int  frequencyRight) {
        AudioFormat af = new AudioFormat(44100, 16, 2, true, false);
        byte[] data = getStereoSinusTone(frequenzyLeft, frequencyRight, af);
        try {
            Clip c = (Clip) AudioSystem.getLine(new Line.Info(Clip.class));
            c.open(af, data, 0, data.length);
            c.loop(1);
            while(c.isRunning()) {
                try {
                    Thread.sleep(50);
                } catch (Exception ex) {}
            }
        } catch (LineUnavailableException ex) {
            ex.printStackTrace();
        }
    }
 
    public static void main(String[] args) {
        ToneGeneratorExample tge = new ToneGeneratorExample();
        if (args.length == 1) {
            tge.play(Integer.parseInt(args[0]));
        } else if (args.length == 2) {
            tge.play(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
        } else {
        	System.out.println("Mono Ton");
            tge.play(500);
            System.out.println("Ton auf dem linken Kanal");
            tge.play(500, 0);
            System.out.println("Ton auf dem rechten Kanal");
            tge.play(0, 500);
            System.out.println("Ton auf dem beiden Kanälen");
            tge.play(500, 1300);
            System.out.println("Fertig");
        }
    }
}
 
Vielen Dank für deine Antwort Andi, das hilft mir etwas weiter.

Mein Hauptproblem besteht jedoch darin, dass ich die Frequenz des Tones während der Laufzeit fließend verändern möchte! (Wie bei einem Funktionsgenerator)
Das ist mit deinem Code nicht möglich.

Leider habe ich dazu gar keinen richtigen Ansatz, da ich nicht sicher bin ob das überhaupt möglich ist (ich hoffe es).
 
Natürlich ist das möglich - meine Prozeduren berechnen einen Ton (oder im Fall von stereo zwei Tönen) mit konstanter Frequenz - wenn du diese Berechnungsroutine änderst, dann tut das.

By the way - bist du dir sicher mit Rechteck? Das tönt nämlich ziemlich hässlich
 
Ich möchte das erzeugte Signal gerne in Echtzeit streamen, um ohne große Verzögerungen die Frequenz ändern zu können.

Bei deinem Beispiel berechnest du zuerst ein byte array "data" in der Methode
Java:
getMonoSinusTone(int frequency, AudioFormat af)
mit einer bestimmten Länge, das in der Methode
Java:
play(int frequency)
aufgerufen wird, und dann von Clip c 1mal geloopt wird.

Muss ich also das byte array "data" so klein wie möglich machen damit man eine Änderung der Frequenz möglichst schnell zu hören bekommt? leider bin ich hier etwas aufgeschmissen 🙁

PS: ich möchte meine Soundkarte als Funktions/Signalgenerator misbrauchen, er soll später mal Sinus, Dreieck, Sägezahn etc erzeugen jedoch ist das Rechteck am wichtigsten (bin Elektronik/Technische Informatik - Schüler)
 
Mit ein wenig Fantasie findest du selbst heraus wie das geht - das habe ich irgendwann letztes Jahr geschrieben und weiss doch nicht mehr im Detail was ich da warum und wie mache...

Da wird ein Byte[] abgefüllt und das von einer Routine die sinus mit einer konstanten Frequenz rechnet.
Ändere die Routine so, dass sie eben Rechteck rechnet und das mit einer variablen Frequenz - wo ist das Problem?

Es gibt keine eingebauten Sinus oder Rechteckgeneraroren - weder auf der Soundkarte noch in Java - also muss man alles rechnen - der Delay ist durch die Buffergrösse gegeben und kann auch damit beeinflusst werden.

Java ist eh keine Highest Speed Sprache - also who cares. C wäre näher an der HW aber damit handelst du dir noch ganz andere Probleme ein.
 

Zurück
Oben