B4J Question Sound generator: 2 channels

peacemaker

Expert
Licensed User
Longtime User
Hi, All

Any suggestion how to make a sound generator of 2 channels: one sine wave to the left channel, another sine wave to second channel ?
If non-crossplatform solution, then for Windows.
 

Magma

Expert
Licensed User
Longtime User

If you check the picture you will see that there is a Balance (L<>R)... so different waves same time at the balance you want :)
 
Upvote 0

peacemaker

Expert
Licensed User
Longtime User
I updated the example of MONO Sound Generator using the latest jAudiotrack2.
Now it needs to study how to make it stereo, i mean, 2-channel generator... from jAudioTrack2 - Mixer Full:


B4X:
Do While True
        'Maximum length of the last buffer read from each track
        Len = 0
        TotalRead = 0
     
        'Define / Clear the mix buffer for the nexr iteration
        Dim MixBuffer(BufferSize / 2) As Short
     
     
        'Mixing Process
        'For each track:
        'Read each loaded file in sequence as NewSample(2 bytes per sample Left channel then Right channel),
        'Aquire each 16bit sample and convert to float
        'Check the level of each track is not too high (WarningClipValue is an arbitrary value of 29000 & -29000)
        'Adjust the level of the left and right samples by the Track left and right balance values.
        'Add The NewSample to the corresponding value in the MixBuffer(all tracks already processed) into the variable Mixed
        'Check the level of the mixed value is not too high (WarningClipValue is an arbitrary value of 29000 & -29000)
        'Add the Mixed value to the corresponding position of the shortbuffer MixBuffer.
     
        'Once all tracks are added to the MixBuffer:
        'Convert the Short Mixbuffer to bytes and write it to the SourceDataLine
        'Check if the Stop or pause flags are set and exit if needed
        'If the total bytes read from each track is 0, then playing of all tracks is complete so exit.
        'Tidy up setting appropriate flags, stopping the SourceDataLine and return from the sub
     
        For Each Track As MixerTrack In mTracks
            MaxValue = 0
            'Avoid reading past the end of the Data Chunk in case the Data Chunk is not the last chunk in the file.
            ThisBufferSize = Max(0, Min(BufferSize , Track.DataChunkEnd - Track.PlayPos))
         
         
            TotalRead = TotalRead + Track.RandomAccessFile.ReadBytes(Dat,0,ThisBufferSize, Track.DataChunkStart + Track.PlayPos)
            Len = Max(Len,Dat.Length)
         
            'Play position for each track
            Track.PlayPos = Track.PlayPos + Dat.Length
            'Check if track is muted
            If Track.Mute = False Then
                For i = 0 To Dat.Length - 1 Step 2
                    MixBufferi = i / 2
                    'Convert 16 bits to short so they can be added together
                    NewSample = BC.ShortsFromBytes(Array As Byte(Dat(i),Dat(i+1)))(0) * Track.Gain * Track.Volume * mMasterGain
                 
                    'Check input. If the level is too high it could cause clipping.  Just a warning
                    If NewSample > WarningClipValue Then
                        If ReportClip Then CallSubDelayed3(ClipCallback,ClipEventName & "_ClipEvent",Track,NewSample)
                    End If
                    If NewSample < -WarningClipValue Then
                        If ReportClip Then CallSubDelayed3(ClipCallback,ClipEventName & "_ClipEvent",Track,NewSample)
                    End If
                 
                    'Deal with Balance of the stereo track
                    If i Mod 4 = 0 Then
                        NewSample = NewSample * Track.LeftBalance
                    Else
                        NewSample = NewSample * Track.RightBalance
                    End If
                 
                    'Add the NewSample to the MixBuffer corresponding value
                    Mixed = (MixBuffer(MixBufferi) + NewSample)

                    'Check output level. if the level is too high it could cause clipping.  Just a warning
                    If Mixed > WarningClipValue Then
                        If ReportClip Then CallSubDelayed3(ClipCallback,ClipEventName & "_ClipEvent",Null,Mixed)
                    End If
                    If Mixed < -WarningClipValue Then
                        If ReportClip Then CallSubDelayed3(ClipCallback,ClipEventName & "_ClipEvent",Null,Mixed)
                    End If
                 
                    'Update the current Mixbuffer
                    MixBuffer(MixBufferi) = Mixed
                    MaxValue = Max(MaxValue,NewSample)
                Next
            End If
            If ReportSound And MaxValue > SoundThreshold Then SoundMap.Put(Track,MaxValue)
                     
            'Free up the gui
            Sleep(0)
         
            'Exit if flag is set
            If StopPlaying Then Exit
        Next

        'Report current position if requested
        If ReportPos Then CallSubDelayed2(PosCallback,PosEventName & "_PosEvent",PlayPos)
     
        'Update play position for reporting (Maximum value)
        PlayPos = PlayPos + Len
     
        'Convert the mixed MixBuffer to bytes and write it to the SourceDataLine
        SDLW.Write(BC.ShortsToBytes(MixBuffer),0,MixBuffer.Length * 2)

