Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Audio Upload

The CGD1 supports uploading custom ringtones via a block-based BLE transfer protocol. Audio is sent as 8-bit unsigned PCM at 8 kHz mono.

Audio Format

PropertyValue
Format8-bit unsigned PCM
Sample rate8000 Hz
ChannelsMono
Max size~98 KB (~12 seconds)
PaddingMultiple of 512 bytes (00 end marker + FF fill)

Ringtone Signatures

Built-in Ringtones

The original Qingping/ClearGrass PCM ringtones have been replaced with audio from lomiri-sounds (CC-BY-SA-3.0) and a chiptune remix by Dubmood (CC-BY-NC-SA-4.0). See LICENSE_RINGTONES.md for full attribution and license details.

SignatureNameSourceCopyright holderLicensePCM length
fdc366a5BeepAlarm clock.ogg2013, Canonical Ltd.CC-BY-SA-3.095967
0961bb77Digital RingtoneMallet.ogg2013, Canonical Ltd.CC-BY-SA-3.018155
ba2c2c8cDigital Ringtone 2Sintonia.ogg2018, Mauricio DuarteCC-BY-4.018462
ea2d4c02CuckooCounterpoint.ogg2013, Canonical Ltd.CC-BY-SA-3.076522
791bacb3Telephone RingtoneCall me.ogg2018, AnonymousCC0-1.095967
1d019fd6Exotic GuitarLatin.ogg2013, Canonical Ltd.CC-BY-SA-3.096000
6e70b659Lively PianoUBports.ogg2018, Mauricio DuarteCC-BY-4.096000
8f004886Story PianoMelody piano.ogg2013, Canonical Ltd.CC-BY-SA-3.086043
26522519Forest PianoMangore.ogg2018, Mauricio DuarteCC-BY-4.069819
4d6f6e6bMonkey Islandmonkey-island-8bitKalle Jonsson (Dubmood)CC-BY-NC-SA-4.080000
416c5379Alarm SynthAlarm synth.ogg2013, Canonical Ltd.CC-BY-SA-3.096000
41724d62Array MbiraArray mbira.ogg2013, Canonical Ltd.CC-BY-SA-3.079033
426c6973BlissBliss.ogg2013, Canonical Ltd.CC-BY-SA-3.047181
43656c73CelestialCelestial.ogg2013, Canonical Ltd.CC-BY-SA-3.096000
456e7472EntropyEntropy.ogg2018, Mauricio DuarteCC-BY-4.096000
476c4d61Glass MarimbaGlass marimba.ogg2013, Canonical Ltd.CC-BY-SA-3.096000
48616c6fHalo PentatonicHalo Pentatonic.ogg2013, Canonical Ltd.CC-BY-SA-3.087819
4861726dHarmonicsHarmonics.ogg2013, Canonical Ltd.CC-BY-SA-3.064230
48617270Harp ArpHarp arp.ogg2013, Canonical Ltd.CC-BY-SA-3.050077
4b6f746fKoto ChordsKoto chords.ogg2013, Canonical Ltd.CC-BY-SA-3.096000
53616b65Sakenointisakenointi.ogg2018, TMetsoCC-BY-4.078454
53616d73Sam’s SongSam’s Song.ogg2013, Sam HulickCC-BY-SA-3.038147
536f756cSoulSoul.ogg2013, Canonical Ltd.CC-BY-SA-3.096000
53706172SparkleSparkle.ogg2013, Canonical Ltd.CC-BY-SA-3.071332
53757072SupremeSupreme.ogg2013, Canonical Ltd.CC-BY-SA-3.091793
53757275Suru ArpeggioSuru arpeggio.ogg2013, Canonical Ltd.CC-BY-SA-3.096000
54696d65Time Not LostTime not Lost.ogg2018, Mauricio DuarteCC-BY-4.095944
576f6f64Wooden DriveWooden Drive.ogg2018, Amber ForestCC-BY-3.056816
958f8a83ElysiumELYSIUM.MODJesterCC-BY-NC-SA-4.096000

Sources:

  • lomiri-sounds: https://gitlab.com/ubports/development/core/lomiri-sounds
  • Upstream copyright file: https://gitlab.com/ubports/development/core/lomiri-sounds/-/blob/main/debian/copyright?ref_type=heads
  • Dubmood (Monkey Island): chiptune remix, CC-BY-NC-SA-4.0
  • Full attribution and license details: LICENSE_RINGTONES.md

Custom Slots

Two alternating slot signatures are used for custom uploads:

SignatureNameConstant
deaddeadCustom Slot ARingtoneSignature::CustomSlotA
beefbeefCustom Slot BRingtoneSignature::CustomSlotB

Always alternate between slots when uploading new custom audio. The device may reject uploads if the target signature matches the currently active ringtone.

Custom Ringtones from ~/.config/cgd1-rs/ringtones/

The GTK controller automatically discovers user-provided ringtone files placed in the XDG config directory:

