diff --git a/app/src/main/java/net/sergeych/karabass/AdvancedTubaSynth.kt b/app/src/main/java/net/sergeych/karabass/AdvancedTubaSynth.kt index e691ffc..e292f4e 100644 --- a/app/src/main/java/net/sergeych/karabass/AdvancedTubaSynth.kt +++ b/app/src/main/java/net/sergeych/karabass/AdvancedTubaSynth.kt @@ -47,18 +47,16 @@ class AdvancedTubaSynth(private val context: Context) { private var currentFrequency = 0f private var targetFrequency = 0f private var isNoteOn = false - private var currentAmplitude = 0.0 private var phase = 0.0 - // Простая и надежная огибающая + // Простая огибающая private var envelopeValue = 0.0 - private var envelopeState = 0 // 0: idle, 1: attack, 2: sustain, 3: release - private var envelopeCounter = 0 + private var envelopeSamples = 0 + private var envelopeState = 0 // 0: idle, 1: attack, 2: release // Параметры огибающей private val attackSamples = (0.01 * SAMPLE_RATE).toInt() // 10ms атака - private val releaseSamples = (0.1 * SAMPLE_RATE).toInt() // 100ms релиз - private val sustainLevel = 0.8 + private val releaseSamples = (0.15 * SAMPLE_RATE).toInt() // 150ms релиз // Минимальный буфер для low-latency private val bufferSize = 512 @@ -117,7 +115,7 @@ class AdvancedTubaSynth(private val context: Context) { // Новая нота - начинаем с атаки isNoteOn = true envelopeState = 1 // attack - envelopeCounter = 0 + envelopeSamples = 0 envelopeValue = 0.0 currentFrequency = frequency @@ -135,10 +133,8 @@ class AdvancedTubaSynth(private val context: Context) { synchronized(lock) { if (isNoteOn) { isNoteOn = false - if (envelopeState != 0) { - envelopeState = 3 // release - envelopeCounter = 0 - } + envelopeState = 2 // release + envelopeSamples = 0 } } } @@ -162,10 +158,9 @@ class AdvancedTubaSynth(private val context: Context) { currentThread = Thread { val buffer = ShortArray(bufferSize) - var continueGeneration = true try { - while (!shouldStop && isPlaying && continueGeneration) { + while (!shouldStop && isPlaying) { // Заполняем буфер var allSamplesZero = true for (i in buffer.indices) { @@ -180,33 +175,22 @@ class AdvancedTubaSynth(private val context: Context) { val track = audioTrack if (track?.state == AudioTrack.STATE_INITIALIZED && track.playState == AudioTrack.PLAYSTATE_PLAYING) { try { - val written = track.write(buffer, 0, buffer.size) - if (written < buffer.size) { - Thread.sleep(1) - } + track.write(buffer, 0, buffer.size) } catch (e: Exception) { if (!shouldStop) { e.printStackTrace() android.os.Handler(android.os.Looper.getMainLooper()).post { initializeAudioTrack() } - continueGeneration = false } } - } else { - android.os.Handler(android.os.Looper.getMainLooper()).post { - initializeAudioTrack() - } - continueGeneration = false } - // Останавливаем генерацию только если огибающая в состоянии idle - // и прошло достаточно времени для гарантии полного затухания + // Останавливаем генерацию только если огибающая завершила релиз if (allSamplesZero && envelopeState == 0) { synchronized(lock) { if (!isNoteOn) { isPlaying = false - continueGeneration = false } } } @@ -223,7 +207,6 @@ class AdvancedTubaSynth(private val context: Context) { private fun generateSample(): Double { synchronized(lock) { - // Плавное изменение частоты для легато if (abs(currentFrequency - targetFrequency) > 0.1) { currentFrequency += (targetFrequency - currentFrequency) * 0.2f @@ -237,59 +220,45 @@ class AdvancedTubaSynth(private val context: Context) { return 0.0 } - // Генерация waveform с правильным тембром тубы + // Генерация waveform с чистым тембром тубы val wave = calculateTubaWaveform(currentFrequency) - // В начале generateSample(), перед возвратом результата: - val result = wave * envelopeValue - -// Очень мягкое ограничение на самых низких уровнях громкости - return if (abs(result) < 0.001) { - result * (1.0 - exp(-abs(result) * 100.0)) - } else { - result - } -// return wave * envelopeValue + return wave * envelopeValue } } private fun updateEnvelope() { when (envelopeState) { 1 -> { // Атака - envelopeCounter++ - if (envelopeCounter >= attackSamples) { - envelopeValue = sustainLevel - envelopeState = 2 // сустейн + envelopeSamples++ + if (envelopeSamples >= attackSamples) { + envelopeValue = 1.0 + // Остаемся в атаке - переход к релизу только при stopNote() } else { - // Линейная атака - envelopeValue = (envelopeCounter.toDouble() / attackSamples) * sustainLevel + // Плавная атака + envelopeValue = envelopeSamples.toDouble() / attackSamples } } - 2 -> { // Суснейн - envelopeValue = sustainLevel - // Если нота отпущена, переходим к релизу - if (!isNoteOn) { - envelopeState = 3 - envelopeCounter = 0 - } - } - 3 -> { // Релиз - envelopeCounter++ - if (envelopeCounter >= releaseSamples) { + 2 -> { // Релиз + envelopeSamples++ + if (envelopeSamples >= releaseSamples) { envelopeValue = 0.0 envelopeState = 0 // idle } else { - // Линейный релиз - envelopeValue = sustainLevel * (1.0 - envelopeCounter.toDouble() / releaseSamples) + // Плавный релиз + envelopeValue = 1.0 - (envelopeSamples.toDouble() / releaseSamples) + + // Гарантируем плавное затухание в конце + if (envelopeValue < 0.01) { + envelopeValue = 0.0 + } } } } - - currentAmplitude = envelopeValue } private fun calculateTubaWaveform(freq: Float): Double { - // Используем фазовый накопитель для избежания щелчков + // Используем фазовый накопитель val phaseIncrement = 2.0 * PI * freq / SAMPLE_RATE phase += phaseIncrement @@ -298,22 +267,18 @@ class AdvancedTubaSynth(private val context: Context) { phase -= 2.0 * PI } - // Базовый тон и обертоны, характерные для тубы + // Характерные обертоны для тубы 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 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 mixed = fundamental * 0.7 + overtone2 * 0.5 + overtone3 * 0.3 + overtone4 * 0.2 + overtone5 * 0.15 - // Смешиваем основной тон и обертоны - val mixed = fundamental * 0.7 + overtones * 0.6 - - // Мягкое ограничение для теплого звука - return tanh(mixed * 1.5) / 1.5 + // Очень мягкое ограничение для теплого звука + return tanh(mixed) } fun isActive(): Boolean { @@ -332,6 +297,13 @@ class AdvancedTubaSynth(private val context: Context) { shouldStop = true isPlaying = false + // Даем время на завершение релиза + try { + Thread.sleep(200) + } catch (e: InterruptedException) { + // Игнорируем + } + // Безопасная остановка потока currentThread?.let { thread -> try {