Compare commits

..

No commits in common. "8b6826170194b471c43c9103d0bae32eda93f5e6" and "21be5ed9868a918ca399d8b8b61a38bf03477c1f" have entirely different histories.

View File

@ -47,24 +47,21 @@ class AdvancedTubaSynth(private val context: Context) {
private var currentFrequency = 0f private var currentFrequency = 0f
private var targetFrequency = 0f private var targetFrequency = 0f
private var isNoteOn = false private var isNoteOn = false
private var currentAmplitude = 0.0
private var phase = 0.0 private var phase = 0.0
// Простая огибающая // Простая и надежная огибающая
private var envelopeValue = 0.0 private var envelopeValue = 0.0
private var envelopeSamples = 0 private var envelopeState = 0 // 0: idle, 1: attack, 2: sustain, 3: release
private var envelopeState = 0 // 0: idle, 1: attack, 2: release private var envelopeCounter = 0
// Параметры огибающей // Параметры огибающей
private val attackSamples = (0.02 * SAMPLE_RATE).toInt() // Увеличил до 20ms для более плавного старта private val attackSamples = (0.01 * SAMPLE_RATE).toInt() // 10ms атака
private val releaseSamples = (0.2 * SAMPLE_RATE).toInt() // 200ms релиз private val releaseSamples = (0.1 * SAMPLE_RATE).toInt() // 100ms релиз
private val sustainLevel = 0.8
// Минимальный буфер для low-latency // Минимальный буфер для low-latency
private val bufferSize = 1024 // Увеличил буфер для стабильности private val bufferSize = 512
// Для управления выводом
private var lastBufferWritten = false
private var silenceBuffersWritten = 0
private val requiredSilenceBuffers = 3 // Дополнительные буферы тишины после затухания
init { init {
initializeAudioTrack() initializeAudioTrack()
@ -73,18 +70,6 @@ class AdvancedTubaSynth(private val context: Context) {
private fun initializeAudioTrack() { private fun initializeAudioTrack() {
synchronized(lock) { synchronized(lock) {
try { 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) { audioTrack = 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)
@ -100,7 +85,7 @@ class AdvancedTubaSynth(private val context: Context) {
AudioTrack.Builder() AudioTrack.Builder()
.setAudioAttributes(attributes) .setAudioAttributes(attributes)
.setAudioFormat(format) .setAudioFormat(format)
.setBufferSizeInBytes(bufferSize * 4) // Увеличил размер буфера .setBufferSizeInBytes(bufferSize * 2)
.build() .build()
} else { } else {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -109,7 +94,7 @@ 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 * 2,
AudioTrack.MODE_STREAM AudioTrack.MODE_STREAM
) )
} }
@ -132,14 +117,9 @@ class AdvancedTubaSynth(private val context: Context) {
// Новая нота - начинаем с атаки // Новая нота - начинаем с атаки
isNoteOn = true isNoteOn = true
envelopeState = 1 // attack envelopeState = 1 // attack
envelopeSamples = 0 envelopeCounter = 0
envelopeValue = 0.0 envelopeValue = 0.0
currentFrequency = frequency currentFrequency = frequency
lastBufferWritten = false
silenceBuffersWritten = 0
// Сбрасываем фазу при начале новой ноты для предотвращения щелчков
phase = 0.0
if (!isPlaying) { if (!isPlaying) {
startAudioGeneration() startAudioGeneration()
@ -155,8 +135,10 @@ class AdvancedTubaSynth(private val context: Context) {
synchronized(lock) { synchronized(lock) {
if (isNoteOn) { if (isNoteOn) {
isNoteOn = false isNoteOn = false
envelopeState = 2 // release if (envelopeState != 0) {
envelopeSamples = 0 envelopeState = 3 // release
envelopeCounter = 0
}
} }
} }
} }
@ -172,8 +154,6 @@ class AdvancedTubaSynth(private val context: Context) {
shouldStop = false shouldStop = false
isPlaying = true isPlaying = true
lastBufferWritten = false
silenceBuffersWritten = 0
val track = audioTrack val track = audioTrack
if (track == null || track.state != AudioTrack.STATE_INITIALIZED) { if (track == null || track.state != AudioTrack.STATE_INITIALIZED) {
@ -182,9 +162,10 @@ class AdvancedTubaSynth(private val context: Context) {
currentThread = Thread { currentThread = Thread {
val buffer = ShortArray(bufferSize) val buffer = ShortArray(bufferSize)
var continueGeneration = true
try { try {
while (!shouldStop && isPlaying) { while (!shouldStop && isPlaying && continueGeneration) {
// Заполняем буфер // Заполняем буфер
var allSamplesZero = true var allSamplesZero = true
for (i in buffer.indices) { for (i in buffer.indices) {
@ -199,17 +180,9 @@ class AdvancedTubaSynth(private val context: Context) {
val track = audioTrack val track = audioTrack
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) val written = track.write(buffer, 0, buffer.size)
if (written < buffer.size) {
// Если это последний буфер с данными, запоминаем это Thread.sleep(1)
if (allSamplesZero && envelopeState == 0) {
silenceBuffersWritten++
if (silenceBuffersWritten >= requiredSilenceBuffers) {
lastBufferWritten = true
}
} else {
silenceBuffersWritten = 0
lastBufferWritten = false
} }
} catch (e: Exception) { } catch (e: Exception) {
if (!shouldStop) { if (!shouldStop) {
@ -217,20 +190,23 @@ class AdvancedTubaSynth(private val context: Context) {
android.os.Handler(android.os.Looper.getMainLooper()).post { android.os.Handler(android.os.Looper.getMainLooper()).post {
initializeAudioTrack() initializeAudioTrack()
} }
continueGeneration = false
} }
} }
} else {
android.os.Handler(android.os.Looper.getMainLooper()).post {
initializeAudioTrack()
}
continueGeneration = false
} }
// Останавливаем генерацию только после того как: // Останавливаем генерацию только если огибающая в состоянии idle
// 1. Огибающая завершила релиз // и прошло достаточно времени для гарантии полного затухания
// 2. Мы записали несколько буферов тишины для гарантии if (allSamplesZero && envelopeState == 0) {
// 3. Нет активной ноты
if (lastBufferWritten) {
synchronized(lock) { synchronized(lock) {
if (!isNoteOn && envelopeState == 0) { if (!isNoteOn) {
// Даем дополнительное время на вывод последних буферов
Thread.sleep(50)
isPlaying = false isPlaying = false
continueGeneration = false
} }
} }
} }
@ -247,6 +223,7 @@ class AdvancedTubaSynth(private val context: Context) {
private fun generateSample(): Double { private fun generateSample(): Double {
synchronized(lock) { synchronized(lock) {
// Плавное изменение частоты для легато // Плавное изменение частоты для легато
if (abs(currentFrequency - targetFrequency) > 0.1) { if (abs(currentFrequency - targetFrequency) > 0.1) {
currentFrequency += (targetFrequency - currentFrequency) * 0.2f currentFrequency += (targetFrequency - currentFrequency) * 0.2f
@ -260,46 +237,59 @@ class AdvancedTubaSynth(private val context: Context) {
return 0.0 return 0.0
} }
// Генерация waveform с чистым тембром тубы // Генерация waveform с правильным тембром тубы
val wave = calculateTubaWaveform(currentFrequency) val wave = calculateTubaWaveform(currentFrequency)
return wave * envelopeValue // В начале generateSample(), перед возвратом результата:
val result = wave * envelopeValue
// Очень мягкое ограничение на самых низких уровнях громкости
return if (abs(result) < 0.001) {
result * (1.0 - exp(-abs(result) * 100.0))
} else {
result
}
// return wave * envelopeValue
} }
} }
private fun updateEnvelope() { private fun updateEnvelope() {
when (envelopeState) { when (envelopeState) {
1 -> { // Атака 1 -> { // Атака
envelopeSamples++ envelopeCounter++
if (envelopeSamples >= attackSamples) { if (envelopeCounter >= attackSamples) {
envelopeValue = 1.0 envelopeValue = sustainLevel
envelopeState = 2 // сустейн
} else { } else {
// Квадратичная атака для более плавного начала // Линейная атака
val progress = envelopeSamples.toDouble() / attackSamples envelopeValue = (envelopeCounter.toDouble() / attackSamples) * sustainLevel
envelopeValue = progress * progress
} }
} }
2 -> { // Релиз 2 -> { // Суснейн
envelopeSamples++ envelopeValue = sustainLevel
if (envelopeSamples >= releaseSamples) { // Если нота отпущена, переходим к релизу
if (!isNoteOn) {
envelopeState = 3
envelopeCounter = 0
}
}
3 -> { // Релиз
envelopeCounter++
if (envelopeCounter >= releaseSamples) {
envelopeValue = 0.0 envelopeValue = 0.0
envelopeState = 0 // idle envelopeState = 0 // idle
} else { } else {
// Квадратичный релиз для более плавного затухания // Линейный релиз
val progress = envelopeSamples.toDouble() / releaseSamples envelopeValue = sustainLevel * (1.0 - envelopeCounter.toDouble() / releaseSamples)
envelopeValue = (1.0 - progress) * (1.0 - progress)
// Гарантируем плавное затухание в конце
if (envelopeValue < 0.001) {
envelopeValue = 0.0
}
} }
} }
} }
currentAmplitude = envelopeValue
} }
private fun calculateTubaWaveform(freq: Float): Double { private fun calculateTubaWaveform(freq: Float): Double {
// Используем фазовый накопитель // Используем фазовый накопитель для избежания щелчков
val phaseIncrement = 2.0 * PI * freq / SAMPLE_RATE val phaseIncrement = 2.0 * PI * freq / SAMPLE_RATE
phase += phaseIncrement phase += phaseIncrement
@ -308,26 +298,22 @@ class AdvancedTubaSynth(private val context: Context) {
phase -= 2.0 * PI phase -= 2.0 * PI
} }
// Характерные обертоны для тубы // Базовый тон и обертоны, характерные для тубы
val fundamental = sin(phase) val fundamental = sin(phase)
val overtone2 = sin(2.0 * phase) * 0.5 // октава
val overtone3 = sin(3.0 * phase) * 0.3 // квинта
val overtone4 = sin(4.0 * phase) * 0.2 // октава
val overtone5 = sin(5.0 * phase) * 0.15 // большая терция
// Смешиваем с весами, характерными для тубы // Оптимизированные обертоны для тембра тубы
val mixed = fundamental * 0.7 + overtone2 * 0.5 + overtone3 * 0.3 + overtone4 * 0.2 + overtone5 * 0.15 val overtones =
sin(2.0 * phase) * 0.5 + // октава
sin(3.0 * phase) * 0.3 + // квинта
sin(4.0 * phase) * 0.2 + // октава
sin(5.0 * phase) * 0.15 + // большая терция
sin(6.0 * phase) * 0.1 // квинта
val result = tanh(mixed) // Смешиваем основной тон и обертоны
val mixed = fundamental * 0.7 + overtones * 0.6
// Очень мягкое ограничение на резкие переходы // Мягкое ограничение для теплого звука
return if (abs(result) > 0.8) { return tanh(mixed * 1.5) / 1.5
result * 0.99 // Слегка уменьшаем пики
} else {
result
}
// Очень мягкое ограничение для теплого звука
// return tanh(mixed)
} }
fun isActive(): Boolean { fun isActive(): Boolean {
@ -346,17 +332,10 @@ class AdvancedTubaSynth(private val context: Context) {
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(100)
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
thread.interrupt() thread.interrupt()
} }
@ -366,10 +345,6 @@ class AdvancedTubaSynth(private val context: Context) {
// Безопасное освобождение AudioTrack // Безопасное освобождение AudioTrack
audioTrack?.let { track -> audioTrack?.let { track ->
try { try {
// Плавно уменьшаем громкость перед остановкой
track.setVolume(0.1f)
Thread.sleep(50)
if (track.playState == AudioTrack.PLAYSTATE_PLAYING) { if (track.playState == AudioTrack.PLAYSTATE_PLAYING) {
track.stop() track.stop()
} }