001/*******************************************************************************
002 * Copyright (c) 2024, 2026, Olivier Ayache.  All rights reserved.
003 *
004 * This file is part of AVPKit.
005 *
006 * AVPKit is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU Lesser General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * AVPKit is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with AVPKit.  If not, see <http://www.gnu.org/licenses/>.
018 *******************************************************************************/
019
020package com.avpkit.core;
021
022import java.nio.ByteBuffer;
023
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027
028import com.avpkit.ferry.IBuffer;
029import com.avpkit.core.IAudioSamples;
030import com.avpkit.core.ITimeValue;
031import com.avpkit.core.Utils;
032
033/**
034 * This class generates fake audio data that is an A-note (a sine-wave at 440hz).
035 * 
036 * @author aclarke
037 *
038 */
039public class TestAudioSamplesGenerator
040{
041  private final Logger log = LoggerFactory.getLogger(this.getClass());
042
043  private int mNumChannels = 0;
044  private int mSampleRate = 0;
045  private int mDesiredNoteFrequency = 440; // default to the note: 'A'
046
047  private long mSamplesGenerated = 0;
048
049  private long mStartingPts = 0;
050  
051  public TestAudioSamplesGenerator()
052  {
053    log.trace("firing up");
054  }
055  public void fillNextSamples(IAudioSamples samples, long samplesRequested)
056  {
057    long samplesToGen = Math.min(samples.getMaxSamples(), samplesRequested);
058    
059    if (samples.getChannels() != mNumChannels)
060    {
061      throw new IllegalArgumentException("unmatched channels ");
062    }
063    
064    if (samplesToGen <= 0)
065    {
066      throw new IllegalArgumentException("incorrect number of samples to generate");
067    }
068    
069    // we're going to access the raw data..
070    IBuffer buf = samples.getData();
071    if (buf == null)
072      throw new RuntimeException("could not get IBuffer from samples");
073    
074    ByteBuffer data = buf.getByteBuffer(0, buf.getBufferSize());
075    if (data == null)
076      throw new RuntimeException("could not get raw data from samples");
077    // clear out all the old data
078    data.clear();
079   
080    long startingSample = mSamplesGenerated;
081    long startingPts = Utils.samplesToTimeValue(mSamplesGenerated, mSampleRate).get(ITimeValue.Unit.MICROSECONDS)
082      + mStartingPts;
083    
084    for(long i = 0; i < samplesToGen; i++)
085    {
086      // figure out where I am in time....
087      double time = (double)mSamplesGenerated / (double) mSampleRate;
088
089      double croppedTime = time % ((double)1/(double)mDesiredNoteFrequency);
090      
091      // where intra the desired sample frequency we are
092      double percentOfRange = croppedTime / ((double)1 /(double) mDesiredNoteFrequency);
093      
094      double cycleValue = percentOfRange*Math.PI*2;
095      
096      double samp = Math.sin(cycleValue);
097      
098      // keep the amplitude low; at 50%
099      short aNoteSample = (short) ((samp * 256*256*.5));
100      
101      /*
102      log.trace("generated: {}, {}, {}, {}, {}, {}, {}",
103          new Object[]{
104            mSamplesGenerated,
105            aNoteSample,
106            samp,
107            time,
108            croppedTime,
109            percentOfRange,
110            cycleValue
111      });
112      */
113      
114      for (int j = 0; j < mNumChannels; j++)
115      {
116        /**
117         * We don't ByteBuffer.putShort here because
118         * we seem to need to flip the bytes into a
119         * low byte first format.
120         */
121        data.put((byte)(aNoteSample&0xFF));
122        data.put((byte)((aNoteSample>>8)&0xFF));
123      }
124      ++mSamplesGenerated;
125    }
126    data.flip();
127    
128    log.trace("completed: {}, {}, {}, {}, {}, {}",
129        new Object[]{
130        samplesToGen,
131        mSampleRate,
132        mNumChannels,
133        startingPts,
134        startingSample,
135        IAudioSamples.Format.FMT_S16
136    });
137    
138    samples.setComplete(true, (int)samplesToGen, mSampleRate, mNumChannels,
139        IAudioSamples.Format.FMT_S16,
140        startingPts);
141  }
142  
143  public void prepare(int numChannels, int sampleRate)
144  {
145    prepare(numChannels, sampleRate, 0);
146  }
147  
148  public void prepare(int numChannels, int sampleRate, long startingPts)
149  {
150    mNumChannels = numChannels;
151    mSampleRate = sampleRate;
152    mStartingPts  = startingPts;
153    log.debug("prepare - channels: {}, sampleRate: {}", numChannels, sampleRate);
154  }
155  
156  public void setDesiredNoteFrequency(int note)
157  {
158    mDesiredNoteFrequency = note;
159  }
160  
161  public int getDesiredNoteFrequency()
162  {
163    return mDesiredNoteFrequency;
164  }
165}