Android로 임의의 음색 재생


92

Android에서 임의의 주파수 소리를 내도록 할 수있는 방법이 있습니까 (즉, 사전 녹음 된 사운드 파일을 원하지 않음)?

나는 주변을 둘러 보았고 ToneGenerator 가 내가 찾은 유일한 것인데 , 심지어는 가깝지만 표준 DTMF 톤만 출력 할 수있는 것 같습니다.

어떤 아이디어?


2
실제 해결책을 찾았습니까?
o0 '.

20
아니요,하지만 결국 프로젝트를하지 않았습니다.
Jeremy Logan

1
@JeremyLogan 그리고 당신은 긍정적 인 부정적인 피드백을 받았습니다. 롤.
TheRealChx101

답변:


109

원래 블로그 에서이 예제 코드 를 찾았 지만 끔찍한 소리를 생성하는 버그가있었습니다. 버그를 수정하고 여기에 결과 코드를 게시했습니다. 나를 위해 잘 작동하는 것 같습니다!

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 <steve@staticfree.info>
    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();
    }
}

2
이 줄이 맞습니까? audioTrack.write (generatedSnd, 0, numSamples); 또는 샘플 당 2 바이트가 있으므로 numSamples * 2 여야합니다. 또한 쓰기 방법은 단락 배열을 취하므로 중간 바이트 배열을 만드는 이점은 무엇입니까?
Damian Kennedy

2
정말 좋은 예입니다. 감사합니다. 그러나 또 다른 불쾌한 버그를 발견했습니다 (코드를 확장하는 경우). , generatedSnd.length);
AudioDroid 2011-06-05

6
AudioTrack 생성자에서 "numSamples"를 사용하는 대신 다섯 번째 매개 변수가 "버퍼 크기 (바이트)"이기 때문에 generatedSnd.length를 사용해야합니다. 이 예는 톤의 전반부 만 재생합니다.
Torben

5
@ Black27 샘플은 진폭 범위가 0.0~ 1.0. 곱하기 32767는 16 비트 고정 소수점 범위로 변환합니다. AudioTrack은 버퍼가 적은 것으로 예상 엔디안 포맷. 따라서 다음 두 줄은 바이트 순서를 빅 엔디안에서 리틀 엔디안으로 변환합니다.
ains

2
private static final int sampleRate = 192000 사용; 초음속 연주를 할 수있었습니다
user3505444

26

위의 코드 개선 :

클릭을 방지하기 위해 진폭 램프 업 및 다운 다운을 추가합니다.

압정이 언제 끝났는지 확인하는 코드를 추가합니다.

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<numSamples);

if (audioTrack != null) audioTrack.release();                    // Track play done. Release track.

1
주요 변화는 진폭의 증가 및 감소였습니다. 원래 코드는 최대 진폭으로 시작되고 끝났습니다. 이렇게하면 톤의 시작과 끝에서 딸깍하는 소리가납니다. 이 코드는 샘플의 처음 20 %에 걸쳐 진폭을 0에서 최대 진폭으로 증가시킵니다. 그런 다음 샘플의 마지막 20 %에 걸쳐 전체 진폭에서 0으로 감소합니다. 음색이 더 부드럽고 훨씬 더 즐겁습니다. 다른 변경 사항은 톤의 재생을 모니터링하고 톤이 끝날 때까지 계속하지 않는 것입니다.
Xarph

나는 그것을 실행할 수 없다 .. 나는 첫 번째 것을 실행할 수있다. 그러나 그것을 수정하는 방법을 정말로 이해할 수 없다. 당신이 한 일을 수정하는 방법을 정말로 이해할 수 없다. 나는 클릭 소리를 없애고 싶기 때문에 정말로 도움이 될 것이다. .
Coder

3
+1, 그러나이 답변의 코드는 컴파일에 가깝지 않습니다. 여기에 올바르게 구현했습니다. gist.github.com/SuspendedPhan/7596139 Steve의 genTone () 메서드를 제 것으로 바꾸면 램핑 효과를 얻을 수 있습니다.
Dylan P

MODE_STATIC에 메모리 누수가 있기 때문에 아래의 MODE_STREAM을 사용하도록 코드를 수정했습니다
극단적 인

API로 시작하여 setVolume ()을 사용하여 램프를 수행 할 수 있습니다. 이를 통해 아주 작은 샘플을 반복하고 동적 길이의 사운드를 재생할 수도 있습니다 (예 : 사용자가 버튼을 잡고있는 동안). 코드 예제 : github.com/stefanhaustein/android-tone-generator/blob/master/...
스테판 HAUSTEIN

8

위의 멋진 솔루션을 간단한 구성 가능한 버저로 즉시 사용할 수있는 깔끔한 작은 패키지로 포장했습니다. 백그라운드 스레드에서 실행되며 중지 및 재생 방법과 설정할 수있는 몇 가지 옵션이 있습니다.

JCenter에 있으므로 다음과 같이 종속성 목록에 추가 할 수 있습니다.

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

이렇게 계속 부저를 울리면

ContinuousBuzzer tonePlayer = new ContinuousBuzzer();
tonePlayer.play();

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

또는 부저가 한 번만 울리고 이렇게 주파수와 볼륨을 설정할 수 있습니다.

OneTimeBuzzer buzzer = new OneTimeBuzzer();
buzzer.setDuration(5);

// volume values are from 0-100
buzzer.setVolume(50);
buzzer.setToneFreqInHz(110);

여기에 대한 확장 블로그 게시물 GitHub here


@Melchester는 이제 수정되었습니다. 머리를 올려 주셔서 감사하고 그것에 대해 미안합니다
meese

4

MODE_STATIC을 사용할 때 메모리 누수를 유발하는 일부 이전 Android 버전에는 버그가 있으므로 MODE_STREAM을 사용하도록 위의 Xarph의 답변을 수정했습니다. 바라건대 일부 도움이 될 것입니다.

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.
}


3

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();
    }
}

2
    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);
    } 

// synth_frequency에 임의의 값을 추가하여 사운드를 변경할 수 있습니다. 예를 들어 랜덤 변수를 추가하여 사운드를 얻을 수 있습니다.


결국 모든 것을 짧은 것으로 변환하고 있습니다. 앵글을 플로트로 할 이유가 없습니다. 이중 수학은 동일한 속도이며 많은 캐스팅이 필요하지 않습니다.
Tatarize

2

전공 (16 개 음표)

 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();
  }

}

2

이 유용한 라이브러리보기

https://github.com/karlotoy/perfectTune

사용하기 쉽습니다

이것을 당신의 의존성에 추가하십시오

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

그리고 다음과 같이 사용합니다.

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

곡을 중지하려면 :

perfectTune.stopTune();

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.