p.s. updated to v.0.11 to be correct freq, comparing to the etalon of 1 kHz, for the stereo mode
 

Attachments

  • TempDownload.png
    TempDownload.png
    11.9 KB · Views: 110
  • 0.11.zip
    3.5 KB · Views: 100
Last edited:
Upvote 0

peacemaker

Expert
Licensed User
Longtime User
Hi, @moty22 and @stevel05
Thanks for your jAudio libs. But could you explain more detailed how to correctly write to the audio output so, that to have stereo signal, but different in each channel ?
Say, left channel - sine generation of 100 Hz, right channel - generation of 1000 Hz sine.
 
Upvote 0

emexes

Expert
Licensed User
Any suggestion how to make a sound generator of 2 channels: one sine wave to the left channel, another sine wave to second channel ?

My first guess would be Mono = False when initializing the audio streamer:

https://www.b4x.com/android/help/audio.html#audiostreamer_initialize

and that then your samples should be interleaved left-right-left-right-left-right ie instead of a sample block being a set of mono 16-bit samples, it would now be a set of stereo 2 x 16 bit samples, ie an integer multiple of 32 bits or 4 bytes.

I don't know whether left or right is first, but I'm guessing left because (i) most computer programming is left-to-right, and (ii) left is before right in the dictionary.
 
Upvote 0

peacemaker

Expert
Licensed User
Longtime User
it would now be a set of 2 x 16 bit samples
Do you mean 2 bytes of date per channel ?

In my code example above - it is the stereo mode, so - this generator sends 2 bytes to each channel and it sounds like mono-generation now: both channels in the same time.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
Hi, @moty22 and @stevel05
Thanks for your jAudio libs. But could you explain more detailed how to correctly write to the audio output so, that to have stereo signal, but different in each channel ?
Say, left channel - sine generation of 100 Hz, right channel - generation of 1000 Hz sine.
Well i wanted to try.. the sine generator (original) of moty22... and in Release version getting sometimes errors... then change it to debug... run it, nothing sound... then change it again at release and worked... may be threads..... creating some probs...

but i will try to see if i can help you at left-right you want... not promising anything
 
Upvote 0

peacemaker

Expert
Licensed User
Longtime User
i can help you at left-right
Thanks for try. Check, please, my latest 0.11 version above. Better to modify it.

This code:
B4X:
SampleRateInHz = rate*smp / Channels /2    'Hz
is for getting the correct frequency for Channels = 2 (stereo output) - i have compared with the 1K etalon sound.
SO, now it looks that it needs to make preparing the data buffer for 2 sine waves.
 
Upvote 0

peacemaker

Expert
Licensed User
Longtime User
Interesting source code is found as reference:
Java:
/*File AudioSynth01.java
Copyright 2003, R.G.Baldwin

This program demonstrates the ability to create
synthetic audio data, and to play it back
immediately, or to store it in an AU file for
later playback.

A GUI appears on the screen containing the
following components in the North position:

Generate button
Play/File button
Elapsed time meter (JTextField)

Several radio buttons appear in the Center
position of the GUI.  Each radio button selects
a different format for synthetic audio data.

The South position of the GUI contains the
following components:

Listen radio button
File radio button
File Name text field

Select a radio button from the Center and click
the Generate button.  A short segment of
synthetic audio data will be generated and saved
in memory.  The segment length is two seconds
for monaural data and one second for stereo data,
at 16000 samp/sec and 16 bits per sample.

To listen to the audio data, select the Listen
radio button in the South position and click the
Play/File button.  You can listen to the data
repeatedly if you so choose.  In addition to
listening to the data, you can also save it in
an audio file.

To save the audio data in an audio file of type
AU, enter a file name (without extension) in the
text field in the South position, select the
File radio button in the South position, and
click the Play/File button.

You should be able to play the audio file back
with any standard media player that can handle
the AU file type, or with a program written in
Java, such as the program named AudioPlayer02
that was discussed in an earlier lesson.

Tested using SDK 1.4.0 under Win2000
************************************************/

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.sound.sampled.*;
import java.io.*;
import java.nio.channels.*;
import java.nio.*;
import java.util.*;

