0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Android】TarsosDSP を 2024.02 時点の Android 開発環境で使ってみる

Last updated at Posted at 2024-02-10

はじめに

ピッチ(音高)を測るチューナーアプリを作るためライブラリを探していたら TarsosDSP というものを見つけた。 Android でも使用できるみたいだがかなり古い。TarsosDSP を使用した Android プロジェクトの GitHub リポジトリを見つけたがこれらも古い。

記事を書いている 2024.02 時点の最新の環境でも TarsosDSP を使えるのか検証する。

環境

  • Android Studio
    Android Studio Hedgehog | 2023.1.1 Patch 1
  • Target API Level 34
  • Empty Activity テンプレート (= Jetpack Compose)

1. Android プロジェクトを作成する

Empty Activity テンプレートでAndroid プロジェクトを新規作成する。

2. TarsosDSP のセットアップ

TarsosDSP の GitHub リポジトリの README に記載されたリンク 「TarsosDSP release directory」をクリックする。

TarsosDSP-Android-latest.jar をダウンロードする。

app/libs/TarsosDSP-Android-latest.jar として配置する。

app モジュールから TarsosDSP-Android-latest.jar を参照できるようにする。

app/build.gradle.kts
dependencies {
+   implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
}

3. AndroidManifest.xml にパーミッションを追加する

マイクのパーミッション RECORD_AUDIO を追加する。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

+   <uses-permission android:name="android.permission.RECORD_AUDIO" />
...

4. TarsosDSP API を使ってコーディングする

Empty Activity テンプレートで作られた MainActivity を利用して TarsosDSP API を使ったコーディングをする。
マイクから入力した音のピッチが測れるよう AudioDispatcherFactory.fromDefaultMicrophone! を使用する。

MainActivity.kt
class MainActivity : ComponentActivity() {

    private val pitchInHzStateFlow = MutableStateFlow(0.0f)
    private val noteStateFlow = MutableStateFlow("")

    private val requestPermissionLauncher = registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { granted ->
        if (granted) {
            initTarsosDsp()
        } else {
            //TODO: エラーダイアログを表示する
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        requestPermissionLauncher.launch(android.Manifest.permission.RECORD_AUDIO)

        setContent {
            val pitchInHzState by pitchInHzStateFlow.collectAsState()
            val noteState by noteStateFlow.collectAsState()

            HelloTunerTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Greeting(hz = pitchInHzState, note = noteState)
                }
            }
        }
    }

    private fun initTarsosDsp() {
        val dispatcher = AudioDispatcherFactory.fromDefaultMicrophone(22050, 1024, 0)

        val pdh = PitchDetectionHandler { res, e ->
            val pitchInHz = res.pitch
            runOnUiThread { processPitch(pitchInHz) }
        }
        val pitchProcessor: AudioProcessor =
            PitchProcessor(PitchEstimationAlgorithm.FFT_YIN, 22050f, 1024, pdh)
        dispatcher.addAudioProcessor(pitchProcessor)

        val audioThread = Thread(dispatcher, "Audio Thread")
        audioThread.start()
    }

    private fun processPitch(pitchInHz: Float) {
        pitchInHzStateFlow.value = pitchInHz
        if(pitchInHz >= 110 && pitchInHz < 123.47) {
            //A
            noteStateFlow.value = "A"
        }
        else if(pitchInHz >= 123.47 && pitchInHz < 130.81) {
            //B
            noteStateFlow.value = "B"
        }
        else if(pitchInHz >= 130.81 && pitchInHz < 146.83) {
            //C
            noteStateFlow.value = "C"
        }
        else if(pitchInHz >= 146.83 && pitchInHz < 164.81) {
            //D
            noteStateFlow.value = "D"
        }
        else if(pitchInHz >= 164.81 && pitchInHz < 174.61) {
            //E
            noteStateFlow.value = "E"
        }
        else if(pitchInHz >= 174.61 && pitchInHz < 185) {
            //F
            noteStateFlow.value = "F"
        }
        else if(pitchInHz >= 185 && pitchInHz < 196) {
            //G
            noteStateFlow.value = "G"
        }
    }
}

@Composable
fun Greeting(
    modifier: Modifier = Modifier,
    hz: Float,
    note: String
) {
    Column(
        modifier = modifier,
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Row {
            Text(text = "ノート:")
            Text(text = "${note}")
        }
        Row {
            Text(text = "周波数:")
            Text(text = "${hz.toString()}")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    HelloTunerTheme {
        Greeting(hz = 130.81f, note = "A")
    }
}

完成

補足

TarsosDSP の GitHub リポジトリの README のとおり、以下のように dependencies にパッケージを追加したり、

repositories {
    maven {
        name = "TarsosDSP repository"
        url = "https://mvn.0110.be/releases"
    }
}
dependencies {
    implementation 'be.tarsos.dsp:core:2.5'
    implementation 'be.tarsos.dsp:jvm:2.5'
}

ソースコードをビルドしてできた jar を使うと、

git clone --depth 1 https://JorenSix@github.com/JorenSix/TarsosDSP.git
cd TarsosDSP
./gradlew build

javax.sound.sampled.AudioRecord を参照する箇所でビルドエラーとなるはず。理由は、javax.sound.sampled パッケージは Android SDK に含まれてないからだ。 Android 用にビルドされた TarsosDSP-Android-latest.jar は javax.sound.sampled.AudioRecord の代替として android.media.AudioRecord を使用しているのでどうエラーは発生しない。

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?