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