public class AudioSynth01 extends JFrame{

  //The following are general instance variables
  // used to create a SourceDataLine object.
  AudioFormat audioFormat;
  AudioInputStream audioInputStream;
  SourceDataLine sourceDataLine;

  //The following are audio format parameters.
  // They may be modified by the signal generator
  // at runtime.  Values allowed by Java
  // SDK 1.4.1 are shown in comments.
  float sampleRate = 16000.0F;
  //Allowable 8000,11025,16000,22050,44100
  int sampleSizeInBits = 16;
  //Allowable 8,16
  int channels = 1;
  //Allowable 1,2
  boolean signed = true;
  //Allowable true,false
  boolean bigEndian = true;
  //Allowable true,false

  //A buffer to hold two seconds monaural and one
  // second stereo data at 16000 samp/sec for
  // 16-bit samples
  byte audioData[] = new byte[16000*4];

  //Following components appear in the North
  // position of the GUI.
  final JButton generateBtn =
                         new JButton("Generate");
  final JButton playOrFileBtn =
                        new JButton("Play/File");
  final JLabel elapsedTimeMeter =
                              new JLabel("0000");

  //Following radio buttons select a synthetic
  // data type.  Add more buttons if you add
  // more synthetic data types.  They appear in
  // the center position of the GUI.
  final JRadioButton tones =
                  new JRadioButton("Tones",true);
  final JRadioButton stereoPanning =
              new JRadioButton("Stereo Panning");
  final JRadioButton stereoPingpong =
             new JRadioButton("Stereo Pingpong");
  final JRadioButton fmSweep =
                    new JRadioButton("FM Sweep");
  final JRadioButton decayPulse =
                 new JRadioButton("Decay Pulse");
  final JRadioButton echoPulse =
                 new JRadioButton("Echo Pulse");
  final JRadioButton waWaPulse =
                 new JRadioButton("WaWa Pulse");

  //Following components appear in the South
  // position of the GUI.
  final JRadioButton listen =
                 new JRadioButton("Listen",true);
  final JRadioButton file =
                        new JRadioButton("File");
  final JTextField fileName =
                       new JTextField("junk",10);

  //-------------------------------------------//
  public static void main(
                        String args[]){
    new AudioSynth01();
  }//end main
  //-------------------------------------------//

