fixed segfault bug
This commit is contained in:
parent
37ec0f7a6d
commit
a78093c6b8
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@
|
|||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
/app/release/
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
package net.sergeych.karabass
|
package net.sergeych.karabass
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.media.AudioFormat
|
import android.media.AudioFormat
|
||||||
@ -40,6 +39,7 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
@Volatile private var audioTrack: AudioTrack? = null
|
@Volatile private var audioTrack: AudioTrack? = null
|
||||||
@Volatile private var isPlaying = false
|
@Volatile private var isPlaying = false
|
||||||
@Volatile private var shouldStop = false
|
@Volatile private var shouldStop = false
|
||||||
|
@Volatile private var isReleased = false
|
||||||
private var currentThread: Thread? = null
|
private var currentThread: Thread? = null
|
||||||
|
|
||||||
// Синхронизированные переменные для потокобезопасности
|
// Синхронизированные переменные для потокобезопасности
|
||||||
@ -55,23 +55,24 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
private var envelopeState = 0 // 0: idle, 1: attack, 2: release
|
private var envelopeState = 0 // 0: idle, 1: attack, 2: release
|
||||||
|
|
||||||
// Параметры огибающей
|
// Параметры огибающей
|
||||||
private val attackSamples = (0.02 * SAMPLE_RATE).toInt() // Увеличил до 20ms для более плавного старта
|
private val attackSamples = (0.02 * SAMPLE_RATE).toInt()
|
||||||
private val releaseSamples = (0.2 * SAMPLE_RATE).toInt() // 200ms релиз
|
private val releaseSamples = (0.2 * SAMPLE_RATE).toInt()
|
||||||
|
|
||||||
// Минимальный буфер для low-latency
|
// Минимальный буфер для low-latency
|
||||||
private val bufferSize = 1024 // Увеличил буфер для стабильности
|
private val bufferSize = 1024
|
||||||
|
|
||||||
// Для управления выводом
|
// Для управления выводом
|
||||||
private var lastBufferWritten = false
|
|
||||||
private var silenceBuffersWritten = 0
|
private var silenceBuffersWritten = 0
|
||||||
private val requiredSilenceBuffers = 3 // Дополнительные буферы тишины после затухания
|
private val requiredSilenceBuffers = 3
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initializeAudioTrack()
|
initializeAudioTrack()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeAudioTrack() {
|
private fun initializeAudioTrack(): Boolean {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
|
if (isReleased) return false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Освобождаем старый AudioTrack если есть
|
// Освобождаем старый AudioTrack если есть
|
||||||
audioTrack?.let { track ->
|
audioTrack?.let { track ->
|
||||||
@ -81,11 +82,11 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
}
|
}
|
||||||
track.release()
|
track.release()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
// Игнорируем ошибки при освобождении
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val newTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
val attributes = AudioAttributes.Builder()
|
val attributes = AudioAttributes.Builder()
|
||||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||||
@ -100,7 +101,7 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
AudioTrack.Builder()
|
AudioTrack.Builder()
|
||||||
.setAudioAttributes(attributes)
|
.setAudioAttributes(attributes)
|
||||||
.setAudioFormat(format)
|
.setAudioFormat(format)
|
||||||
.setBufferSizeInBytes(bufferSize * 4) // Увеличил размер буфера
|
.setBufferSizeInBytes(bufferSize * 4)
|
||||||
.build()
|
.build()
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@ -109,22 +110,29 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
SAMPLE_RATE,
|
SAMPLE_RATE,
|
||||||
AudioFormat.CHANNEL_OUT_MONO,
|
AudioFormat.CHANNEL_OUT_MONO,
|
||||||
AudioFormat.ENCODING_PCM_16BIT,
|
AudioFormat.ENCODING_PCM_16BIT,
|
||||||
bufferSize * 4, // Увеличил размер буфера
|
bufferSize * 4,
|
||||||
AudioTrack.MODE_STREAM
|
AudioTrack.MODE_STREAM
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
audioTrack?.play()
|
if (newTrack.state == AudioTrack.STATE_INITIALIZED) {
|
||||||
|
audioTrack = newTrack
|
||||||
|
newTrack.play()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
newTrack.release()
|
||||||
|
return false
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
return false
|
||||||
initializeAudioTrack()
|
|
||||||
}, 100)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startNote(frequency: Float) {
|
fun startNote(frequency: Float) {
|
||||||
|
if (isReleased) return
|
||||||
|
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
targetFrequency = frequency
|
targetFrequency = frequency
|
||||||
|
|
||||||
@ -135,7 +143,6 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
envelopeSamples = 0
|
envelopeSamples = 0
|
||||||
envelopeValue = 0.0
|
envelopeValue = 0.0
|
||||||
currentFrequency = frequency
|
currentFrequency = frequency
|
||||||
lastBufferWritten = false
|
|
||||||
silenceBuffersWritten = 0
|
silenceBuffersWritten = 0
|
||||||
|
|
||||||
// Сбрасываем фазу при начале новой ноты для предотвращения щелчков
|
// Сбрасываем фазу при начале новой ноты для предотвращения щелчков
|
||||||
@ -152,6 +159,8 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun stopNote() {
|
fun stopNote() {
|
||||||
|
if (isReleased) return
|
||||||
|
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
if (isNoteOn) {
|
if (isNoteOn) {
|
||||||
isNoteOn = false
|
isNoteOn = false
|
||||||
@ -162,29 +171,39 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun changeNote(frequency: Float) {
|
fun changeNote(frequency: Float) {
|
||||||
|
if (isReleased) return
|
||||||
|
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
targetFrequency = frequency
|
targetFrequency = frequency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startAudioGeneration() {
|
private fun startAudioGeneration() {
|
||||||
if (isPlaying) return
|
if (isPlaying || isReleased) return
|
||||||
|
|
||||||
shouldStop = false
|
shouldStop = false
|
||||||
isPlaying = true
|
isPlaying = true
|
||||||
lastBufferWritten = false
|
|
||||||
silenceBuffersWritten = 0
|
|
||||||
|
|
||||||
|
// Проверяем и при необходимости пересоздаем AudioTrack
|
||||||
val track = audioTrack
|
val track = audioTrack
|
||||||
if (track == null || track.state != AudioTrack.STATE_INITIALIZED) {
|
if (track == null || track.state != AudioTrack.STATE_INITIALIZED) {
|
||||||
initializeAudioTrack()
|
if (!initializeAudioTrack()) {
|
||||||
|
isPlaying = false
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentThread = Thread {
|
currentThread = Thread {
|
||||||
val buffer = ShortArray(bufferSize)
|
val buffer = ShortArray(bufferSize)
|
||||||
|
var currentAudioTrack: AudioTrack? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (!shouldStop && isPlaying) {
|
// Получаем ссылку на AudioTrack один раз в начале
|
||||||
|
synchronized(lock) {
|
||||||
|
currentAudioTrack = audioTrack
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!shouldStop && isPlaying && !isReleased) {
|
||||||
// Заполняем буфер
|
// Заполняем буфер
|
||||||
var allSamplesZero = true
|
var allSamplesZero = true
|
||||||
for (i in buffer.indices) {
|
for (i in buffer.indices) {
|
||||||
@ -196,7 +215,7 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Безопасная запись в AudioTrack
|
// Безопасная запись в AudioTrack
|
||||||
val track = audioTrack
|
val track = currentAudioTrack
|
||||||
if (track?.state == AudioTrack.STATE_INITIALIZED && track.playState == AudioTrack.PLAYSTATE_PLAYING) {
|
if (track?.state == AudioTrack.STATE_INITIALIZED && track.playState == AudioTrack.PLAYSTATE_PLAYING) {
|
||||||
try {
|
try {
|
||||||
track.write(buffer, 0, buffer.size)
|
track.write(buffer, 0, buffer.size)
|
||||||
@ -204,18 +223,29 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
// Если это последний буфер с данными, запоминаем это
|
// Если это последний буфер с данными, запоминаем это
|
||||||
if (allSamplesZero && envelopeState == 0) {
|
if (allSamplesZero && envelopeState == 0) {
|
||||||
silenceBuffersWritten++
|
silenceBuffersWritten++
|
||||||
if (silenceBuffersWritten >= requiredSilenceBuffers) {
|
|
||||||
lastBufferWritten = true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
silenceBuffersWritten = 0
|
silenceBuffersWritten = 0
|
||||||
lastBufferWritten = false
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (!shouldStop) {
|
if (!shouldStop && !isReleased) {
|
||||||
e.printStackTrace()
|
// Пытаемся восстановить AudioTrack
|
||||||
android.os.Handler(android.os.Looper.getMainLooper()).post {
|
synchronized(lock) {
|
||||||
initializeAudioTrack()
|
if (initializeAudioTrack()) {
|
||||||
|
currentAudioTrack = audioTrack
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// AudioTrack не готов
|
||||||
|
if (!shouldStop && !isReleased) {
|
||||||
|
synchronized(lock) {
|
||||||
|
if (initializeAudioTrack()) {
|
||||||
|
currentAudioTrack = audioTrack
|
||||||
|
} else {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,12 +255,11 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
// 1. Огибающая завершила релиз
|
// 1. Огибающая завершила релиз
|
||||||
// 2. Мы записали несколько буферов тишины для гарантии
|
// 2. Мы записали несколько буферов тишины для гарантии
|
||||||
// 3. Нет активной ноты
|
// 3. Нет активной ноты
|
||||||
if (lastBufferWritten) {
|
if (silenceBuffersWritten >= requiredSilenceBuffers) {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
if (!isNoteOn && envelopeState == 0) {
|
if (!isNoteOn && envelopeState == 0) {
|
||||||
// Даем дополнительное время на вывод последних буферов
|
|
||||||
Thread.sleep(50)
|
|
||||||
isPlaying = false
|
isPlaying = false
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,6 +276,8 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
|
|
||||||
private fun generateSample(): Double {
|
private fun generateSample(): Double {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
|
if (isReleased) return 0.0
|
||||||
|
|
||||||
// Плавное изменение частоты для легато
|
// Плавное изменение частоты для легато
|
||||||
if (abs(currentFrequency - targetFrequency) > 0.1) {
|
if (abs(currentFrequency - targetFrequency) > 0.1) {
|
||||||
currentFrequency += (targetFrequency - currentFrequency) * 0.2f
|
currentFrequency += (targetFrequency - currentFrequency) * 0.2f
|
||||||
@ -318,21 +349,13 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
// Смешиваем с весами, характерными для тубы
|
// Смешиваем с весами, характерными для тубы
|
||||||
val mixed = fundamental * 0.7 + overtone2 * 0.5 + overtone3 * 0.3 + overtone4 * 0.2 + overtone5 * 0.15
|
val mixed = fundamental * 0.7 + overtone2 * 0.5 + overtone3 * 0.3 + overtone4 * 0.2 + overtone5 * 0.15
|
||||||
|
|
||||||
val result = tanh(mixed)
|
|
||||||
|
|
||||||
// Очень мягкое ограничение на резкие переходы
|
|
||||||
return if (abs(result) > 0.8) {
|
|
||||||
result * 0.99 // Слегка уменьшаем пики
|
|
||||||
} else {
|
|
||||||
result
|
|
||||||
}
|
|
||||||
// Очень мягкое ограничение для теплого звука
|
// Очень мягкое ограничение для теплого звука
|
||||||
// return tanh(mixed)
|
return tanh(mixed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isActive(): Boolean {
|
fun isActive(): Boolean {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
return isNoteOn || envelopeState != 0
|
return !isReleased && (isNoteOn || envelopeState != 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,20 +366,14 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
|
isReleased = true
|
||||||
shouldStop = true
|
shouldStop = true
|
||||||
isPlaying = false
|
isPlaying = false
|
||||||
|
|
||||||
// Даем время на завершение релиза и вывод всех буферов
|
// Останавливаем поток генерации
|
||||||
try {
|
|
||||||
Thread.sleep(300)
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
// Игнорируем
|
|
||||||
}
|
|
||||||
|
|
||||||
// Безопасная остановка потока
|
|
||||||
currentThread?.let { thread ->
|
currentThread?.let { thread ->
|
||||||
try {
|
try {
|
||||||
thread.join(200)
|
thread.join(500) // Увеличил время ожидания
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
thread.interrupt()
|
thread.interrupt()
|
||||||
}
|
}
|
||||||
@ -375,7 +392,7 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
}
|
}
|
||||||
track.release()
|
track.release()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
// Игнорируем ошибки при освобождении
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
audioTrack = null
|
audioTrack = null
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.13.0"
|
agp = "8.13.0"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.2.20"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.1.5"
|
junitVersion = "1.1.5"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user