Tocando un tono arbitrario con Android

¿Hay alguna manera de hacer que Android emita un sonido de frecuencia arbitraria (es decir, no quiero tener archivos de sonido pregrabados)?

Miré a mi alrededor y ToneGenerator fue lo único que pude encontrar que estaba cerca, pero parece que solo es capaz de emitir los tonos DTMF estándar.

¿Algunas ideas?

Originalmente encontré este código de ejemplo en un blog, pero tenía algunos errores que generaban algunos sonidos horrendos. Solucioné los errores y publiqué el código resultante aquí. ¡Parece que funciona bien para mí!

public class PlaySound extends Activity { // originally from http://marblemice.blogspot.com/2010/04/generate-and-play-tone-in-android.html // and modified by Steve Pomeroy  private final int duration = 3; // seconds private final int sampleRate = 8000; private final int numSamples = duration * sampleRate; private final double sample[] = new double[numSamples]; private final double freqOfTone = 440; // hz private final byte generatedSnd[] = new byte[2 * numSamples]; Handler handler = new Handler(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onResume() { super.onResume(); // Use a new tread as this can take a while final Thread thread = new Thread(new Runnable() { public void run() { genTone(); handler.post(new Runnable() { public void run() { playSound(); } }); } }); thread.start(); } void genTone(){ // fill out the array for (int i = 0; i < numSamples; ++i) { sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone)); } // convert to 16 bit pcm sound array // assumes the sample buffer is normalised. int idx = 0; for (final double dVal : sample) { // scale to maximum amplitude final short val = (short) ((dVal * 32767)); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } } void playSound(){ final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length, AudioTrack.MODE_STATIC); audioTrack.write(generatedSnd, 0, generatedSnd.length); audioTrack.play(); } } 

Mejorando el código anterior:

Añada aceleración de amplitud y reduzca la velocidad para evitar los clics.

Agregue código para determinar cuándo terminó la reproducción.

 double duration = 1; // seconds double freqOfTone = 1000; // hz int sampleRate = 8000; // a number double dnumSamples = duration * sampleRate; dnumSamples = Math.ceil(dnumSamples); int numSamples = (int) dnumSamples; double sample[] = new double[numSamples]; byte generatedSnd[] = new byte[2 * numSamples]; for (int i = 0; i < numSamples; ++i) { // Fill the sample array sample[i] = Math.sin(freqOfTone * 2 * Math.PI * i / (sampleRate)); } // convert to 16 bit pcm sound array // assumes the sample buffer is normalized. // convert to 16 bit pcm sound array // assumes the sample buffer is normalised. int idx = 0; int i = 0 ; int ramp = numSamples / 20 ; // Amplitude ramp as a percent of sample count for (i = 0; i< ramp; ++i) { // Ramp amplitude up (to avoid clicks) double dVal = sample[i]; // Ramp up to maximum final short val = (short) ((dVal * 32767 * i/ramp)); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } for (i = i; i< numSamples - ramp; ++i) { // Max amplitude for most of the samples double dVal = sample[i]; // scale to maximum amplitude final short val = (short) ((dVal * 32767)); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } for (i = i; i< numSamples; ++i) { // Ramp amplitude down double dVal = sample[i]; // Ramp down to zero final short val = (short) ((dVal * 32767 * (numSamples-i)/ramp )); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } AudioTrack audioTrack = null; // Get audio track try { audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, (int)numSamples*2, AudioTrack.MODE_STATIC); audioTrack.write(generatedSnd, 0, generatedSnd.length); // Load the track audioTrack.play(); // Play the track } catch (Exception e){ RunTimeError("Error: " + e); return false; } int x =0; do{ // Monitor playback to find when done if (audioTrack != null) x = audioTrack.getPlaybackHeadPosition(); else x = numSamples; } while (x 

Envolví las maravillosas soluciones anteriores en un pequeño paquete ordenado que es más utilizable de fábrica como un simple zumbador configurable. Lo ejecuta en un hilo de fondo y tiene métodos de parada y reproducción y un puñado de opciones que puede configurar.

Está en JCenter para que pueda agregarlo a su lista de dependencias de esta manera