  public AudioSynth01(){//constructor
    //A panel for the North position.  Note the
    // etched border.
    final JPanel controlButtonPanel =
                                    new JPanel();
    controlButtonPanel.setBorder(
             BorderFactory.createEtchedBorder());

    //A panel and button group for the radio
    // buttons in the Center position.
    final JPanel synButtonPanel = new JPanel();
    final ButtonGroup synButtonGroup =
                               new ButtonGroup();
    //This panel is used for cosmetic purposes
    // only, to cause the radio buttons to be
    // centered horizontally in the Center
    // position.
    final JPanel centerPanel = new JPanel();

    //A panel for the South position.  Note the
    // etched border.
    final JPanel outputButtonPanel =
                                    new JPanel();
    outputButtonPanel.setBorder(
             BorderFactory.createEtchedBorder());
    final ButtonGroup outputButtonGroup =
                               new ButtonGroup();

    //Disable the Play button initially to force
    // the user to generate some data before
    // trying to listen to it or write it to a
    // file.
    playOrFileBtn.setEnabled(false);

    //Register anonymous listeners on the
    // Generate button and the Play/File button.
    generateBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          //Don't allow Play during generation
          playOrFileBtn.setEnabled(false);
          //Generate synthetic data
          new SynGen().getSyntheticData(
                                      audioData);
          //Now it is OK for the user to listen
          // to or file the synthetic audio data.
          playOrFileBtn.setEnabled(true);
        }//end actionPerformed
      }//end ActionListener
    );//end addActionListener()

    playOrFileBtn.addActionListener(
      new ActionListener(){
        public void actionPerformed(
                                  ActionEvent e){
          //Play or file the data synthetic data
          playOrFileData();
        }//end actionPerformed
      }//end ActionListener
    );//end addActionListener()

    //Add two buttons and a text field to a
    // physical group in the North of the GUI.
    controlButtonPanel.add(generateBtn);
    controlButtonPanel.add(playOrFileBtn);
    controlButtonPanel.add(elapsedTimeMeter);

    //Add radio buttons to a mutually exclusive
    // group in the Center of the GUI.  Make
    // additions here if you add new synthetic
    // generator methods.
    synButtonGroup.add(tones);
    synButtonGroup.add(stereoPanning);
    synButtonGroup.add(stereoPingpong);
    synButtonGroup.add(fmSweep);
    synButtonGroup.add(decayPulse);
    synButtonGroup.add(echoPulse);
    synButtonGroup.add(waWaPulse);

    //Add radio buttons to a physical group and
    // center it in the Center of the GUI. Make
    // additions here if you add new synthetic
    // generator methods.
    synButtonPanel.setLayout(
                            new GridLayout(0,1));
    synButtonPanel.add(tones);
    synButtonPanel.add(stereoPanning);
    synButtonPanel.add(stereoPingpong);
    synButtonPanel.add(fmSweep);
    synButtonPanel.add(decayPulse);
    synButtonPanel.add(echoPulse);
    synButtonPanel.add(waWaPulse);

    //Note that the centerPanel has center
    // alignment by default.
    centerPanel.add(synButtonPanel);

    //Add radio buttons to a mutually exclusive
    // group in the South of the GUI.
    outputButtonGroup.add(listen);
    outputButtonGroup.add(file);

    //Add radio buttons to a physical group in
    // the South of the GUI.
    outputButtonPanel.add(listen);
    outputButtonPanel.add(file);
    outputButtonPanel.add(fileName);

    //Add the panels containing components to the
    // content pane of the GUI in the appropriate
    // positions.
    getContentPane().add(
          controlButtonPanel,BorderLayout.NORTH);
    getContentPane().add(centerPanel,
                            BorderLayout.CENTER);
    getContentPane().add(outputButtonPanel,
                             BorderLayout.SOUTH);

    //Finish the GUI.  If you add more radio
    // buttons in the center, you may need to
    // modify the call to setSize to increase
    // the vertical component of the GUI size.
    setTitle("Copyright 2003, R.G.Baldwin");
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    setSize(250,275);
    setVisible(true);
  }//end constructor
  //-------------------------------------------//

  //This method plays or files the synthetic
  // audio data that has been generated and saved
  // in an array in memory.
  private void playOrFileData() {
    try{
      //Get an input stream on the byte array
      // containing the data
      InputStream byteArrayInputStream =
                        new ByteArrayInputStream(
                                      audioData);

      //Get the required audio format
      audioFormat = new AudioFormat(
                                sampleRate,
                                sampleSizeInBits,
                                channels,
                                signed,
                                bigEndian);

      //Get an audio input stream from the
      // ByteArrayInputStream
      audioInputStream = new AudioInputStream(
                    byteArrayInputStream,
                    audioFormat,
                    audioData.length/audioFormat.
                                 getFrameSize());

      //Get info on the required data line
      DataLine.Info dataLineInfo =
                          new DataLine.Info(
                            SourceDataLine.class,
                                    audioFormat);

      //Get a SourceDataLine object
      sourceDataLine = (SourceDataLine)
                             AudioSystem.getLine(
                                   dataLineInfo);
      //Decide whether to play the synthetic
      // data immediately, or to write it into
      // an audio file, based on the user
      // selection of the radio buttons in the
      // South of the GUI..
      if(listen.isSelected()){
      //Create a thread to play back the data and
      // start it running.  It will run until all
      // the data has been played back
        new ListenThread().start();
      }else{
        //Disable buttons until existing data
        // is written to the file.
        generateBtn.setEnabled(false);
        playOrFileBtn.setEnabled(false);

        //Write the data to an output file with
        // the name provided by the text field
        // in the South of the GUI.
        try{
          AudioSystem.write(
                    audioInputStream,
                    AudioFileFormat.Type.AU,
                    new File(fileName.getText() +
                                         ".au"));
        }catch (Exception e) {
          e.printStackTrace();
          System.exit(0);
        }//end catch
        //Enable buttons for another operation
        generateBtn.setEnabled(true);
        playOrFileBtn.setEnabled(true);
      }//end else
    }catch (Exception e) {
      e.printStackTrace();
      System.exit(0);
    }//end catch
  }//end playOrFileData
//=============================================//

//Inner class to play back the data that was
// saved.
class ListenThread extends Thread{
  //This is a working buffer used to transfer
  // the data between the AudioInputStream and
  // the SourceDataLine.  The size is rather
  // arbitrary.
  byte playBuffer[] = new byte[16384];

