Packages
ExCubecl is a GPU compute runtime for Elixir powered by CubeCL via Rust NIFs. Provides GPU buffer management, kernel execution, async command submission, and pipeline orchestration. Currently includes CPU fallback implementations.
Current section
Files
Jump to
Current section
Files
examples/audio_processing.exs
# GPU Audio Processing Example
# Demonstrates audio operations: mix, overlay, resample, channel conversion, filters
# Run with: mix run examples/audio_processing.exs
IO.puts("=== GPU Audio Processing Pipeline ===")
IO.puts("")
# Check device
{:ok, info} = ExCubecl.device_info()
IO.puts("Device: #{info.device_name}")
IO.puts("")
# ── Open a media source ──────────────────────────────────────
IO.puts("--- Media Source ---")
{:ok, src} = ExCubecl.Media.open("sample_audio.mp4")
{:ok, streams} = ExCubecl.Media.streams(src)
Enum.each(streams, fn stream ->
case stream.type do
:video ->
IO.puts("Video stream: #{stream.codec} #{stream.width}x#{stream.height}")
:audio ->
IO.puts("Audio stream: #{stream.codec} #{stream.sample_rate}Hz #{stream.channels}ch")
end
end)
IO.puts("")
# ── Read audio samples ───────────────────────────────────────
IO.puts("--- Reading Audio Samples ---")
{:ok, samples_a} = ExCubecl.Media.read_frame(src, :audio)
{:ok, samples_b} = ExCubecl.Media.read_frame(src, :audio)
{:ok, samples_c} = ExCubecl.Media.read_frame(src, :audio)
IO.puts("Track A: #{samples_a.channels}ch #{samples_a.sample_rate}Hz #{samples_a.frames} frames")
IO.puts("Track B: #{samples_b.channels}ch #{samples_b.sample_rate}Hz #{samples_b.frames} frames")
IO.puts("Track C: #{samples_c.channels}ch #{samples_c.sample_rate}Hz #{samples_c.frames} frames")
IO.puts("")
# ── Audio mixing ─────────────────────────────────────────────
IO.puts("--- Audio Mixing ---")
# Mix two tracks with equal gain
{:ok, mixed} = ExCubecl.Audio.mix([samples_a, samples_b])
IO.puts("Mix (2 tracks, equal gain): #{mixed.channels}ch #{mixed.sample_rate}Hz")
# Mix three tracks with custom gains
{:ok, mixed_3} = ExCubecl.Audio.mix([samples_a, samples_b, samples_c], gains: [0.5, 0.3, 0.2])
IO.puts("Mix (3 tracks, gains=[0.5, 0.3, 0.2]): #{mixed_3.channels}ch")
IO.puts("")
# ── Audio overlay with ducking ───────────────────────────────
IO.puts("--- Audio Overlay (Ducking) ---")
# Overlay voiceover on background music with -12dB ducking
{:ok, overlaid} = ExCubecl.Audio.overlay(samples_a, samples_b, duck_level: -12)
IO.puts("Overlay (duck=-12dB): #{overlaid.channels}ch #{overlaid.sample_rate}Hz")
# Overlay with aggressive ducking for voiceover clarity
{:ok, overlaid_deep} = ExCubecl.Audio.overlay(samples_a, samples_b, duck_level: -24)
IO.puts("Overlay (duck=-24dB): #{overlaid_deep.channels}ch #{overlaid_deep.sample_rate}Hz")
IO.puts("")
# ── Resampling ───────────────────────────────────────────────
IO.puts("--- Resampling ---")
# Upsample 44.1kHz → 48kHz (common for video production)
{:ok, upsampled} = ExCubecl.Audio.resample(samples_a, from: 44_100, to: 48_000)
IO.puts("Upsample: 44100Hz → 48000Hz (#{samples_a.frames} → #{upsampled.frames} frames)")
# Downsample 96kHz → 44.1kHz (mastering)
{:ok, downsampled} = ExCubecl.Audio.resample(samples_a, from: 96_000, to: 44_100)
IO.puts("Downsample: 96000Hz → 44100Hz (#{samples_a.frames} → #{downsampled.frames} frames)")
# Same-rate resample (no-op)
{:ok, same_rate} = ExCubecl.Audio.resample(samples_a, from: 48_000, to: 48_000)
IO.puts("Same rate: 48000Hz → 48000Hz (#{same_rate.frames} frames)")
IO.puts("")
# ── Channel conversion ───────────────────────────────────────
IO.puts("--- Channel Conversion ---")
# Stereo → Mono (for voice analysis)
{:ok, mono} = ExCubecl.Audio.channels(samples_a, :stereo, :mono)
IO.puts("Stereo → Mono: #{mono.channels}ch")
# Mono → Stereo (for playback compatibility)
{:ok, stereo} = ExCubecl.Audio.channels(mono, :mono, :stereo)
IO.puts("Mono → Stereo: #{stereo.channels}ch")
# Stereo → 5.1 Surround (for theater mix)
{:ok, surround_51} = ExCubecl.Audio.channels(samples_a, :stereo, :surround_51)
IO.puts("Stereo → 5.1 Surround: #{surround_51.channels}ch")
# Stereo → 7.1 Surround (for IMAX)
{:ok, surround_71} = ExCubecl.Audio.channels(samples_a, :stereo, :surround_71)
IO.puts("Stereo → 7.1 Surround: #{surround_71.channels}ch")
IO.puts("")
# ── GPU audio filters ────────────────────────────────────────
IO.puts("--- GPU Audio Filters ---")
# Normalize audio levels
{:ok, normalized} = ExCubecl.Filter.apply(samples_a, :normalize)
IO.puts("Normalize: #{normalized.channels}ch #{normalized.sample_rate}Hz")
# Apply EQ (biquad filter)
{:ok, _eq_applied} = ExCubecl.Filter.apply(samples_a, :eq, bands: [{:high_pass, 80}])
IO.puts("EQ: high_pass=80Hz")
# Apply dynamics compressor
{:ok, _compressed} = ExCubecl.Filter.apply(samples_a, :compressor, threshold: -18, ratio: 4.0)
IO.puts("Compressor: threshold=-18dB ratio=4:1")
# Apply reverb (FFT convolution)
{:ok, _reverbed} = ExCubecl.Filter.apply(samples_a, :reverb, room_size: 0.5, wet: 0.2)
IO.puts("Reverb: room_size=0.5 wet=0.2")
IO.puts("")
# ── Audio processing pipeline ────────────────────────────────
IO.puts("--- Audio Processing Pipeline ---")
# Build a mastering chain: normalize → compress → EQ
{:ok, pipeline} = ExCubecl.pipeline()
{:ok, stage1_out} = ExCubecl.buffer(List.duplicate(0.0, 8), [8], :f32)
ExCubecl.pipeline_add(pipeline, "pcm_normalize", [samples_a.handle], stage1_out)
{:ok, stage2_out} = ExCubecl.buffer(List.duplicate(0.0, 8), [8], :f32)
ExCubecl.pipeline_add(pipeline, "dynamics_compress", [stage1_out], stage2_out)
{:ok, stage3_out} = ExCubecl.buffer(List.duplicate(0.0, 8), [8], :f32)
ExCubecl.pipeline_add(pipeline, "biquad_filter", [stage2_out], stage3_out)
IO.puts("Pipeline: normalize → compress → EQ (3 stages)")
{:ok, cmd_ids} = ExCubecl.pipeline_run(pipeline)
IO.puts("Pipeline executed: #{length(cmd_ids)} commands")
:ok = ExCubecl.pipeline_free(pipeline)
IO.puts("")
# ── Async batch processing ───────────────────────────────────
IO.puts("--- Async Batch Processing ---")
# Process multiple audio segments concurrently
cmd_ids =
for i <- 1..5 do
{:ok, cmd} = ExCubecl.submit("process_segment_#{i}")
cmd
end
IO.puts("Submitted #{length(cmd_ids)} async audio processing commands")
# Wait for all to complete
results = Enum.map(cmd_ids, &ExCubecl.wait/1)
completed = Enum.count(results, &(&1 == :ok))
IO.puts("Completed: #{completed}/#{length(cmd_ids)}")
IO.puts("")
# ── Cleanup ──────────────────────────────────────────────────
IO.puts("--- Cleanup ---")
:ok = ExCubecl.Media.close(src)
IO.puts("Media source closed")
# Buffers are automatically freed when GC'd — no manual free needed
IO.puts("")
IO.puts("=== Done ===")