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