  public void run(){
    try{
      //Disable buttons while data is being
      // played.
      generateBtn.setEnabled(false);
      playOrFileBtn.setEnabled(false);

      //Open and start the SourceDataLine
      sourceDataLine.open(audioFormat);
      sourceDataLine.start();

      int cnt;
      //Get beginning of elapsed time for
      // playback
      long startTime = new Date().getTime();

      //Transfer the audio data to the speakers
      while((cnt = audioInputStream.read(
                              playBuffer, 0,
                              playBuffer.length))
                                          != -1){
        //Keep looping until the input read
        // method returns -1 for empty stream.
        if(cnt > 0){
          //Write data to the internal buffer of
          // the data line where it will be
          // delivered to the speakers in real
          // time
          sourceDataLine.write(
                             playBuffer, 0, cnt);
        }//end if
      }//end while

      //Block and wait for internal buffer of the
      // SourceDataLine to become empty.
      sourceDataLine.drain();


      //Get and display the elapsed time for
      // the previous playback.
      int elapsedTime =
         (int)(new Date().getTime() - startTime);
      elapsedTimeMeter.setText("" + elapsedTime);

      //Finish with the SourceDataLine
      sourceDataLine.stop();
      sourceDataLine.close();

      //Re-enable buttons for another operation
      generateBtn.setEnabled(true);
      playOrFileBtn.setEnabled(true);
    }catch (Exception e) {
      e.printStackTrace();
      System.exit(0);
    }//end catch

  }//end run
}//end inner class ListenThread
//=============================================//

//Inner signal generator class.

//An object of this class can be used to
// generate a variety of different synthetic
// audio signals.  Each time the getSyntheticData
// method is called on an object of this class,
// the method will fill the incoming array with
// the samples for a synthetic signal.
class SynGen{
  //Note:  Because this class uses a ByteBuffer
  // asShortBuffer to handle the data, it can
  // only be used to generate signed 16-bit
  // data.
  ByteBuffer byteBuffer;
  ShortBuffer shortBuffer;
  int byteLength;

  void getSyntheticData(byte[] synDataBuffer){
    //Prepare the ByteBuffer and the shortBuffer
    // for use
    byteBuffer = ByteBuffer.wrap(synDataBuffer);
    shortBuffer = byteBuffer.asShortBuffer();

    byteLength = synDataBuffer.length;

    //Decide which synthetic data generator
    // method to invoke based on which radio
    // button the user selected in the Center of
    // the GUI.  If you add more methods for
    // other synthetic data types, you need to
    // add corresponding radio buttons to the
    // GUI and add statements here to test the
    // new radio buttons.  Make additions here
    // if you add new synthetic generator
    // methods.

    if(tones.isSelected()) tones();
    if(stereoPanning.isSelected())
                                 stereoPanning();
    if(stereoPingpong.isSelected())
                                stereoPingpong();
    if(fmSweep.isSelected()) fmSweep();
    if(decayPulse.isSelected()) decayPulse();
    if(echoPulse.isSelected()) echoPulse();
    if(waWaPulse.isSelected()) waWaPulse();

  }//end getSyntheticData method
  //-------------------------------------------//

  //This method generates a monaural tone
  // consisting of the sum of three sinusoids.
  void tones(){
    channels = 1;//Java allows 1 or 2
    //Each channel requires two 8-bit bytes per
    // 16-bit sample.
    int bytesPerSamp = 2;
    sampleRate = 16000.0F;
    // Allowable 8000,11025,16000,22050,44100
    int sampLength = byteLength/bytesPerSamp;
    for(int cnt = 0; cnt < sampLength; cnt++){
      double time = cnt/sampleRate;
      double freq = 950.0;//arbitrary frequency
      double sinValue =
        (Math.sin(2*Math.PI*freq*time) +
        Math.sin(2*Math.PI*(freq/1.8)*time) +
        Math.sin(2*Math.PI*(freq/1.5)*time))/3.0;
      shortBuffer.put((short)(16000*sinValue));
    }//end for loop
  }//end method tones
  //-------------------------------------------//

