fixed bug with audio track creation failure
This commit is contained in:
		
							parent
							
								
									e068ad52de
								
							
						
					
					
						commit
						fc12c9d932
					
				@ -1,5 +1,4 @@
 | 
			
		||||
package net.sergeych.karabass
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.media.AudioAttributes
 | 
			
		||||
import android.media.AudioFormat
 | 
			
		||||
@ -7,6 +6,7 @@ import android.media.AudioManager
 | 
			
		||||
import android.media.AudioTrack
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import kotlin.math.*
 | 
			
		||||
import kotlin.random.Random
 | 
			
		||||
 | 
			
		||||
class AdvancedTubaSynth(private val context: Context) {
 | 
			
		||||
 | 
			
		||||
@ -63,6 +63,57 @@ class AdvancedTubaSynth(private val context: Context) {
 | 
			
		||||
    private val releaseTime = (0.05 * SAMPLE_RATE).toInt()  // 50ms релиз
 | 
			
		||||
    private val sustainLevel = 0.8
 | 
			
		||||
 | 
			
		||||
    // Минимальный буфер для low-latency
 | 
			
		||||
    private val bufferSize = 512 // ~11ms latency
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        // Создаем AudioTrack один раз при инициализации
 | 
			
		||||
        initializeAudioTrack()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun initializeAudioTrack() {
 | 
			
		||||
        synchronized(lock) {
 | 
			
		||||
            try {
 | 
			
		||||
                audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
			
		||||
                    val attributes = AudioAttributes.Builder()
 | 
			
		||||
                        .setUsage(AudioAttributes.USAGE_MEDIA)
 | 
			
		||||
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
 | 
			
		||||
                        .build()
 | 
			
		||||
 | 
			
		||||
                    val format = AudioFormat.Builder()
 | 
			
		||||
                        .setSampleRate(SAMPLE_RATE)
 | 
			
		||||
                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
 | 
			
		||||
                        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
 | 
			
		||||
                        .build()
 | 
			
		||||
 | 
			
		||||
                    AudioTrack.Builder()
 | 
			
		||||
                        .setAudioAttributes(attributes)
 | 
			
		||||
                        .setAudioFormat(format)
 | 
			
		||||
                        .setBufferSizeInBytes(bufferSize * 2)
 | 
			
		||||
                        .build()
 | 
			
		||||
                } else {
 | 
			
		||||
                    @Suppress("DEPRECATION")
 | 
			
		||||
                    AudioTrack(
 | 
			
		||||
                        AudioManager.STREAM_MUSIC,
 | 
			
		||||
                        SAMPLE_RATE,
 | 
			
		||||
                        AudioFormat.CHANNEL_OUT_MONO,
 | 
			
		||||
                        AudioFormat.ENCODING_PCM_16BIT,
 | 
			
		||||
                        bufferSize * 2,
 | 
			
		||||
                        AudioTrack.MODE_STREAM
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                audioTrack?.play()
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                e.printStackTrace()
 | 
			
		||||
                // Пытаемся восстановить AudioTrack через некоторое время
 | 
			
		||||
                android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
 | 
			
		||||
                    initializeAudioTrack()
 | 
			
		||||
                }, 100)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun startNote(frequency: Float) {
 | 
			
		||||
        synchronized(lock) {
 | 
			
		||||
            targetFrequency = frequency
 | 
			
		||||
@ -105,47 +156,15 @@ class AdvancedTubaSynth(private val context: Context) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun startAudioGeneration() {
 | 
			
		||||
        if (isPlaying) return
 | 
			
		||||
 | 
			
		||||
        shouldStop = false
 | 
			
		||||
        isPlaying = true
 | 
			
		||||
 | 
			
		||||
        // Минимальный буфер для low-latency
 | 
			
		||||
        val bufferSize = 512 // ~11ms latency
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
			
		||||
                val attributes = AudioAttributes.Builder()
 | 
			
		||||
                    .setUsage(AudioAttributes.USAGE_MEDIA)
 | 
			
		||||
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
 | 
			
		||||
                    .build()
 | 
			
		||||
 | 
			
		||||
                val format = AudioFormat.Builder()
 | 
			
		||||
                    .setSampleRate(SAMPLE_RATE)
 | 
			
		||||
                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
 | 
			
		||||
                    .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
 | 
			
		||||
                    .build()
 | 
			
		||||
 | 
			
		||||
                AudioTrack.Builder()
 | 
			
		||||
                    .setAudioAttributes(attributes)
 | 
			
		||||
                    .setAudioFormat(format)
 | 
			
		||||
                    .setBufferSizeInBytes(bufferSize * 2)
 | 
			
		||||
                    .build()
 | 
			
		||||
            } else {
 | 
			
		||||
                @Suppress("DEPRECATION")
 | 
			
		||||
                AudioTrack(
 | 
			
		||||
                    AudioManager.STREAM_MUSIC,
 | 
			
		||||
                    SAMPLE_RATE,
 | 
			
		||||
                    AudioFormat.CHANNEL_OUT_MONO,
 | 
			
		||||
                    AudioFormat.ENCODING_PCM_16BIT,
 | 
			
		||||
                    bufferSize * 2,
 | 
			
		||||
                    AudioTrack.MODE_STREAM
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            audioTrack?.play()
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            e.printStackTrace()
 | 
			
		||||
            isPlaying = false
 | 
			
		||||
            return
 | 
			
		||||
        // Проверяем, что AudioTrack существует и готов
 | 
			
		||||
        val track = audioTrack
 | 
			
		||||
        if (track == null || track.state != AudioTrack.STATE_INITIALIZED) {
 | 
			
		||||
            initializeAudioTrack()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        currentThread = Thread {
 | 
			
		||||
@ -161,17 +180,29 @@ class AdvancedTubaSynth(private val context: Context) {
 | 
			
		||||
 | 
			
		||||
                    // Безопасная запись в AudioTrack
 | 
			
		||||
                    val track = audioTrack
 | 
			
		||||
                    if (track?.playState == AudioTrack.PLAYSTATE_PLAYING) {
 | 
			
		||||
                    if (track?.state == AudioTrack.STATE_INITIALIZED && track.playState == AudioTrack.PLAYSTATE_PLAYING) {
 | 
			
		||||
                        try {
 | 
			
		||||
                            track.write(buffer, 0, buffer.size)
 | 
			
		||||
                            val written = track.write(buffer, 0, buffer.size)
 | 
			
		||||
                            if (written < buffer.size) {
 | 
			
		||||
                                // Проблема с записью - возможно, AudioTrack в плохом состоянии
 | 
			
		||||
                                Thread.sleep(1) // Небольшая пауза
 | 
			
		||||
                            }
 | 
			
		||||
                        } catch (e: Exception) {
 | 
			
		||||
                            // Игнорируем ошибки записи при остановке
 | 
			
		||||
                            if (!shouldStop) {
 | 
			
		||||
                                e.printStackTrace()
 | 
			
		||||
                                // Пытаемся восстановить AudioTrack
 | 
			
		||||
                                android.os.Handler(android.os.Looper.getMainLooper()).post {
 | 
			
		||||
                                    initializeAudioTrack()
 | 
			
		||||
                                }
 | 
			
		||||
                                break
 | 
			
		||||
                            }
 | 
			
		||||
                            break
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // AudioTrack не готов - пытаемся восстановить
 | 
			
		||||
                        android.os.Handler(android.os.Looper.getMainLooper()).post {
 | 
			
		||||
                            initializeAudioTrack()
 | 
			
		||||
                        }
 | 
			
		||||
                        break
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
@ -185,12 +216,6 @@ class AdvancedTubaSynth(private val context: Context) {
 | 
			
		||||
            } catch (e: Exception) {
 | 
			
		||||
                e.printStackTrace()
 | 
			
		||||
            } finally {
 | 
			
		||||
                // Безопасная остановка
 | 
			
		||||
                try {
 | 
			
		||||
                    audioTrack?.stop()
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    e.printStackTrace()
 | 
			
		||||
                }
 | 
			
		||||
                isPlaying = false
 | 
			
		||||
            }
 | 
			
		||||
        }.apply {
 | 
			
		||||
 | 
			
		||||
@ -1,260 +0,0 @@
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.media.AudioAttributes
 | 
			
		||||
import android.media.AudioFormat
 | 
			
		||||
import android.media.AudioManager
 | 
			
		||||
import android.media.AudioTrack
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import kotlin.math.*
 | 
			
		||||
import kotlin.random.Random
 | 
			
		||||
 | 
			
		||||
class RealisticTubaSynth(private val context: Context) {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val SAMPLE_RATE = 44100
 | 
			
		||||
        const val CUTOFF_FREQ = 800.0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var audioTrack: AudioTrack? = null
 | 
			
		||||
    private var isPlaying = false
 | 
			
		||||
    private var currentThread: Thread? = null
 | 
			
		||||
 | 
			
		||||
    data class TubaNote(
 | 
			
		||||
        val frequency: Float,
 | 
			
		||||
        val amplitude: Float = 0.8f,
 | 
			
		||||
        val durationMs: Int
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    fun playTubaNote(frequency: Float, durationMs: Int = 1500) {
 | 
			
		||||
        // Останавливаем предыдущее воспроизведение
 | 
			
		||||
        stop()
 | 
			
		||||
 | 
			
		||||
        val note = TubaNote(frequency, 0.8f, durationMs)
 | 
			
		||||
        generateTubaSound(note)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun generateTubaSound(note: TubaNote) {
 | 
			
		||||
        currentThread = Thread {
 | 
			
		||||
            isPlaying = true
 | 
			
		||||
            val numSamples = note.durationMs * SAMPLE_RATE / 1000
 | 
			
		||||
            val buffer = ShortArray(numSamples)
 | 
			
		||||
 | 
			
		||||
            // Фильтр для шума дыхания
 | 
			
		||||
            val alpha = 1 - exp(-2.0 * PI * CUTOFF_FREQ / SAMPLE_RATE)
 | 
			
		||||
            var filteredNoise = 0.0
 | 
			
		||||
 | 
			
		||||
            val random = Random(System.currentTimeMillis())
 | 
			
		||||
 | 
			
		||||
            for (i in 0 until numSamples) {
 | 
			
		||||
                if (!isPlaying) break
 | 
			
		||||
 | 
			
		||||
                val time = i.toDouble() / SAMPLE_RATE
 | 
			
		||||
 | 
			
		||||
                // Основной waveform тубы с более богатым спектром
 | 
			
		||||
                val tubaWave = calculateTubaWaveform(note.frequency, time)
 | 
			
		||||
 | 
			
		||||
                // Шум дыхания с фильтром низких частот
 | 
			
		||||
                val whiteNoise = random.nextDouble() * 2 - 1
 | 
			
		||||
                filteredNoise += alpha * (whiteNoise - filteredNoise)
 | 
			
		||||
 | 
			
		||||
                // Огибающая с резкой атакой и быстрым спадом
 | 
			
		||||
                val envelope = getRealisticTubaEnvelope(i, numSamples, note.frequency)
 | 
			
		||||
 | 
			
		||||
                // Усиление басов
 | 
			
		||||
                val bassBoost = 1.0 + 0.5 * sin(2 * PI * 60.0 * time)
 | 
			
		||||
 | 
			
		||||
                // Смешиваем всё вместе
 | 
			
		||||
                var sample = tubaWave + filteredNoise * 0.03 * envelope
 | 
			
		||||
                sample *= envelope * bassBoost
 | 
			
		||||
 | 
			
		||||
                // Ограничиваем амплитуду
 | 
			
		||||
                sample = sample.coerceIn(-1.0, 1.0)
 | 
			
		||||
 | 
			
		||||
                buffer[i] = (sample * Short.MAX_VALUE).toInt().toShort()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (isPlaying) {
 | 
			
		||||
                playAudioBuffer(buffer)
 | 
			
		||||
            }
 | 
			
		||||
        }.apply {
 | 
			
		||||
            start()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun calculateTubaWaveform(freq: Float, time: Double): Double {
 | 
			
		||||
        // Классификация нот по диапазонам
 | 
			
		||||
        val range = when {
 | 
			
		||||
            freq < 40 -> "pedal"      // Педальные ноты (очень низкие)
 | 
			
		||||
            freq < 80 -> "low"        // Низкий диапазон
 | 
			
		||||
            freq < 160 -> "middle"    // Средний диапазон
 | 
			
		||||
            freq < 240 -> "high"      // Высокий диапазон
 | 
			
		||||
            else -> "very_high"       // Очень высокий диапазон
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val fundamental = sin(2 * PI * freq * time)
 | 
			
		||||
 | 
			
		||||
        // Разные наборы обертонов для разных диапазонов
 | 
			
		||||
        val overtones = when (range) {
 | 
			
		||||
            "pedal" -> {
 | 
			
		||||
                // Педальные ноты: сильные низкие обертоны, меньше высоких
 | 
			
		||||
                sin(2 * PI * freq * 2 * time) * 0.6 +
 | 
			
		||||
                        sin(2 * PI * freq * 3 * time) * 0.5 +
 | 
			
		||||
                        sin(2 * PI * freq * 4 * time) * 0.4 +
 | 
			
		||||
                        sin(2 * PI * freq * 5 * time) * 0.3
 | 
			
		||||
            }
 | 
			
		||||
            "low" -> {
 | 
			
		||||
                // Низкий диапазон: богатый спектр
 | 
			
		||||
                sin(2 * PI * freq * 2 * time) * 0.5 +
 | 
			
		||||
                        sin(2 * PI * freq * 3 * time) * 0.45 +
 | 
			
		||||
                        sin(2 * PI * freq * 4 * time) * 0.4 +
 | 
			
		||||
                        sin(2 * PI * freq * 5 * time) * 0.35 +
 | 
			
		||||
                        sin(2 * PI * freq * 6 * time) * 0.25
 | 
			
		||||
            }
 | 
			
		||||
            "middle" -> {
 | 
			
		||||
                // Средний диапазон: сбалансированный спектр
 | 
			
		||||
                sin(2 * PI * freq * 2 * time) * 0.4 +
 | 
			
		||||
                        sin(2 * PI * freq * 3 * time) * 0.4 +
 | 
			
		||||
                        sin(2 * PI * freq * 4 * time) * 0.35 +
 | 
			
		||||
                        sin(2 * PI * freq * 5 * time) * 0.3 +
 | 
			
		||||
                        sin(2 * PI * freq * 6 * time) * 0.25 +
 | 
			
		||||
                        sin(2 * PI * freq * 7 * time) * 0.2
 | 
			
		||||
            }
 | 
			
		||||
            "high" -> {
 | 
			
		||||
                // Высокий диапазон: больше высоких обертонов
 | 
			
		||||
                sin(2 * PI * freq * 2 * time) * 0.3 +
 | 
			
		||||
                        sin(2 * PI * freq * 3 * time) * 0.35 +
 | 
			
		||||
                        sin(2 * PI * freq * 4 * time) * 0.3 +
 | 
			
		||||
                        sin(2 * PI * freq * 5 * time) * 0.25 +
 | 
			
		||||
                        sin(2 * PI * freq * 6 * time) * 0.2 +
 | 
			
		||||
                        sin(2 * PI * freq * 7 * time) * 0.15 +
 | 
			
		||||
                        sin(2 * PI * freq * 8 * time) * 0.1
 | 
			
		||||
            }
 | 
			
		||||
            else -> {
 | 
			
		||||
                // Очень высокий диапазон: яркий, с преобладанием высоких обертонов
 | 
			
		||||
                sin(2 * PI * freq * 2 * time) * 0.25 +
 | 
			
		||||
                        sin(2 * PI * freq * 3 * time) * 0.3 +
 | 
			
		||||
                        sin(2 * PI * freq * 4 * time) * 0.25 +
 | 
			
		||||
                        sin(2 * PI * freq * 5 * time) * 0.2 +
 | 
			
		||||
                        sin(2 * PI * freq * 6 * time) * 0.15 +
 | 
			
		||||
                        sin(2 * PI * freq * 7 * time) * 0.1 +
 | 
			
		||||
                        sin(2 * PI * freq * 8 * time) * 0.08 +
 | 
			
		||||
                        sin(2 * PI * freq * 9 * time) * 0.05
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val mixed = fundamental * 0.6 + overtones * 0.8
 | 
			
		||||
        return tanh(mixed * 1.2) / 1.2
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getRealisticTubaEnvelope(sampleIndex: Int, totalSamples: Int, frequency: Float): Double {
 | 
			
		||||
        val position = sampleIndex.toDouble() / totalSamples
 | 
			
		||||
 | 
			
		||||
        // Разная огибающая для разных диапазонов
 | 
			
		||||
        val (attack, decay, sustain, release) = when {
 | 
			
		||||
            frequency < 40 -> arrayOf(0.04, 0.2, 0.6, 0.15)  // Педальные ноты: медленнее
 | 
			
		||||
            frequency < 100 -> arrayOf(0.025, 0.15, 0.65, 0.1) // Низкие: умеренные
 | 
			
		||||
            frequency < 200 -> arrayOf(0.015, 0.1, 0.7, 0.08)  // Средние: быстрее
 | 
			
		||||
            else -> arrayOf(0.01, 0.08, 0.75, 0.06)           // Высокие: очень быстрые
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return when {
 | 
			
		||||
            position < attack -> (position / attack).pow(0.3)
 | 
			
		||||
            position < attack + decay -> {
 | 
			
		||||
                val decayPos = (position - attack) / decay
 | 
			
		||||
                1.0 - (1.0 - sustain) * decayPos
 | 
			
		||||
            }
 | 
			
		||||
            position > 1 - release -> {
 | 
			
		||||
                val releasePos = (position - (1 - release)) / release
 | 
			
		||||
                sustain * (1 - releasePos)
 | 
			
		||||
            }
 | 
			
		||||
            else -> sustain
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private fun playAudioBuffer(buffer: ShortArray) {
 | 
			
		||||
        try {
 | 
			
		||||
            // Создаем новый AudioTrack каждый раз
 | 
			
		||||
            val bufferSize = AudioTrack.getMinBufferSize(
 | 
			
		||||
                SAMPLE_RATE,
 | 
			
		||||
                AudioFormat.CHANNEL_OUT_MONO,
 | 
			
		||||
                AudioFormat.ENCODING_PCM_16BIT
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            val newAudioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
			
		||||
                val attributes = AudioAttributes.Builder()
 | 
			
		||||
                    .setUsage(AudioAttributes.USAGE_MEDIA)
 | 
			
		||||
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
 | 
			
		||||
                    .build()
 | 
			
		||||
 | 
			
		||||
                val format = AudioFormat.Builder()
 | 
			
		||||
                    .setSampleRate(SAMPLE_RATE)
 | 
			
		||||
                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
 | 
			
		||||
                    .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
 | 
			
		||||
                    .build()
 | 
			
		||||
 | 
			
		||||
                AudioTrack.Builder()
 | 
			
		||||
                    .setAudioAttributes(attributes)
 | 
			
		||||
                    .setAudioFormat(format)
 | 
			
		||||
                    .setBufferSizeInBytes(bufferSize)
 | 
			
		||||
                    .build()
 | 
			
		||||
            } else {
 | 
			
		||||
                @Suppress("DEPRECATION")
 | 
			
		||||
                AudioTrack(
 | 
			
		||||
                    AudioManager.STREAM_MUSIC,
 | 
			
		||||
                    SAMPLE_RATE,
 | 
			
		||||
                    AudioFormat.CHANNEL_OUT_MONO,
 | 
			
		||||
                    AudioFormat.ENCODING_PCM_16BIT,
 | 
			
		||||
                    bufferSize,
 | 
			
		||||
                    AudioTrack.MODE_STATIC
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newAudioTrack.apply {
 | 
			
		||||
                setVolume(0.8f)
 | 
			
		||||
 | 
			
		||||
                // Используем MODE_STATIC для однократного воспроизведения
 | 
			
		||||
                write(buffer, 0, buffer.size)
 | 
			
		||||
 | 
			
		||||
                setPlaybackPositionUpdateListener(object : AudioTrack.OnPlaybackPositionUpdateListener {
 | 
			
		||||
                    override fun onMarkerReached(track: AudioTrack) {
 | 
			
		||||
                        // Автоматически освобождаем после воспроизведения
 | 
			
		||||
                        track.stop()
 | 
			
		||||
                        track.release()
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    override fun onPeriodicNotification(track: AudioTrack) {}
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                setNotificationMarkerPosition(buffer.size)
 | 
			
		||||
                play()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            audioTrack = newAudioTrack
 | 
			
		||||
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            e.printStackTrace()
 | 
			
		||||
            isPlaying = false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun stop() {
 | 
			
		||||
        isPlaying = false
 | 
			
		||||
        currentThread?.interrupt()
 | 
			
		||||
        currentThread = null
 | 
			
		||||
 | 
			
		||||
        audioTrack?.let { track ->
 | 
			
		||||
            try {
 | 
			
		||||
                if (track.playState == AudioTrack.PLAYSTATE_PLAYING) {
 | 
			
		||||
                    track.stop()
 | 
			
		||||
                }
 | 
			
		||||
                track.release()
 | 
			
		||||
            } catch (e: IllegalStateException) {
 | 
			
		||||
                // Игнорируем ошибки при остановке уже остановленного трека
 | 
			
		||||
                e.printStackTrace()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        audioTrack = null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun release() {
 | 
			
		||||
        stop()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,199 +0,0 @@
 | 
			
		||||
package net.sergeych.karabass
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.media.AudioAttributes
 | 
			
		||||
import android.media.AudioFormat
 | 
			
		||||
import android.media.AudioManager
 | 
			
		||||
import android.media.AudioTrack
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import kotlin.math.*
 | 
			
		||||
import kotlin.random.Random
 | 
			
		||||
 | 
			
		||||
class TubaSynth(private val context: Context) {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val SAMPLE_RATE = 44100
 | 
			
		||||
        const val CUTOFF_FREQ = 500.0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var audioTrack: AudioTrack? = null
 | 
			
		||||
    private var isPlaying = false
 | 
			
		||||
 | 
			
		||||
    data class TubaNote(
 | 
			
		||||
        val frequency: Float,
 | 
			
		||||
        val amplitude: Float = 0.8f,
 | 
			
		||||
        val durationMs: Int
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    fun playTubaNote(frequency: Float, durationMs: Int = 2000) {
 | 
			
		||||
        if (isPlaying) {
 | 
			
		||||
            stop()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val note = TubaNote(frequency, 0.8f, durationMs)
 | 
			
		||||
        generateTubaSound(note)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun generateTubaSound(note: TubaNote) {
 | 
			
		||||
        Thread {
 | 
			
		||||
            isPlaying = true
 | 
			
		||||
            val numSamples = note.durationMs * SAMPLE_RATE / 1000
 | 
			
		||||
            val buffer = ShortArray(numSamples)
 | 
			
		||||
 | 
			
		||||
            // Фильтр для шума дыхания
 | 
			
		||||
            val alpha = 1 - exp(-2.0 * PI * CUTOFF_FREQ / SAMPLE_RATE)
 | 
			
		||||
            var filteredNoise = 0.0
 | 
			
		||||
 | 
			
		||||
            val random = Random(System.currentTimeMillis())
 | 
			
		||||
 | 
			
		||||
            for (i in 0 until numSamples) {
 | 
			
		||||
                if (!isPlaying) break
 | 
			
		||||
 | 
			
		||||
                val time = i.toDouble() / SAMPLE_RATE
 | 
			
		||||
 | 
			
		||||
                // Основной waveform тубы
 | 
			
		||||
                val tubaWave = calculateTubaWaveform(note.frequency, time)
 | 
			
		||||
 | 
			
		||||
                // Шум дыхания с фильтром низких частот
 | 
			
		||||
                val whiteNoise = random.nextDouble() * 2 - 1
 | 
			
		||||
                filteredNoise += alpha * (whiteNoise - filteredNoise)
 | 
			
		||||
 | 
			
		||||
                // Огибающая
 | 
			
		||||
                val envelope = getTubaEnvelope(i, numSamples)
 | 
			
		||||
 | 
			
		||||
                // Усиление басов
 | 
			
		||||
                val bassBoost = 1.0 + 0.4 * sin(2 * PI * 80.0 * time)
 | 
			
		||||
 | 
			
		||||
                // Смешиваем всё вместе
 | 
			
		||||
                var sample = tubaWave + filteredNoise * 0.05 * envelope
 | 
			
		||||
                sample *= envelope * bassBoost
 | 
			
		||||
 | 
			
		||||
                // Ограничиваем амплитуду
 | 
			
		||||
                sample = sample.coerceIn(-1.0, 1.0)
 | 
			
		||||
 | 
			
		||||
                buffer[i] = (sample * Short.MAX_VALUE).toInt().toShort()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (isPlaying) {
 | 
			
		||||
                playAudioBuffer(buffer)
 | 
			
		||||
            }
 | 
			
		||||
        }.start()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun calculateTubaWaveform(freq: Float, time: Double): Double {
 | 
			
		||||
        return (
 | 
			
		||||
                sin(2 * PI * freq * time) * 0.6 +
 | 
			
		||||
                        sin(2 * PI * freq * 2 * time) * 0.4 +
 | 
			
		||||
                        sin(2 * PI * freq * 3 * time) * 0.3 +
 | 
			
		||||
                        sin(2 * PI * freq * 4 * time) * 0.2 +
 | 
			
		||||
                        sin(2 * PI * freq * 5 * time) * 0.15 +
 | 
			
		||||
                        sin(2 * PI * freq * 6 * time) * 0.1 +
 | 
			
		||||
                        sin(2 * PI * freq * 7 * time) * 0.05
 | 
			
		||||
                ) * 0.6
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getTubaEnvelope(sampleIndex: Int, totalSamples: Int): Double {
 | 
			
		||||
        val position = sampleIndex.toDouble() / totalSamples
 | 
			
		||||
 | 
			
		||||
        val attack = 0.15
 | 
			
		||||
        val decay = 0.1
 | 
			
		||||
        val release = 0.3
 | 
			
		||||
 | 
			
		||||
        return when {
 | 
			
		||||
            position < attack -> {
 | 
			
		||||
                val x = position / attack
 | 
			
		||||
                x * x * (3 - 2 * x)
 | 
			
		||||
            }
 | 
			
		||||
            position < attack + decay -> {
 | 
			
		||||
                val decayPos = (position - attack) / decay
 | 
			
		||||
                0.9 + 0.1 * (1 - decayPos)
 | 
			
		||||
            }
 | 
			
		||||
            position > 1 - release -> {
 | 
			
		||||
                val releasePos = (position - (1 - release)) / release
 | 
			
		||||
                (1 - releasePos) * 0.9
 | 
			
		||||
            }
 | 
			
		||||
            else -> 0.9
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun playAudioBuffer(buffer: ShortArray) {
 | 
			
		||||
        try {
 | 
			
		||||
            // Останавливаем предыдущее воспроизведение
 | 
			
		||||
            audioTrack?.stop()
 | 
			
		||||
            audioTrack?.release()
 | 
			
		||||
 | 
			
		||||
            val bufferSize = AudioTrack.getMinBufferSize(
 | 
			
		||||
                SAMPLE_RATE,
 | 
			
		||||
                AudioFormat.CHANNEL_OUT_MONO,
 | 
			
		||||
                AudioFormat.ENCODING_PCM_16BIT
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            // Создаем AudioTrack с правильными параметрами
 | 
			
		||||
            audioTrack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
			
		||||
                val attributes = AudioAttributes.Builder()
 | 
			
		||||
                    .setUsage(AudioAttributes.USAGE_MEDIA)
 | 
			
		||||
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
 | 
			
		||||
                    .build()
 | 
			
		||||
 | 
			
		||||
                val format = AudioFormat.Builder()
 | 
			
		||||
                    .setSampleRate(SAMPLE_RATE)
 | 
			
		||||
                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
 | 
			
		||||
                    .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
 | 
			
		||||
                    .build()
 | 
			
		||||
 | 
			
		||||
                AudioTrack.Builder()
 | 
			
		||||
                    .setAudioAttributes(attributes)
 | 
			
		||||
                    .setAudioFormat(format)
 | 
			
		||||
                    .setBufferSizeInBytes(bufferSize)
 | 
			
		||||
                    .build()
 | 
			
		||||
            } else {
 | 
			
		||||
                @Suppress("DEPRECATION")
 | 
			
		||||
                AudioTrack(
 | 
			
		||||
                    AudioManager.STREAM_MUSIC,
 | 
			
		||||
                    SAMPLE_RATE,
 | 
			
		||||
                    AudioFormat.CHANNEL_OUT_MONO,
 | 
			
		||||
                    AudioFormat.ENCODING_PCM_16BIT,
 | 
			
		||||
                    bufferSize,
 | 
			
		||||
                    AudioTrack.MODE_STREAM
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            audioTrack?.apply {
 | 
			
		||||
                // Устанавливаем громкость
 | 
			
		||||
                setVolume(0.8f)
 | 
			
		||||
 | 
			
		||||
                // Воспроизводим
 | 
			
		||||
                play()
 | 
			
		||||
 | 
			
		||||
                // Пишем данные
 | 
			
		||||
                write(buffer, 0, buffer.size)
 | 
			
		||||
 | 
			
		||||
                // Ждем окончания воспроизведения
 | 
			
		||||
                setNotificationMarkerPosition(buffer.size)
 | 
			
		||||
                setPlaybackPositionUpdateListener(object : AudioTrack.OnPlaybackPositionUpdateListener {
 | 
			
		||||
                    override fun onMarkerReached(track: AudioTrack) {
 | 
			
		||||
                        stop()
 | 
			
		||||
                        release()
 | 
			
		||||
                        isPlaying = false
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    override fun onPeriodicNotification(track: AudioTrack) {}
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            e.printStackTrace()
 | 
			
		||||
            isPlaying = false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun stop() {
 | 
			
		||||
        isPlaying = false
 | 
			
		||||
        audioTrack?.stop()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun release() {
 | 
			
		||||
        stop()
 | 
			
		||||
        audioTrack?.release()
 | 
			
		||||
        audioTrack = null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user