~/.config/cgd1-rs/ringtones/*.pcm

Each .pcm file appears as a separate entry in the Audio Editor’s ringtone dropdown, using the filename (without extension) as the display name. WAV files with a .pcm extension are also accepted — the controller automatically extracts the raw PCM data from the data chunk if the file starts with RIFF....WAVE.

How It Works

  1. Signature derivation: The controller computes a deterministic 4-byte signature by hashing the filename. The hash avoids collisions with all known built-in and slot signatures by incrementing a salt until a non-colliding value is found. The resulting signature is stored as a RingtoneSignature::Custom([u8; 4]).

  2. Upload on Apply: When the user selects a custom ringtone and clicks “Apply”, the controller reads the PCM file from disk, uploads the audio to the device under the derived signature, and writes that signature to the device settings to activate it.

  3. Read-back: When reading settings from the device, if the device reports a Custom signature that matches a known custom ringtone file, the dropdown automatically selects that entry. If the file no longer exists, the raw hex signature is displayed instead.

Adding a Custom Ringtone

# Create the directory if it doesn't exist
mkdir -p ~/.config/cgd1-rs/ringtones

# Copy your PCM file (8-bit unsigned, 8 kHz, mono, max. 98 KB)
cp my_ringtone.pcm ~/.config/cgd1-rs/ringtones/my_ringtone.pcm

Restart the controller (or re-open the Audio Editor panel) for the new ringtone to appear in the dropdown.

Note: Custom ringtones from the config directory use RingtoneSignature::Custom with a derived hash signature, not the fixed CustomSlotA/CustomSlotB slots. The two fixed slots remain available for manual uploads via the “Custom Upload” section of the Audio Editor.

Upload Protocol

sequenceDiagram
    participant App as Application
    participant Device as ClockDevice
    participant CGD1 as CGD1 Device

    Note over App,CGD1: Step 0: Prepare audio
    App->>App: Validate + pad to 512-byte multiple

    Note over App,CGD1: Step 1: MTU Exchange
    App->>CGD1: Request MTU 247
    CGD1-->>App: Negotiated MTU

    Note over App,CGD1: Step 2: Audio Init
    App->>Device: upload_ringtone(audio, signature)
    Device->>CGD1: 08 10 [Size 3B LE] [Signature 4B]
    CGD1-->>Device: ACK 04 ff 10 00 [Payload]

    Note over App,CGD1: Step 3: Audio Data (block-based)
    loop Every 4 packets (512 bytes)
        App->>CGD1: 81 08 [Audio 128B]
        App->>CGD1: 81 08 [Audio 128B]
        App->>CGD1: 81 08 [Audio 128B]
        App->>CGD1: 81 08 [Audio 128B]
        CGD1-->>App: ACK 04 ff 08 00 [Payload]
    end

    Note over App,CGD1: Step 4: Completion
    Note over CGD1: Device stores audio under signature

Step 0 - Prepare the Payload

  1. Decode/resample the source file to 8-bit unsigned PCM, 8000 Hz, mono
  2. Pad to a multiple of 512 bytes: first padding byte is 00 (end-of-audio marker), remaining are FF
  3. Keep the total under ~98 KB

The validate_audio function checks these constraints and returns an error if they are violated.

Step 1 - MTU Exchange

Before uploading, an MTU exchange is performed to ensure the 130-byte packets (128 bytes audio + 2-byte header) fit within a single BLE packet:

#![allow(unused)]
fn main() {
let mtu = transport.request_mtu(247).await?;
if mtu < 130 {
    return Err(ClockError::MtuTooSmall { mtu });
}
}

Step 2 - Audio Init

Send 08 10 [Size 3B LE] [Signature 4B] to Data Write.

  • Size: Padded audio length in bytes (Little Endian, 3 bytes)
  • Signature: Target ringtone slot signature

Wait for ACK: 04 ff 10 [Status] [Payload] (status 00 = success)

Step 3 - Send Audio Data

  • Packet format: 81 08 [Audio 128B] (130 bytes on the wire)
  • A trailing packet shorter than 128 bytes is padded with FF
  • Packets per block: 4 (512 bytes of audio per block)
  • After every 4th packet (or the last packet), wait for block ACK: 04 ff 08 [Status] [Payload]
  • Each packet is written with write-with-response

Step 4 - Completion

After the last block ACK, the device stores the audio under the given signature. Select it as the active ringtone by writing the same signature in the settings payload (bytes 16–19).

Important: The transfer must own the connection. Alarm reads, settings reads, RSSI polling, or notification re-subscriptions issued in parallel can abort the upload. The library holds a mutex for the whole transfer.

CLI Usage

cgd1 ringtone-upload AA:BB:CC:DD:EE:FF audio.pcm --signature CustomSlotA
ArgumentDescription
addressDevice MAC address
filePath to 8-bit PCM audio file (8 kHz, mono)
--signatureRingtone name (CustomSlotA, CustomSlotB) or 4-byte hex (e.g., deadbeef)

After uploading, select the ringtone by writing its signature to the device settings:

cgd1 settings-write AA:BB:CC:DD:EE:FF --volume 3

The CLI does not yet support writing the ringtone signature directly via settings-write. Use the library API or the GTK controller for this.

Library API

#![allow(unused)]
fn main() {
use cgd1_rs::RingtoneSignature;
use std::path::Path;

// Upload from file
let audio = std::fs::read("audio.pcm")?;
device.upload_ringtone(&audio, RingtoneSignature::CustomSlotA).await?;

// Or upload from bytes
let audio: Vec<u8> = generate_pcm_audio();
device.upload_ringtone(&audio, RingtoneSignature::CustomSlotA).await?;

// Select as active ringtone
let mut settings = device.read_settings().await?;
settings.ringtone_signature = RingtoneSignature::CustomSlotA;
device.write_settings(&settings).await?;
}