  //This method generates a stereo speaker sweep,
  // starting with a relatively high frequency
  // tone on the left speaker and moving across
  // to a lower frequency tone on the right
  // speaker.
  void stereoPanning(){
    channels = 2;//Java allows 1 or 2
    int bytesPerSamp = 4;//Based on channels
    sampleRate = 16000.0F;
    // Allowable 8000,11025,16000,22050,44100
    int sampLength = byteLength/bytesPerSamp;
    for(int cnt = 0; cnt < sampLength; cnt++){
      //Calculate time-varying gain for each
      // speaker
      double rightGain = 16000.0*cnt/sampLength;
      double leftGain = 16000.0 - rightGain;

      double time = cnt/sampleRate;
      double freq = 600;//An arbitrary frequency
      //Generate data for left speaker
      double sinValue =
                 Math.sin(2*Math.PI*(freq)*time);
      shortBuffer.put(
                     (short)(leftGain*sinValue));
      //Generate data for right speaker
      sinValue =
             Math.sin(2*Math.PI*(freq*0.8)*time);
      shortBuffer.put(
                    (short)(rightGain*sinValue));
    }//end for loop
  }//end method stereoPanning
  //-------------------------------------------//

  //This method uses stereo to switch a sound
  // back and forth between the left and right
  // speakers at a rate of about eight switches
  // per second.  On my system, this is a much
  // better demonstration of the sound separation
  // between the two speakers than is the
  // demonstration produced by the stereoPanning
  // method.  Note also that because the sounds
  // are at different frequencies, the sound
  // produced is similar to that of U.S.
  // emergency vehicles.

  void stereoPingpong(){
    channels = 2;//Java allows 1 or 2
    int bytesPerSamp = 4;//Based on channels
    sampleRate = 16000.0F;
    // Allowable 8000,11025,16000,22050,44100
    int sampLength = byteLength/bytesPerSamp;
    double leftGain = 0.0;
    double rightGain = 16000.0;
    for(int cnt = 0; cnt < sampLength; cnt++){
      //Calculate time-varying gain for each
      // speaker
      if(cnt % (sampLength/8) == 0){
        //swap gain values
        double temp = leftGain;
        leftGain = rightGain;
        rightGain = temp;
      }//end if

      double time = cnt/sampleRate;
      double freq = 600;//An arbitrary frequency
      //Generate data for left speaker
      double sinValue =
                 Math.sin(2*Math.PI*(freq)*time);
      shortBuffer.put(
                     (short)(leftGain*sinValue));
      //Generate data for right speaker
      sinValue =
             Math.sin(2*Math.PI*(freq*0.8)*time);
      shortBuffer.put(
                    (short)(rightGain*sinValue));
    }//end for loop
  }//end stereoPingpong method
  //-------------------------------------------//

  //This method generates a monaural linear
  // frequency sweep from 100 Hz to 1000Hz.
  void fmSweep(){
    channels = 1;//Java allows 1 or 2
    int bytesPerSamp = 2;//Based on channels
    sampleRate = 16000.0F;
    // Allowable 8000,11025,16000,22050,44100
    int sampLength = byteLength/bytesPerSamp;
    double lowFreq = 100.0;
    double highFreq = 1000.0;

    for(int cnt = 0; cnt < sampLength; cnt++){
      double time = cnt/sampleRate;

      double freq = lowFreq +
               cnt*(highFreq-lowFreq)/sampLength;
      double sinValue =
                   Math.sin(2*Math.PI*freq*time);
      shortBuffer.put((short)(16000*sinValue));
    }//end for loop
  }//end method fmSweep
  //-------------------------------------------//

  //This method generates a monaural triple-
  // frequency pulse that decays in a linear
  // fashion with time.
  void decayPulse(){
    channels = 1;//Java allows 1 or 2
    int bytesPerSamp = 2;//Based on channels
    sampleRate = 16000.0F;
    // Allowable 8000,11025,16000,22050,44100
    int sampLength = byteLength/bytesPerSamp;
    for(int cnt = 0; cnt < sampLength; cnt++){
      //The value of scale controls the rate of
      // decay - large scale, fast decay.
      double scale = 2*cnt;
      if(scale > sampLength) scale = sampLength;
      double gain =
             16000*(sampLength-scale)/sampLength;
      double time = cnt/sampleRate;
      double freq = 499.0;//an arbitrary freq
      double sinValue =
        (Math.sin(2*Math.PI*freq*time) +
        Math.sin(2*Math.PI*(freq/1.8)*time) +
        Math.sin(2*Math.PI*(freq/1.5)*time))/3.0;
      shortBuffer.put((short)(gain*sinValue));
    }//end for loop
  }//end method decayPulse
  //-------------------------------------------//

