less clicks on note end/start
This commit is contained in:
parent
ab0c5147e6
commit
8b68261701
@ -55,11 +55,16 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
private var envelopeState = 0 // 0: idle, 1: attack, 2: release
|
||||
|
||||
// Параметры огибающей
|
||||
private val attackSamples = (0.01 * SAMPLE_RATE).toInt() // 10ms атака
|
||||
private val releaseSamples = (0.15 * SAMPLE_RATE).toInt() // 150ms релиз
|
||||
private val attackSamples = (0.02 * SAMPLE_RATE).toInt() // Увеличил до 20ms для более плавного старта
|
||||
private val releaseSamples = (0.2 * SAMPLE_RATE).toInt() // 200ms релиз
|
||||
|
||||
// Минимальный буфер для low-latency
|
||||
private val bufferSize = 512
|
||||
private val bufferSize = 1024 // Увеличил буфер для стабильности
|
||||
|
||||
// Для управления выводом
|
||||
private var lastBufferWritten = false
|
||||
private var silenceBuffersWritten = 0
|
||||
private val requiredSilenceBuffers = 3 // Дополнительные буферы тишины после затухания
|
||||
|
||||
init {
|
||||
initializeAudioTrack()
|
||||
@ -68,6 +73,18 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
private fun initializeAudioTrack() {
|
||||
synchronized(lock) {
|
||||
try {
|
||||
// Освобождаем старый AudioTrack если есть
|
||||
audioTrack?.let { track ->
|
||||
try {
|
||||
if (track.playState == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
track.stop()
|
||||
}
|
||||
track.release()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val attributes = AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
@ -83,7 +100,7 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
AudioTrack.Builder()
|
||||
.setAudioAttributes(attributes)
|
||||
.setAudioFormat(format)
|
||||
.setBufferSizeInBytes(bufferSize * 2)
|
||||
.setBufferSizeInBytes(bufferSize * 4) // Увеличил размер буфера
|
||||
.build()
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
@ -92,7 +109,7 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
SAMPLE_RATE,
|
||||
AudioFormat.CHANNEL_OUT_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
bufferSize * 2,
|
||||
bufferSize * 4, // Увеличил размер буфера
|
||||
AudioTrack.MODE_STREAM
|
||||
)
|
||||
}
|
||||
@ -118,6 +135,11 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
envelopeSamples = 0
|
||||
envelopeValue = 0.0
|
||||
currentFrequency = frequency
|
||||
lastBufferWritten = false
|
||||
silenceBuffersWritten = 0
|
||||
|
||||
// Сбрасываем фазу при начале новой ноты для предотвращения щелчков
|
||||
phase = 0.0
|
||||
|
||||
if (!isPlaying) {
|
||||
startAudioGeneration()
|
||||
@ -150,6 +172,8 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
|
||||
shouldStop = false
|
||||
isPlaying = true
|
||||
lastBufferWritten = false
|
||||
silenceBuffersWritten = 0
|
||||
|
||||
val track = audioTrack
|
||||
if (track == null || track.state != AudioTrack.STATE_INITIALIZED) {
|
||||
@ -176,6 +200,17 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
if (track?.state == AudioTrack.STATE_INITIALIZED && track.playState == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
try {
|
||||
track.write(buffer, 0, buffer.size)
|
||||
|
||||
// Если это последний буфер с данными, запоминаем это
|
||||
if (allSamplesZero && envelopeState == 0) {
|
||||
silenceBuffersWritten++
|
||||
if (silenceBuffersWritten >= requiredSilenceBuffers) {
|
||||
lastBufferWritten = true
|
||||
}
|
||||
} else {
|
||||
silenceBuffersWritten = 0
|
||||
lastBufferWritten = false
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (!shouldStop) {
|
||||
e.printStackTrace()
|
||||
@ -186,10 +221,15 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Останавливаем генерацию только если огибающая завершила релиз
|
||||
if (allSamplesZero && envelopeState == 0) {
|
||||
// Останавливаем генерацию только после того как:
|
||||
// 1. Огибающая завершила релиз
|
||||
// 2. Мы записали несколько буферов тишины для гарантии
|
||||
// 3. Нет активной ноты
|
||||
if (lastBufferWritten) {
|
||||
synchronized(lock) {
|
||||
if (!isNoteOn) {
|
||||
if (!isNoteOn && envelopeState == 0) {
|
||||
// Даем дополнительное время на вывод последних буферов
|
||||
Thread.sleep(50)
|
||||
isPlaying = false
|
||||
}
|
||||
}
|
||||
@ -233,10 +273,10 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
envelopeSamples++
|
||||
if (envelopeSamples >= attackSamples) {
|
||||
envelopeValue = 1.0
|
||||
// Остаемся в атаке - переход к релизу только при stopNote()
|
||||
} else {
|
||||
// Плавная атака
|
||||
envelopeValue = envelopeSamples.toDouble() / attackSamples
|
||||
// Квадратичная атака для более плавного начала
|
||||
val progress = envelopeSamples.toDouble() / attackSamples
|
||||
envelopeValue = progress * progress
|
||||
}
|
||||
}
|
||||
2 -> { // Релиз
|
||||
@ -245,11 +285,12 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
envelopeValue = 0.0
|
||||
envelopeState = 0 // idle
|
||||
} else {
|
||||
// Плавный релиз
|
||||
envelopeValue = 1.0 - (envelopeSamples.toDouble() / releaseSamples)
|
||||
// Квадратичный релиз для более плавного затухания
|
||||
val progress = envelopeSamples.toDouble() / releaseSamples
|
||||
envelopeValue = (1.0 - progress) * (1.0 - progress)
|
||||
|
||||
// Гарантируем плавное затухание в конце
|
||||
if (envelopeValue < 0.01) {
|
||||
if (envelopeValue < 0.001) {
|
||||
envelopeValue = 0.0
|
||||
}
|
||||
}
|
||||
@ -277,8 +318,16 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
// Смешиваем с весами, характерными для тубы
|
||||
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 {
|
||||
@ -297,9 +346,9 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
shouldStop = true
|
||||
isPlaying = false
|
||||
|
||||
// Даем время на завершение релиза
|
||||
// Даем время на завершение релиза и вывод всех буферов
|
||||
try {
|
||||
Thread.sleep(200)
|
||||
Thread.sleep(300)
|
||||
} catch (e: InterruptedException) {
|
||||
// Игнорируем
|
||||
}
|
||||
@ -307,7 +356,7 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
// Безопасная остановка потока
|
||||
currentThread?.let { thread ->
|
||||
try {
|
||||
thread.join(100)
|
||||
thread.join(200)
|
||||
} catch (e: InterruptedException) {
|
||||
thread.interrupt()
|
||||
}
|
||||
@ -317,6 +366,10 @@ class AdvancedTubaSynth(private val context: Context) {
|
||||
// Безопасное освобождение AudioTrack
|
||||
audioTrack?.let { track ->
|
||||
try {
|
||||
// Плавно уменьшаем громкость перед остановкой
|
||||
track.setVolume(0.1f)
|
||||
Thread.sleep(50)
|
||||
|
||||
if (track.playState == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
track.stop()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user