AeThex-OS/android/app/src/main/java/com/aethex/os/SoundManager.java
MrPiglr b3c308b2c8 Add functional marketplace modules, bottom nav bar, root terminal, arcade games
- ModuleManager: Central tracking for installed marketplace modules
- DataAnalyzerWidget: Real-time CPU/RAM/Battery/Storage widget (unlocked by Data Analyzer module)
- BottomNavBar: Navigation bar for Projects/Chat/Marketplace/Settings
- RootShell: Real root command execution utility
- TerminalActivity: Full root shell with neofetch, sysinfo, real Linux commands
- Terminal Pro module: Adds aliases (ll, la, h), command history
- ArcadeActivity + SnakeGame: Pixel Arcade module unlocks retro games
- fade_in/fade_out animations for smooth transitions
2026-02-18 22:03:50 -07:00

162 lines
5.2 KiB
Java

package com.aethex.os;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioTrack;
import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Singleton sound manager for AeThexOS Android.
* Generates tones programmatically using AudioTrack - no sound asset files needed.
*/
public class SoundManager {
private static final int SAMPLE_RATE = 44100;
private static final float VOLUME = 0.3f;
public enum Sound {
OPEN(523, 0.1, WaveType.SINE),
CLOSE(392, 0.1, WaveType.SINE),
CLICK(800, 0.03, WaveType.SQUARE),
NOTIFICATION(880, 0.15, WaveType.SINE),
SWITCH(440, 0.2, WaveType.SAWTOOTH),
BOOT_BEEP(660, 0.05, WaveType.SINE);
final int frequency;
final double duration;
final WaveType waveType;
Sound(int frequency, double duration, WaveType waveType) {
this.frequency = frequency;
this.duration = duration;
this.waveType = waveType;
}
}
private enum WaveType {
SINE, SQUARE, SAWTOOTH
}
private static volatile SoundManager instance;
private final Map<Sound, byte[]> bufferCache = new EnumMap<>(Sound.class);
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private volatile boolean enabled = true;
private SoundManager() {
}
public static SoundManager getInstance() {
if (instance == null) {
synchronized (SoundManager.class) {
if (instance == null) {
instance = new SoundManager();
}
}
}
return instance;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
/**
* Play a sound on a background thread. Non-blocking.
*/
public void play(Sound sound) {
if (!enabled) return;
executor.execute(() -> {
try {
byte[] pcm = getOrGenerateBuffer(sound);
int bufferSize = pcm.length;
AudioAttributes attributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
AudioFormat format = new AudioFormat.Builder()
.setSampleRate(SAMPLE_RATE)
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build();
AudioTrack track = new AudioTrack.Builder()
.setAudioAttributes(attributes)
.setAudioFormat(format)
.setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STATIC)
.build();
track.write(pcm, 0, pcm.length);
track.setVolume(VOLUME);
track.play();
// Wait for playback to complete, then release
long durationMs = (long) (sound.duration * 1000) + 50;
Thread.sleep(durationMs);
track.stop();
track.release();
} catch (Exception e) {
// Silently ignore sound playback failures
}
});
}
private synchronized byte[] getOrGenerateBuffer(Sound sound) {
byte[] cached = bufferCache.get(sound);
if (cached != null) return cached;
byte[] buffer = generatePcmBuffer(sound.frequency, sound.duration, sound.waveType);
bufferCache.put(sound, buffer);
return buffer;
}
private byte[] generatePcmBuffer(int frequency, double duration, WaveType waveType) {
int numSamples = (int) (SAMPLE_RATE * duration);
byte[] pcm = new byte[numSamples * 2]; // 16-bit mono = 2 bytes per sample
for (int i = 0; i < numSamples; i++) {
double t = (double) i / SAMPLE_RATE;
double sample;
switch (waveType) {
case SQUARE:
sample = Math.signum(Math.sin(2.0 * Math.PI * frequency * t));
break;
case SAWTOOTH:
sample = 2.0 * (t * frequency - Math.floor(0.5 + t * frequency));
break;
case SINE:
default:
sample = Math.sin(2.0 * Math.PI * frequency * t);
break;
}
// Apply a short fade-in/fade-out envelope to avoid clicks
int fadeLength = Math.min(numSamples / 10, SAMPLE_RATE / 200);
if (i < fadeLength) {
sample *= (double) i / fadeLength;
} else if (i > numSamples - fadeLength) {
sample *= (double) (numSamples - i) / fadeLength;
}
short pcmValue = (short) (sample * Short.MAX_VALUE);
pcm[i * 2] = (byte) (pcmValue & 0xFF);
pcm[i * 2 + 1] = (byte) ((pcmValue >> 8) & 0xFF);
}
return pcm;
}
}