  //This method generates a monaural triple-
  // frequency pulse that decays in a linear
  // fashion with time.  However, three echoes
  // can be heard over time with the amplitude
  // of the echoes also decreasing with time.
  void echoPulse(){
    channels = 1;//Java allows 1 or 2
    int bytesPerSamp = 2;//Based on channels
    sampleRate = 16000.0F;
    // Allowable 8000,11025,16000,22050,44100
    int sampLength = byteLength/bytesPerSamp;
    int cnt2 = -8000;
    int cnt3 = -16000;
    int cnt4 = -24000;
    for(int cnt1 = 0; cnt1 < sampLength;
                    cnt1++,cnt2++,cnt3++,cnt4++){
      double val = echoPulseHelper(
                                cnt1,sampLength);
      if(cnt2 > 0){
        val += 0.7 * echoPulseHelper(
                                cnt2,sampLength);
      }//end if
      if(cnt3 > 0){
        val += 0.49 * echoPulseHelper(
                                cnt3,sampLength);
      }//end if
      if(cnt4 > 0){
        val += 0.34 * echoPulseHelper(
                                cnt4,sampLength);
      }//end if

      shortBuffer.put((short)val);
    }//end for loop
  }//end method echoPulse
  //-------------------------------------------//

  double echoPulseHelper(int cnt,int sampLength){
    //The value of scale controls the rate of
    // decay - large scale, fast decay.
    double scale = 2*cnt;
    if(scale > sampLength) scale = sampLength;
    double gain =
             16000*(sampLength-scale)/sampLength;
    double time = cnt/sampleRate;
    double freq = 499.0;//an arbitrary freq
    double sinValue =
      (Math.sin(2*Math.PI*freq*time) +
      Math.sin(2*Math.PI*(freq/1.8)*time) +
      Math.sin(2*Math.PI*(freq/1.5)*time))/3.0;
    return(short)(gain*sinValue);
  }//end echoPulseHelper

  //-------------------------------------------//

  //This method generates a monaural triple-
  // frequency pulse that decays in a linear
  // fashion with time.  However, three echoes
  // can be heard over time with the amplitude
  // of the echoes also decreasing with time.
  //Note that this method is identical to the
  // method named echoPulse, except that the
  // algebraic sign was switched on the amplitude
  // of two of the echoes before adding them to
  // the composite synthetic signal.  This
  // resulted in a difference in the
  // sound.
  void waWaPulse(){
    channels = 1;//Java allows 1 or 2
    int bytesPerSamp = 2;//Based on channels
    sampleRate = 16000.0F;
    // Allowable 8000,11025,16000,22050,44100
    int sampLength = byteLength/bytesPerSamp;
    int cnt2 = -8000;
    int cnt3 = -16000;
    int cnt4 = -24000;
    for(int cnt1 = 0; cnt1 < sampLength;
                    cnt1++,cnt2++,cnt3++,cnt4++){
      double val = waWaPulseHelper(
                                cnt1,sampLength);
      if(cnt2 > 0){
        val += -0.7 * waWaPulseHelper(
                                cnt2,sampLength);
      }//end if
      if(cnt3 > 0){
        val += 0.49 * waWaPulseHelper(
                                cnt3,sampLength);
      }//end if
      if(cnt4 > 0){
        val += -0.34 * waWaPulseHelper(
                                cnt4,sampLength);
      }//end if

      shortBuffer.put((short)val);
    }//end for loop
  }//end method waWaPulse
  //-------------------------------------------//

  double waWaPulseHelper(int cnt,int sampLength){
    //The value of scale controls the rate of
    // decay - large scale, fast decay.
      double scale = 2*cnt;
      if(scale > sampLength) scale = sampLength;
      double gain =
             16000*(sampLength-scale)/sampLength;
    double time = cnt/sampleRate;
    double freq = 499.0;//an arbitrary freq
    double sinValue =
      (Math.sin(2*Math.PI*freq*time) +
      Math.sin(2*Math.PI*(freq/1.8)*time) +
      Math.sin(2*Math.PI*(freq/1.5)*time))/3.0;
    return(short)(gain*sinValue);
  }//end waWaPulseHelper

  //-------------------------------------------//
}//end SynGen class
//=============================================//

}//end outer class AudioSynth01.java