 compile 'net.mabboud:android-tone-player:0.2' 

y lo usa así para un zumbador continuo

 ContinuousBuzzer tonePlayer = new ContinuousBuzzer(); tonePlayer.play(); // just an example don't actually use Thread.sleep in your app Thread.sleep(1000); tonePlayer.stop(); 

o un timbre que se reproduce solo una vez y puede configurar la frecuencia y el volumen como este

 OneTimeBuzzer buzzer = new OneTimeBuzzer(); buzzer.setDuration(5); // volume values are from 0-100 buzzer.setVolume(50); buzzer.setToneFreqInHz(110); 

Publicación ampliada del blog aquí al respecto aquí GitHub aquí

Aquí hay otro blog que muestra un sintetizador simple más una interfaz de usuario

http://audioprograming.wordpress.com/2012/10/18/a-simple-synth-in-android-step-by-step-guide-using-the-java-sdk/

También podría estar interesado en csound o pdlib (lib de datos puros) para Android.

Como hay un error en algunas versiones anteriores de Android que causa una pérdida de memoria al usar MODE_STATIC, modifiqué la respuesta de Xarph anterior para usar MODE_STREAM. Espero que ayude a algunos.

 public void playTone(double freqOfTone, double duration) { //double duration = 1000; // seconds // double freqOfTone = 1000; // hz int sampleRate = 8000; // a number double dnumSamples = duration * sampleRate; dnumSamples = Math.ceil(dnumSamples); int numSamples = (int) dnumSamples; double sample[] = new double[numSamples]; byte generatedSnd[] = new byte[2 * numSamples]; for (int i = 0; i < numSamples; ++i) { // Fill the sample array sample[i] = Math.sin(freqOfTone * 2 * Math.PI * i / (sampleRate)); } // convert to 16 bit pcm sound array // assumes the sample buffer is normalized. // convert to 16 bit pcm sound array // assumes the sample buffer is normalised. int idx = 0; int i = 0 ; int ramp = numSamples / 20 ; // Amplitude ramp as a percent of sample count for (i = 0; i< ramp; ++i) { // Ramp amplitude up (to avoid clicks) double dVal = sample[i]; // Ramp up to maximum final short val = (short) ((dVal * 32767 * i/ramp)); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } for (i = i; i< numSamples - ramp; ++i) { // Max amplitude for most of the samples double dVal = sample[i]; // scale to maximum amplitude final short val = (short) ((dVal * 32767)); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } for (i = i; i< numSamples; ++i) { // Ramp amplitude down double dVal = sample[i]; // Ramp down to zero final short val = (short) ((dVal * 32767 * (numSamples-i)/ramp )); // in 16 bit wav PCM, first byte is the low order byte generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } AudioTrack audioTrack = null; // Get audio track try { int bufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM); audioTrack.play(); // Play the track audioTrack.write(generatedSnd, 0, generatedSnd.length); // Load the track } catch (Exception e){ } if (audioTrack != null) audioTrack.release(); // Track play done. Release track. } 

Código modificado basado en la respuesta de Singhaks

 public class MainActivity extends Activity { private final int duration = 30; // seconds private final int sampleRate = 8000; private final int numSamples = duration * sampleRate; private final double sample[] = new double[numSamples]; private final double freqOfTone = 440; // hz private final byte generatedSnd[] = new byte[2 * numSamples]; Handler handler = new Handler(); private AudioTrack audioTrack; private boolean play = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, numSamples, AudioTrack.MODE_STREAM); } @Override protected void onResume() { super.onResume(); // Use a new tread as this can take a while Thread thread = new Thread(new Runnable() { public void run() { handler.post(new Runnable() { public void run() { playSound(); genTone(); } }); } }); thread.start(); } void genTone(){ // fill out the array while(play){ for (int i = 0; i < numSamples; ++i) { // float angular_frequency = sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone)); } int idx = 0; // convert to 16 bit pcm sound array // assumes the sample buffer is normalised. for (double dVal : sample) { short val = (short) (dVal * 32767); generatedSnd[idx++] = (byte) (val & 0x00ff); generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8); } audioTrack.write(generatedSnd, 0, numSamples); } } void playSound(){ play = true; audioTrack.play(); } } 
  float synth_frequency = 440; int minSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minSize, AudioTrack.MODE_STREAM); audioTrack.play(); short[] buffer = new short[minSize]; float angle = 0; while (true) { if (play) { for (int i = 0; i < buffer.length; i++) { float angular_frequency = (float)(2*Math.PI) * synth_frequency / SAMPLE_RATE; buffer[i] = (short)(Short.MAX_VALUE * ((float) Math.sin(angle))); angle += angular_frequency; } audioTrack.write(buffer, 0, buffer.length); } 

// Puedes agregar un valor arbitrario en synth_frequency para obtener un cambio de sonido; por ejemplo, puedes agregar una variable aleatoria para obtener sonido

Do major (16 notas)

  public class MainActivity extends AppCompatActivity { private double mInterval = 0.125; private int mSampleRate = 8000; private byte[] generatedSnd; private final double mStandardFreq = 440; Handler handler = new Handler(); private AudioTrack audioTrack; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onResume() { super.onResume(); // Use a new tread as this can take a while final Thread thread = new Thread(new Runnable() { public void run() { byte[] tempByte = new byte[0]; for (int i = 0; i < 16 ; i++ ){ double note = getNoteFrequencies(i); byte[] tonByteNote = getTone(mInterval, mSampleRate, note); tempByte = concat(tonByteNote, tempByte); } generatedSnd = tempByte; handler.post(new Runnable() { public void run() { playTrack(generatedSnd); } }); } }); thread.start(); } public byte[] concat(byte[] a, byte[] b) { int aLen = a.length; int bLen = b.length; byte[] c= new byte[aLen+bLen]; System.arraycopy(a, 0, c, 0, aLen); System.arraycopy(b, 0, c, aLen, bLen); return c; } private double getNoteFrequencies(int index){ return mStandardFreq * Math.pow(2, (double) index/12.0d); } private byte[] getTone(double duration, int rate, double frequencies){ int maxLength = (int)(duration * rate); byte generatedTone[] = new byte[2 * maxLength]; double[] sample = new double[maxLength]; int idx = 0; for (int x = 0; x < maxLength; x++){ sample[x] = sine(x, frequencies / rate); } for (final double dVal : sample) { final short val = (short) ((dVal * 32767)); // in 16 bit wav PCM, first byte is the low order byte generatedTone[idx++] = (byte) (val & 0x00ff); generatedTone[idx++] = (byte) ((val & 0xff00) >>> 8); } return generatedTone; } private AudioTrack getAudioTrack(int length){ if (audioTrack == null) audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, mSampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, length, AudioTrack.MODE_STATIC); return audioTrack; } private double sine(int x, double frequencies){ return Math.sin( 2*Math.PI * x * frequencies); } void playTrack(byte[] generatedSnd){ getAudioTrack(generatedSnd.length) .write(generatedSnd, 0, generatedSnd.length); audioTrack.play(); } } 

vea esta útil biblioteca

https://github.com/karlotoy/perfectTune

es fácil de usar

agregar esto a sus dependencias

  compile 'com.github.karlotoy:perfectTune:1.0.2' 

Y lo usas así:

 PerfectTune perfectTune = new PerfectTune(); perfectTune.setTuneFreq(desire_freq); perfectTune.playTune(); 

para detener la melodía:

 perfectTune.stopTune(); 

Hay varios progtwigs para esto, pero apestan. Medí algunos:

http://www.endolith.com/wordpress/2009/11/24/android-audio-applications/

Entonces no hagas lo que sea que ellos hagan. :RE