Compare commits
2 Commits
21be5ed986
...
8b68261701
Author | SHA1 | Date | |
---|---|---|---|
8b68261701 | |||
ab0c5147e6 |
@ -47,21 +47,24 @@ 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 envelopeState = 0 // 0: idle, 1: attack, 2: sustain, 3: release
|
private var envelopeSamples = 0
|
||||||
private var envelopeCounter = 0
|
private var envelopeState = 0 // 0: idle, 1: attack, 2: release
|
||||||
|
|
||||||
// Параметры огибающей
|
// Параметры огибающей
|
||||||
private val attackSamples = (0.01 * SAMPLE_RATE).toInt() // 10ms атака
|
private val attackSamples = (0.02 * SAMPLE_RATE).toInt() // Увеличил до 20ms для более плавного старта
|
||||||
private val releaseSamples = (0.1 * SAMPLE_RATE).toInt() // 100ms релиз
|
private val releaseSamples = (0.2 * SAMPLE_RATE).toInt() // 200ms релиз
|
||||||
private val sustainLevel = 0.8
|
|
||||||
|
|
||||||
// Минимальный буфер для low-latency
|
// Минимальный буфер для low-latency
|
||||||
private val bufferSize = 512
|
private val bufferSize = 1024 // Увеличил буфер для стабильности
|
||||||
|
|
||||||
|
// Для управления выводом
|
||||||
|
private var lastBufferWritten = false
|
||||||
|
private var silenceBuffersWritten = 0
|
||||||
|
private val requiredSilenceBuffers = 3 // Дополнительные буферы тишины после затухания
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initializeAudioTrack()
|
initializeAudioTrack()
|
||||||
@ -70,6 +73,18 @@ 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)
|
||||||
@ -85,7 +100,7 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
AudioTrack.Builder()
|
AudioTrack.Builder()
|
||||||
.setAudioAttributes(attributes)
|
.setAudioAttributes(attributes)
|
||||||
.setAudioFormat(format)
|
.setAudioFormat(format)
|
||||||
.setBufferSizeInBytes(bufferSize * 2)
|
.setBufferSizeInBytes(bufferSize * 4) // Увеличил размер буфера
|
||||||
.build()
|
.build()
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@ -94,7 +109,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 * 2,
|
bufferSize * 4, // Увеличил размер буфера
|
||||||
AudioTrack.MODE_STREAM
|
AudioTrack.MODE_STREAM
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -117,9 +132,14 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
// Новая нота - начинаем с атаки
|
// Новая нота - начинаем с атаки
|
||||||
isNoteOn = true
|
isNoteOn = true
|
||||||
envelopeState = 1 // attack
|
envelopeState = 1 // attack
|
||||||
envelopeCounter = 0
|
envelopeSamples = 0
|
||||||
envelopeValue = 0.0
|
envelopeValue = 0.0
|
||||||
currentFrequency = frequency
|
currentFrequency = frequency
|
||||||
|
lastBufferWritten = false
|
||||||
|
silenceBuffersWritten = 0
|
||||||
|
|
||||||
|
// Сбрасываем фазу при начале новой ноты для предотвращения щелчков
|
||||||
|
phase = 0.0
|
||||||
|
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
startAudioGeneration()
|
startAudioGeneration()
|
||||||
@ -135,10 +155,8 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
if (isNoteOn) {
|
if (isNoteOn) {
|
||||||
isNoteOn = false
|
isNoteOn = false
|
||||||
if (envelopeState != 0) {
|
envelopeState = 2 // release
|
||||||
envelopeState = 3 // release
|
envelopeSamples = 0
|
||||||
envelopeCounter = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,6 +172,8 @@ 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) {
|
||||||
@ -162,10 +182,9 @@ 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 && continueGeneration) {
|
while (!shouldStop && isPlaying) {
|
||||||
// Заполняем буфер
|
// Заполняем буфер
|
||||||
var allSamplesZero = true
|
var allSamplesZero = true
|
||||||
for (i in buffer.indices) {
|
for (i in buffer.indices) {
|
||||||
@ -180,9 +199,17 @@ 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 {
|
||||||
val written = track.write(buffer, 0, buffer.size)
|
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) {
|
||||||
@ -190,23 +217,20 @@ 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. Огибающая завершила релиз
|
||||||
if (allSamplesZero && envelopeState == 0) {
|
// 2. Мы записали несколько буферов тишины для гарантии
|
||||||
|
// 3. Нет активной ноты
|
||||||
|
if (lastBufferWritten) {
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
if (!isNoteOn) {
|
if (!isNoteOn && envelopeState == 0) {
|
||||||
|
// Даем дополнительное время на вывод последних буферов
|
||||||
|
Thread.sleep(50)
|
||||||
isPlaying = false
|
isPlaying = false
|
||||||
continueGeneration = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,7 +247,6 @@ 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
|
||||||
@ -237,59 +260,46 @@ class AdvancedTubaSynth(private val context: Context) {
|
|||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Генерация waveform с правильным тембром тубы
|
// Генерация waveform с чистым тембром тубы
|
||||||
val wave = calculateTubaWaveform(currentFrequency)
|
val wave = calculateTubaWaveform(currentFrequency)
|
||||||
|
|
||||||
// В начале generateSample(), перед возвратом результата:
|
return wave * envelopeValue
|
||||||
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 -> { // Атака
|
||||||
envelopeCounter++
|
envelopeSamples++
|
||||||
if (envelopeCounter >= attackSamples) {
|
if (envelopeSamples >= attackSamples) {
|
||||||
envelopeValue = sustainLevel
|
envelopeValue = 1.0
|
||||||
envelopeState = 2 // сустейн
|
|
||||||
} else {
|
} else {
|
||||||
// Линейная атака
|
// Квадратичная атака для более плавного начала
|
||||||
envelopeValue = (envelopeCounter.toDouble() / attackSamples) * sustainLevel
|
val progress = envelopeSamples.toDouble() / attackSamples
|
||||||
|
envelopeValue = progress * progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 -> { // Суснейн
|
2 -> { // Релиз
|
||||||
envelopeValue = sustainLevel
|
envelopeSamples++
|
||||||
// Если нота отпущена, переходим к релизу
|
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 {
|
||||||
// Линейный релиз
|
// Квадратичный релиз для более плавного затухания
|
||||||
envelopeValue = sustainLevel * (1.0 - envelopeCounter.toDouble() / releaseSamples)
|
val progress = envelopeSamples.toDouble() / releaseSamples
|
||||||
}
|
envelopeValue = (1.0 - progress) * (1.0 - progress)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentAmplitude = envelopeValue
|
// Гарантируем плавное затухание в конце
|
||||||
|
if (envelopeValue < 0.001) {
|
||||||
|
envelopeValue = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -298,22 +308,26 @@ 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 overtones =
|
val mixed = fundamental * 0.7 + overtone2 * 0.5 + overtone3 * 0.3 + overtone4 * 0.2 + overtone5 * 0.15
|
||||||
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 tanh(mixed * 1.5) / 1.5
|
return if (abs(result) > 0.8) {
|
||||||
|
result * 0.99 // Слегка уменьшаем пики
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
// Очень мягкое ограничение для теплого звука
|
||||||
|
// return tanh(mixed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isActive(): Boolean {
|
fun isActive(): Boolean {
|
||||||
@ -332,10 +346,17 @@ 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(100)
|
thread.join(200)
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
thread.interrupt()
|
thread.interrupt()
|
||||||
}
|
}
|
||||||
@ -345,6 +366,10 @@ 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()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user