I guess, it can be used...
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
..Haven't any success...
hope was easy... i am sure that must use this conversion stevel05's using in TrackMixer_G:
B4X:
        For Each Track As MixerTrack In mTracks
            MaxValue = 0
            'Avoid reading past the end of the Data Chunk in case the Data Chunk is not the last chunk in the file.
            ThisBufferSize = Max(0, Min(BufferSize , Track.DataChunkEnd - Track.PlayPos))
           
           
            TotalRead = TotalRead + Track.RandomAccessFile.ReadBytes(Dat,0,ThisBufferSize, Track.DataChunkStart + Track.PlayPos)
            Len = Max(Len,Dat.Length)
           
            'Play position for each track
            Track.PlayPos = Track.PlayPos + Dat.Length
            'Check if track is muted
            If Track.Mute = False Then
                For i = 0 To Dat.Length - 1 Step 2
                    MixBufferi = i / 2
                    'Convert 16 bits to short so they can be added together
                    Log(Track.Gain & " " & Track.Volume & " " & mMasterGain)
                    NewSample = BC.ShortsFromBytes(Array As Byte(Dat(i),Dat(i+1)))(0) * Track.Gain * Track.Volume * mMasterGain
                   
                    'Check input. If the level is too high it could cause clipping.  Just a warning
                    If NewSample > WarningClipValue Then
                        If ReportClip Then CallSubDelayed3(ClipCallback,ClipEventName & "_ClipEvent",Track,NewSample)
                    End If
                    If NewSample < -WarningClipValue Then
                        If ReportClip Then CallSubDelayed3(ClipCallback,ClipEventName & "_ClipEvent",Track,NewSample)
                    End If
                   
                    'Deal with Balance of the stereo track
                    If i Mod 4 = 0 Then
                        NewSample = NewSample * Track.LeftBalance
                    Else
                        NewSample = NewSample * Track.RightBalance
                    End If
                   
                    Log(Track.Name & " " & Track.LeftBalance & " " & Track.RightBalance)
                   
                    'Add the NewSample to the MixBuffer corresponding value
                    Mixed = (MixBuffer(MixBufferi) + NewSample)

                    'Check output level. if the level is too high it could cause clipping.  Just a warning
                    If Mixed > WarningClipValue Then
                        If ReportClip Then CallSubDelayed3(ClipCallback,ClipEventName & "_ClipEvent",Null,Mixed)
                    End If
                    If Mixed < -WarningClipValue Then
                        If ReportClip Then CallSubDelayed3(ClipCallback,ClipEventName & "_ClipEvent",Null,Mixed)
                    End If
                   
                    'Update the current Mixbuffer
                    MixBuffer(MixBufferi) = Mixed
                    MaxValue = Max(MaxValue,NewSample)
                Next

ofcourse tried to insert it with changes at your code... hope it is a start for you...

As i understand converting bytearray to short (first dividing)... then checking / MOD 4 every byte ? and multiply by 1 or 0 (to remove channel playing)... but here i am loosing it :)
 

Attachments

  • sg4-unsuccessful.zip
    4.5 KB · Views: 78
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Assuming that the sample data is 16bit stereo, once they have been converted to the short array, the left channel will occupy the even indices and the right channel will occupy the odd indices.

If you are creating your own sounds it would be simpler to create two Mono buffers and mix the two mono samples to a Stereo output by interlacing each mono sample into a short buffer.
 
Upvote 0

peacemaker

Expert
Licensed User
Longtime User
If we have such 2 patterns:
1669813002897.png

one for left channel, second for right channel, the frequency_left = 100 Hz (bytes are L0....L7), frequency_right = 1000 Hz (bytes are R0...R7) - how to be placed into the array ?
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Generate both sines as separate Mono arrays so L() and R().

If you want to output as 16 bit then it would be better if the input arrays are Short values otherwise the output will be quiet, using 32767 as the max value instead of 127.

Then the stereo output should be

L(0),R(0),L(1),R(1) ... Etc
 
Upvote 0

peacemaker

Expert
Licensed User
Longtime User
But to have 2 different freqs, the array should be something like freq_L << freq_R:
L(0)R(0) 0R(1) 0R(2) 0R(3) 0R(4) L(1)R(5) 0R(6) 0R(7) 0R(0) 0R(1) L(2)R(2) 0R(3) 0R(4) 0R(5) 0R(6) L(3)R(7)....

?
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
Here you go. All the work is done in the Generate Sub.

There are some controls to enable testing.

I hope it helps.
 

Attachments

  • TwoSines.zip
    3.5 KB · Views: 93
Upvote 0

peacemaker

Expert
Licensed User
Longtime User
Yes, thanks ! It looks as universal solution.
 
Upvote 0

stevel05

Expert
Licensed User
Longtime User
As an addition, pops and clicks annoy me, this version incorporates a release phase which reduces the amplitude of the samples to 0 before the end so it doesn't thump when it finishes.

Useful if you want to do anything musical with it.
 

Attachments

  • TwoSines2.zip
    3.5 KB · Views: 90
Last edited:
Upvote 0
Top