ํ์ฌ ์งํํ๊ณ ์๋ ํ๋ก์ ํธ์ ๋ฉ์ธ ๊ธฐ๋ฅ์ ๋ ๊ฐ์ง์ด๋ค.
1) ์ฌ์ฉ์์ ์์ฑ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ค์๊ฐ์ผ๋ก ๊ฐ ์์ pitch๋ฅผ detectํ๊ณ ์ด๋ฅผ note๋ก ๋ฐ๊พธ์ด์ฃผ๋ ๊ธฐ๋ฅ
2) ์ฌ์ฉ์์๊ฒ ์๋ง์ key์ ๋ง์ถ์ด ๊ธฐ์กด ์์ ๋ฐ์ดํฐ์ pitch๋ฅผ shiftํด์ฃผ๋ ๊ธฐ๋ฅ
์ด๋ฒ ํฌ์คํ ์์๋ ์ฒซ ๋ฒ์งธ ๊ธฐ๋ฅ์ ํ ์คํธํ๋ ๊ณผ์ ์ ๋ค๋ฃจ๋ ค๊ณ ํ๋ค.
Library ์ ์
- pitch detection ๋ผ์ด๋ธ๋ฌ๋ฆฌ : Praat(+Parselmouth), aubio, librosa
- pitch detection ์๊ณ ๋ฆฌ์ฆ(JS) : PitchDetect
- ๋ฅ๋ฌ๋ ๋ชจ๋ธ : SPICE(tensorflow)
๋๋ต 2๋ฌ ๋์ ์ค์๊ฐ pitch dectection / pitch shifting์ ์ง์ํ๋ฉด์ ์คํ ์์ค์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐพ์๋ณด์์ง๋ง, ๋๋ถ๋ถ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ค์๊ฐ์ฑ์ ์ ๊ณตํ์ง ์์๋ค. ๊ทธ๋ฌ๋ ์ค, ์ค์๊ฐ์ฑ์ ์ง์ํ๋ฉด์๋ ํ์ฌ ์ฌ์ฉํ๊ณ ์๋ ์๋๋ก์ด๋ ์คํ๋์ค์ 100% ํธํ๋๋ TarsosDSP ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐพ๊ฒ ๋์๋ค.
TarsosDSP
์ค๋์ค ํ๋ก์ธ์ฑ์ ์ํ Java ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก, YIN, Mcleod Pitch method, Dynamic Wavelet Algorithm Pitch Tracking๊ณผ ๊ฐ์ ๋ค์ํ pitch detection ์๊ณ ๋ฆฌ์ฆ๋ค์ ํฌํจํ๊ณ ์๋ค. ๋ํ Goertzel DTMF ๋์ฝ๋ฉ ์๊ณ ๋ฆฌ์ฆ, time stretch algorithm(WSOLA), resampling, filters, simple synthesis, some audio effects์ pitch shifting ์๊ณ ๋ฆฌ์ฆ์ ์ ๊ณตํ๋ค.
TarsosDSP ์ค์น
01. TarsosDSP ๋ค์ด๋ก๋
์๋๋ก์ด๋์์ TarsosDSP Library๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํด๋น ๋ฆด๋ฆฌ์ฆ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ค์ด๋ฐ์์ผ ํ๋ค.
TarsosDSP-latest/TarsosDSP-Android-latest.jar ํ์ผ์ ๋ค์ด๋ก๋ํ๋ค. ์ด ๋ ์์ถ์ ํ์ง ์๋๋ค!
02. ํ๋ก์ ํธ์ dependency ์ถ๊ฐ
lib ํด๋์ ๋ค์ด๋ฐ์ jar ํ์ผ์ ์ถ๊ฐํ๊ธฐ ์ํด Android -> Project๋ก ๊ตฌ์กฐ๋ฅผ ๋ณ๊ฒฝํ๋ค.
๋ค์ด๋ฐ์ TarsosDSP-latest.jar ํ์ผ์ ๊ทธ๋๋ก libs ํด๋์ ๋ฃ์ด์ค๋ค.
์๋จ ๋ฉ๋ด File -> Project Structure์์ Dependency๋ฅผ ์ ํํ ํ, app ๋ชจ๋์ ์ ํํ๋ฉด ํ์ฌ ์ถ๊ฐ๋ dependency ๋ชฉ๋ก์ด ๋ณด์ธ๋ค.
(+) ๋ฒํผ์ ๋๋ฅธ ํ JAR/AAR Dependency๋ฅผ ์ ํํ ํ, ๊ฒฝ๋ก๋ช ์ libs/TarsosDSP-Android-latest.jar๋ฅผ ์ ๋ ฅํ์ฌ ๋ฑ๋กํ๋ค
์ ์์ ์ผ๋ก ๋ฑ๋ก๋์๋ค๋ฉด, build.gradle ํ์ผ ํ๋จ์ ์๋์ ๊ฐ์ด ํ์๋ ๊ฒ์ด๋ค.
// build.gradle(app)
dependencies {
...
implementation files('libs/TarsosDSP-Android-latest.jar')
}
03. xml ํ์ผ ์์
๊ธฐ๋ณธ์ ์ผ๋ก ๋ง์ดํฌ ๊ธฐ๋ฅ์ ์ฌ์ฉํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๊ถํ ๋ฑ๋ก์ ํธ์๋ฅผ ์ํด targetSdkVersion์ 22๋ก ์ค์ ํด์ค๋ค.
// build.gradle(app)
android {
...
defaultConfig {
...
targetSdk 22
...
}
AndroidManifest.xml์๋ aubio ๋ฐ storage ๊ถํ์ ๋ฑ๋กํ๋ค. Manifest ํ๊ทธ ๋ฐ๋ก ๋ฐ์ ํด๋น ์ฝ๋๋ฅผ ๋ฃ์ด์ค๋ค.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="...">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
์ค์ต ์ค๋ช ๋ฐ ์ฝ๋
1. ๊ธฐ๋ณธ GUI ์ค์
๋ น์๋๋ ์์ฑ์ pitch๋ฅผ ์ค์๊ฐ์ผ๋ก ํ์ํ๊ธฐ ์ํ ํ ์คํธ๋ทฐ์ ๋ น์, ์ฌ์ ๋ฒํผ์ ๊ตฌํํ๋ค.
// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="507dp"
android:padding="16dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pitch:" />
<TextView
android:id="@+id/pitchTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="0"
android:textSize="50sp" />
</FrameLayout>
<Button
android:id="@+id/recordButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="๋
น์" />
<Button
android:id="@+id/playButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="์ฌ์" />
</LinearLayout>
2. TarsosDSP Format Settings
์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ์ด๋ค ์์ผ๋ก processingํ ๊ฒ์ธ์ง ์ง์ ํด์ฃผ๋ ๊ณผ์ ์ด๋ค. ์ด๋ฒ ์ค์ต์์ testingํ ์ฃผ์ ๊ธฐ๋ฅ์ด ์๋๊ธฐ ๋๋ฌธ์ ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ์ฌ ๊ฐ์ ์ง์ ํ์๋ค. ์ธ๋ฐํ ๊ฐ์ ๋์ค์ ์ง์ ํ ์์ ์ด๋ค.
public TarsosDSPAudioFormat(TarsosDSPAudioFormat.Encoding encoding,
float sampleRate,
int sampleSizeInBits,
int channels,
int frameSize,
float frameRate,
boolean bigEndian)
Parameters
encoding - the audio encoding technique
sampleRate - the number of samples per secondsample
SizeInBits - the number of bits in each sample
channels - the number of channels (1 for mono, 2 for stereo, and so on)
frameSize - the number of bytes in each frame
frameRate - the number of frames per second
bigEndian - indicates whether the data for a single sample is stored in big-endian byte order (false means little-endian)
TarsosDSPAudioFormat tarsosDSPAudioFormat;
protected void onCreate(Bundle savedInstanceState)
{
...
tarsosDSPAudioFormat=new TarsosDSPAudioFormat(TarsosDSPAudioFormat.Encoding.PCM_SIGNED,
22050,
2 * 8,
1,
2 * 1,
22050,
ByteOrder.BIG_ENDIAN.equals(ByteOrder.nativeOrder()));
...
}
3. Voice Recording & Real Time Pitch Detection
์ฐ๋ฆฌ ํ๋ก์ ํธ์์ ํต์ฌ์ ์ธ ๋ถ๋ถ์ ๋ด๋นํ๋ ๊ธฐ๋ฅ์ด๋ค!!
1) TarsosDSP๋ฅผ ์ด์ฉํ์ฌ ๋ง์ดํฌ๋ก ์ฌ์ฉ์์ ์์ฑ์ ๋ น์ํ๋ ๋์์ ํด๋น ์์ฑ์ ์ฃผํ์๋ฅผ Note๋ก ๋ฐ๊พธ์ด์ค๋ค.
2) ํด๋น ์์ฑ์ด ์ ๋ ฅ๋ ์๊ฐ๊ณผ ๋ณํ๋ Note๋ฅผ ํ๋์ hashmap์ผ๋ก ๋ฌถ์ด ์ ์ฅํ๋ค.
3) ์ด hashmap๊ณผ ์๋ ์์์ <์๊ฐ, note>hashmap์ ๋น๊ตํ์ฌ ์ฌ์ฉ์์ key๊ฐ ์๊ณก์ ์์ ๊ณผ ๋ฒ์ด๋ฌ๋์ง, ๋ฐ์๊ฐ ์ผ์นํ๋์ง ํ๋จํ๋๋ฐ ์ฌ์ฉ๋๋ค.
์ด ํฌ์คํ ์์๋ ์ฌ์ฉ์์ ์์ฑ์ ์ฒ๋ฆฌํ๋ (1)~(2)๋ง ์ค๋ช ํ๊ฒ ๋ค.
- ์์ ์๊ฐ ์ธก์
long start = System.currentTimeMillis();
- <์๊ฐ, note>๋ฅผ ๋ด์ hashmap ์์ฑ. milisecond ๋จ์๋ก ๋ด์์ผ ํ๊ธฐ์ Double ์๋ฃํ์ ์ ํํ๋ค.
HashMap<Double, String> dictionary = new HashMap<Double, String>();
- ํ์ฌ ์ฌ์ฉํ๊ณ ์๋ dispatcher ๊ฐ์ฒด๋ฅผ ์ ๊ฑฐํ๊ณ , ๋ง์ดํฌ๋ก๋ถํฐ ์ ๋ ฅ์ ๋ฐ๋ dispatcher ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
releaseDispatcher();
dispatcher = AudioDispatcherFactory.fromDefaultMicrophone(22050,1024,0);
- ์ ๋ ฅ๋ฐ์ ์์ฑ ํ์ผ์ ์ ์ฅํ๊ธฐ ์ํด RandomAccessFile์ ์์ฑํด์ฃผ๊ณ , ์ง์ ํ ์ถ๋ ฅ์ผ๋ก ์์ฑ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋กํ๋ WriterProcessor(AudioProcessor) ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ dispatcher์ ์ถ๊ฐํ๋ค.
RandomAccessFile randomAccessFile = new RandomAccessFile(file,"rw");
AudioProcessor recordProcessor = new WriterProcessor(tarsosDSPAudioFormat, randomAccessFile);
dispatcher.addAudioProcessor(recordProcessor);
- pitch detection handler๋ฅผ ๋ง๋ค์ด ์ ๋ ฅ๋ pitch๋ฅผ ๊ฐ์ ธ์จ ํ, note๋ก ๋ณํํด์ค๋ค.
PitchDetectionHandler pitchDetectionHandler = new PitchDetectionHandler() {
@Override
public void handlePitch(PitchDetectionResult res, AudioEvent e){
final float pitchInHz = res.getPitch(); // ์
๋ ฅ๋ pitch ๊ฐ์ ธ์ค๊ธฐ
String octav = ProcessPitch.processPitch(pitchInHz); // pitch -> note
runOnUiThread(new Runnable() {
...
});
}
};
- ๋ณํํ note๋ฅผ ํ๋ฉด์ ํ์ํด์ฃผ๊ณ , ๋ น์์ด ์์๋ ์ดํ๋ก ๋ช ์ด๊ฐ ์ง๋ฌ๋์ง ๊ณ์ฐํ์ฌ hashmap์ ๋ฃ๋๋ค.
runOnUiThread(new Runnable() {
@Override
public void run() {
pitchTextView.setText(octav); // ํ๋ฉด์ ํ์๋๋ note ๋ณ๊ฒฝ
long end = System.currentTimeMillis(); // note๊ฐ ์
๋ ฅ๋ ์๊ฐ ๊ฐ์ ธ์ค๊ธฐ(์ผ๋ฐ์๊ฐ)
double time = (end-start)/(1000.0); // ๋
น์์ด ์์๋ ์ดํ์ ์๊ฐ์ผ๋ก ๋ณ๊ฒฝ
dictionary.put(time, octav); // hashmap์ <time, note> ์
๋ ฅ
}
});
- (Optional)๋ น์์ด ์ข ๋ฃ๋๋ฉด, ์ ์ฅ๋ dictionary์ ์๋ ๊ฐ์ ํ์ธํ๋ค.
Set set = dictionary.entrySet();
Iterator iter2 = set.iterator();
while(iter2.hasNext()) {
Map.Entry entry = (Map.Entry)iter2.next();
Log.v("result",(Double)entry.getKey()+" "+(String)entry.getValue());
}
- pitch detection์ pitchProcessor ํด๋์ค๋ฅผ ํตํด ์ํ๋๋๋ฐ, ์ด ๋ ์ค์๊ฐ pitch detection ๊ฒฐ๊ณผ๋ฅผ ์ ๋ฌ๋ฐ๊ธฐ ์ํ thread handler ๊ฐ์ฒด๋ฅผ ์ง์ ํด์ค์ผ ํ๋ค.
PitchProcessor(PitchProcessor.PitchEstimationAlgorithm algorithm,
float sampleRate,
int bufferSize,
PitchDetectionHandler handler)
AMDF - A pitch extractor that extracts the Average Magnitude Difference (AMDF) from an audio buffer.DYNAMIC_WAVELET - An implementation of a dynamic wavelet pitch detection algorithm (See DynamicWavelet), described in a paper by Eric Larson and Ross Maddox “Real-Time Time-Domain Pitch Tracking Using Wavelets
FFT_PITCH - Returns the frequency of the FFT-bin with most energy.
FFT_YIN - A YIN implementation with a faster FastYin for the implementation.
MPM - McLeodPitchMethod.YIN - YIN algorithm.
AudioProcessor pitchProcessor = new PitchProcessor(
PitchProcessor.PitchEstimationAlgorithm.FFT_YIN,
22050,
1024,
pitchDetectionHandler);
dispatcher.addAudioProcessor(pitchProcessor);
- Thread๋ก dispatcher๋ฅผ ์คํํด์ค๋ค.
Thread audioThread = new Thread(dispatcher, "Audio Thread");
audioThread.start();
+ Pitch to Note(ProcessPitch)
16.35~7902.13Hz๋ฅผ note๋ก ๋ณํํ๋ ํจ์์ด๋ค. ๋ณํํ์ ์๋ ๋ด์ฉ์ ์ฎ๊ฒจ์๋ค. ์๊ฐ๋ณด๋ค ์์ด ๋ง์์ ์ผ๋จ ๋ฐ๋ก class๋ฅผ ๋ง๋ค์ด ๋ถ๋ฆฌํด๋์๋ค. ์ค์ ๊ตฌํ ๋๋ DB์ ๋ฃ๊ณ ๋ถ๋ฌ์ค๋ ํ์์ผ๋ก ์งํํด์ผ ํ ๊ฒ ๊ฐ๋ค.
public class ProcessPitch {
// pitch -> key
public static String processPitch(float pitchInHz){
String noteText = "Nope";
if(pitchInHz >= 16.35 && pitchInHz < 17.32) {
noteText = "C0";
}
else if(pitchInHz >= 17.32 && pitchInHz < 18.35) {
noteText = "C#0";
}
else if(pitchInHz >= 18.35 && pitchInHz < 19.45) {
noteText = "D0";
}
else if(pitchInHz >= 19.45 && pitchInHz < 20.60) {
noteText = "D#0";
}
else if(pitchInHz >= 20.60 && pitchInHz <= 21.83) {
noteText = "E0";
}
...
return noteText;
}
'๐ฅ ํ๋ก์ ํธ > ์กธ์ ํ๋ก์ ํธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[์๋๋ก์ด๋] SharedPreference ์ฌ์ฉ๊ณผ ScrollView Event (0) | 2022.05.17 |
---|
๋๊ธ