better timbre of tuba
This commit is contained in:
		
							parent
							
								
									21be5ed986
								
							
						
					
					
						commit
						ab0c5147e6
					
				@ -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 {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user