Skip to content
serptailPublic

About

Free, open source music app for Android. Stream any song, download for offline, identify music with Shazam, equalizer and Mods, synced lyrics, and automatic in-app updates. No ads, no account needed.

Topics

Resources

Stars

21 stars

Watchers

0 watching

Forks

Repository files navigation

gamut



Free, open source music streaming for Android.
Stream any song, build your library, take it offline.


Release CI Platform Kotlin Jetpack Compose License

Architecture diagram

Download  ·  Website  ·  Features  ·  Build from source  ·  Report a bug  ·  Support


Discover Library Favorites Song recognition

Table of Contents

Overview

gamut is a complete music app for Android: search and play songs, build playlists, follow artists, download for offline listening, and keep everything in sync across devices. It has no subscription, no ads and no paywalled features, and it works without an account.

It is built entirely with Kotlin and Jetpack Compose, split into a modular Gradle project, with an optional Supabase backend that handles sync.

Typical workflow:

  1. Open Discover and browse charts, or search for any artist, song or album.
  2. Tap a result to start playback, then shape the sound with Mods and the equalizer.
  3. Like songs, add them to playlists with mood tags, and follow artists.
  4. Download what you want for offline playback.
  5. Hear something out loud? Tap the Shazam button to identify it.

No account required. Search, stream and build a library without signing up. Sign in with Google only if you want your favorites, playlists and followed artists to sync across devices.

Features

Area What it does
Discover Opens on albums on the rise, with Trending, Billboard, Latest and Albums charts. Search artists, songs and albums, with recent searches and quick-resume tiles
Library Liked songs, playlists with mood tags, followed artists, offline downloads, plus the music already stored on your phone
Player Colors drawn from the artwork, synced lyrics that follow the song line by line, queue, sleep timer, crossfade, and playback controls on the notification shade and lock screen
Mods Nightcore, Daycore, Slowed + Reverb and Lo-Fi presets, or dial speed, pitch and bass yourself. Pitch can follow speed (tape style) or stay in key
Equalizer 10-band parametric EQ from 60 Hz to 14 kHz, with presets, adjustable frequency and Q, and automatic headroom that lowers the level to match
Song recognition Built-in Shazam button. Hold your phone near the music and gamut tells you what is playing
Offline Download songs once and play them with no connection, individually or by playlist
Sync Optional Google sign-in syncs favorites, playlists and followed artists to a new phone
Sharing Send a song as a card image with its link, or just the URL, in the color you like
Everywhere Android Auto browsing, notification and lock-screen controls, phones and tablets
Appearance Light and dark themes throughout

Screenshots

Discover Library Favorites Song recognition
Discover, dark Library, dark Favorites, dark Song recognition, dark
Discover, light Library, light Favorites, light Song recognition, light

Every screen ships with matching dark and light themes. Library and Favorites pick up their background colors from the artwork that is playing.

Installation

gamut runs on Android 8.0 (API 26) and newer, on phones and tablets.

  1. Open the latest release and download .apk.
  2. Open the file on your phone. If Android asks, allow installs from your browser for this one time.
  3. Launch gamut. No account or setup is needed.

Updates are built in. gamut checks for new releases itself and downloads them in the background.

Note: Official release builds target arm64-v8a and armeabi-v7a, which covers virtually all Android phones, 64-bit and 32-bit. Build from source if you need another ABI.

How It Works

gamut does not host any music. When you play a track, it finds a matching source on YouTube Music and streams it, caching the result so repeat plays and the next song in the queue start quickly.

flowchart LR
    A([Track selected]) --> B["Search YouTube Music<br/>songs first, then broader"]
    B --> C{"Score candidates<br/>title, artist, duration"}
    C -->|best match above confidence floor| D["Resolve stream URL"]
    C -->|nothing good enough| X([Playback error shown])
    D --> E["NewPipe Extractor"]
    E -->|timeout or failure| F["yt-dlp fallback"]
    E --> G[("Cached stream URL")]
    F --> G
    G --> H["Media3 player<br/>Mod and EQ audio processors"]
Loading
  • Matching. Candidates are ranked by how well their title and channel match the track, with a bonus for official catalog and "Topic" uploads and penalties for stray words and derivative uploads. The track's duration is used as a sanity check.
  • Resolving. NewPipe Extractor is tried first. If it times out or fails, gamut falls back to an embedded yt-dlp, which updates itself in the background.
  • Prefetching. Upcoming tracks are resolved ahead of time on suitable connections, and resolved URLs are cached until they expire.
  • Metadata. Artwork, artists and album details are enriched from public catalogs through a small Supabase edge function (resolve-metadata), with an allow-list on artwork hosts.
  • Staying alive. Streaming sources change often. A scheduled workflow tests new NewPipe Extractor and yt-dlp releases every six hours and proposes version bumps, and the app can prompt you to update when a fix ships.

Architecture

gamut is a multi-module Gradle project. Feature modules depend on core modules, never on each other, and :app wires everything together with Hilt.

Module Responsibility
:app Entry points, navigation, profile and account screens, in-app updater, Shazam listening service
:feature:feature-search Discover, search, artist and album screens, charts, song recognition
:feature:feature-library Favorites, playlists and mood tags, downloads UI, on-device music
:feature:feature-player-ui Player screen, lyrics, queue, equalizer, Mod sheet, sleep timer, sharing
:feature:feature-resolver Track-to-stream resolution, candidate scoring, prefetching, download manager, yt-dlp updater
:core:core-player Media3 player, media session service, Mod and sound-engine audio processors, equalizer, Android Auto browse tree
:core:core-database Room database, DAOs, download index, recently played
:core:core-network HTTP clients for search, catalogs and recognition
:core:core-sync Supabase client, Google sign-in, sync of favorites, playlists and follows
:core:core-model / core-common Shared models and utilities
:core:core-designsystem Theme, typography, shared Compose components
:baselineprofile Baseline and startup profile generation
:tools:extractor-smoke Smoke test used to validate extractor upgrades

Tech Stack

Android app

Concern Choice
Language and UI Kotlin 2.0, Jetpack Compose, Material 3
Dependency injection Hilt (with KSP)
Playback AndroidX Media3, custom audio processors for Mods and EQ
Persistence Room
Networking Retrofit, OkHttp, Ktor, kotlinx.serialization
Streaming sources NewPipe Extractor, youtubedl-android (yt-dlp)
Images and color Coil, AndroidX Palette, Haze (blur)
Background work WorkManager
Sync and auth Supabase (Kotlin client), Credential Manager with Google ID
Performance Baseline Profiles, Profile Installer

Backend (optional, for sync): Supabase Postgres with row level security, and two Deno edge functions.

Tooling: Gradle Kotlin DSL with a version catalog, GitHub Actions for CI, releases, database backups and dependency bumps.

Project Structure

.github/workflows/        ci, release, backup, keepalive, extractor-bump
app/                      Application module (entry points, update flow, accounts)
baselineprofile/          Baseline profile generator
core/
├── core-common/          Shared utilities
├── core-database/        Room entities, DAOs, download index
├── core-designsystem/    Theme and shared Compose UI
├── core-model/           Domain models
├── core-network/         API clients (search, catalogs, recognition)
├── core-player/          Media3 player, audio processors, equalizer, media session
└── core-sync/            Supabase client, auth, sync
feature/
├── feature-library/      Favorites, playlists, downloads, local music
├── feature-player-ui/    Player, lyrics, queue, EQ, Mods, sharing
├── feature-resolver/     Source resolution, scoring, downloads
└── feature-search/       Discover, search, artist and album pages
gamut-brandkit/           Logos and Android drawables
docs/                     Project website (GitHub Pages) and screenshots
scripts/                  bump_extractors.py
supabase/
├── functions/            resolve-metadata, delete-account
├── migrations/           SQL schema
└── tests/                Row level security tests
tools/extractor-smoke/    Extractor smoke test
%% Generated by https://gitdiagram.com/serptail/gamut
flowchart TD

subgraph group_app["App and experience"]
  node_appentry["App entry<br/>[MainActivity.kt]"]
end

subgraph group_discovery["Discovery and recognition"]
  node_discover["Discover feed"]
  node_search["Track search"]
  node_catalogs["Music catalogs<br/>[DeezerChartsApi.kt]"]
  node_recognition["Song recognition<br/>[ShazamSession.kt]"]
  node_shazamservice["Recognition service"]
  node_shazamclient["Shazam client<br/>[ShazamClient.kt]"]
end

subgraph group_playback["Playback and offline"]
  node_tracksource["Source resolution"]
  node_youtube["YouTube extractor"]
  node_playerui["Player interface<br/>[PlayerScreen.kt]"]
  node_musicplayer["Music player"]
  node_session["Media session"]
  node_audioprocessing["Audio effects"]
  node_downloads["Offline downloads"]
  node_downloadservice["Download service<br/>[DownloadService.kt]"]
  node_storage["Storage settings<br/>[StorageSettings.kt]"]
end

subgraph group_data["Library and sync"]
  node_library["Library and playlists"]
  node_localmusic["Local music"]
  node_database[("App database<br/>[GamutDatabase.kt]")]
  node_downloadindex["Download index<br/>[DownloadIndex.kt]"]
  node_sync["Cloud sync<br/>[SyncEngine.kt]"]
  node_auth["Account sign-in<br/>[AuthRepository.kt]"]
end

node_listener(("Listener"))
node_supabase[("Supabase")]

node_listener -->|"opens app"| node_appentry
node_appentry -->|"browses"| node_discover
node_appentry -->|"searches"| node_search
node_discover -->|"loads charts"| node_catalogs
node_search -->|"searches tracks"| node_youtube
node_listener -->|"identifies music"| node_recognition
node_recognition -->|"uses service"| node_shazamservice
node_shazamclient -.->|"supports sessions"| node_recognition
node_listener -->|"selects track"| node_playerui
node_playerui -.->|"resolves track"| node_tracksource
node_tracksource -.->|"resolves stream"| node_youtube
node_youtube -.->|"provides stream"| node_musicplayer
node_musicplayer -.->|"processes audio"| node_audioprocessing
node_session -->|"controls playback"| node_musicplayer
node_listener -->|"builds library"| node_library
node_library -.->|"stores library"| node_database
node_listener -->|"browses device music"| node_localmusic
node_listener -->|"downloads tracks"| node_downloads
node_downloads -->|"resolves download"| node_tracksource
node_downloads -->|"records downloads"| node_downloadindex
node_downloadservice -->|"observes batch"| node_downloads
node_downloadindex -->|"reads database"| node_database
node_listener -->|"signs in optionally"| node_auth
node_auth -.->|"authenticates sync"| node_sync
node_sync -->|"syncs library"| node_supabase
node_sync -->|"syncs local data"| node_database
node_listener -->|"sets download options"| node_storage

click node_appentry "https://github.com/serptail/gamut/blob/main/app/src/main/kotlin/com/gamut/MainActivity.kt"
click node_discover "https://github.com/serptail/gamut/blob/main/feature/feature-search/src/main/kotlin/com/gamut/feature/search/DiscoverRepository.kt"
click node_search "https://github.com/serptail/gamut/blob/main/feature/feature-search/src/main/kotlin/com/gamut/feature/search/SearchRepository.kt"
click node_catalogs "https://github.com/serptail/gamut/blob/main/core/core-network/src/main/kotlin/com/gamut/core/network/DeezerChartsApi.kt"
click node_recognition "https://github.com/serptail/gamut/blob/main/feature/feature-search/src/main/kotlin/com/gamut/feature/search/ShazamSession.kt"
click node_shazamservice "https://github.com/serptail/gamut/blob/main/app/src/main/kotlin/com/gamut/ShazamListenService.kt"
click node_shazamclient "https://github.com/serptail/gamut/blob/main/core/core-network/src/main/kotlin/com/gamut/core/network/ShazamClient.kt"
click node_tracksource "https://github.com/serptail/gamut/blob/main/feature/feature-resolver/src/main/kotlin/com/gamut/feature/resolver/ResolveTrackSourceUseCase.kt"
click node_youtube "https://github.com/serptail/gamut/blob/main/feature/feature-resolver/src/main/kotlin/com/gamut/feature/resolver/NewPipeYoutubeSource.kt"
click node_playerui "https://github.com/serptail/gamut/blob/main/feature/feature-player-ui/src/main/kotlin/com/gamut/feature/playerui/PlayerScreen.kt"
click node_musicplayer "https://github.com/serptail/gamut/blob/main/core/core-player/src/main/kotlin/com/gamut/core/player/Media3MusicPlayer.kt"
click node_session "https://github.com/serptail/gamut/blob/main/core/core-player/src/main/kotlin/com/gamut/core/player/GamutMediaSessionService.kt"
click node_audioprocessing "https://github.com/serptail/gamut/blob/main/core/core-player/src/main/kotlin/com/gamut/core/player/SoundEngineProcessor.kt"
click node_downloads "https://github.com/serptail/gamut/blob/main/feature/feature-resolver/src/main/kotlin/com/gamut/feature/resolver/TrackDownloadManager.kt"
click node_downloadservice "https://github.com/serptail/gamut/blob/main/feature/feature-resolver/src/main/kotlin/com/gamut/feature/resolver/DownloadService.kt"
click node_library "https://github.com/serptail/gamut/blob/main/feature/feature-library/src/main/kotlin/com/gamut/feature/library/LibraryRepository.kt"
click node_localmusic "https://github.com/serptail/gamut/blob/main/feature/feature-library/src/main/kotlin/com/gamut/feature/library/LocalMusicScreen.kt"
click node_database "https://github.com/serptail/gamut/blob/main/core/core-database/src/main/kotlin/com/gamut/core/database/GamutDatabase.kt"
click node_downloadindex "https://github.com/serptail/gamut/blob/main/core/core-database/src/main/kotlin/com/gamut/core/database/DownloadIndex.kt"
click node_sync "https://github.com/serptail/gamut/blob/main/core/core-sync/src/main/kotlin/com/gamut/core/sync/SyncEngine.kt"
click node_auth "https://github.com/serptail/gamut/blob/main/core/core-sync/src/main/kotlin/com/gamut/core/sync/AuthRepository.kt"
click node_storage "https://github.com/serptail/gamut/blob/main/feature/feature-resolver/src/main/kotlin/com/gamut/feature/resolver/StorageSettings.kt"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_appentry toneBlue
class node_discover,node_search,node_catalogs,node_recognition,node_shazamservice,node_shazamclient,node_supabase toneAmber
class node_tracksource,node_youtube,node_playerui,node_musicplayer,node_session,node_audioprocessing,node_downloads,node_downloadservice,node_storage toneMint
class node_library,node_localmusic,node_database,node_downloadindex,node_sync,node_auth toneRose
class node_listener toneIndigo
Loading

Building from Source

Prerequisites

  • JDK 21
  • Android SDK with platform 35
  • Android Studio (current stable) or the command line

Build

git clone https://github.com/serptail/gamut.git
cd gamut
./gradlew :app:assembleDebug

The APK is written to app/build/outputs/apk/debug/.

Run the unit tests:

./gradlew testDebugUnitTest

Optional: enable sync

Sync is off by default in a fresh checkout. Everything else works without it. To enable Google sign-in and sync, create a local.properties file in the project root:

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
GOOGLE_WEB_CLIENT_ID=your-google-web-client-id.apps.googleusercontent.com

When SUPABASE_URL or SUPABASE_ANON_KEY is blank, the sync layer disables itself. local.properties is git-ignored.

Permissions and Privacy

Permission Why it is needed
INTERNET Search, streaming, lyrics and catalog data
RECORD_AUDIO Only while you run song recognition
READ_MEDIA_AUDIO Playing music files already on your phone
Foreground services (media playback, data sync, microphone) Background playback, downloads and recognition

With sync, gamut stores only your favorite songs, playlists and the artists you follow, and nothing else. Read the full privacy policy.

Troubleshooting

Symptom Fix
"Songs can't be loaded right now" The music source has probably changed. Check for an update inside the app or on the releases page, and try again later
A track starts slowly or fails once Retry. Stream URLs expire, and gamut refreshes and re-resolves them automatically
Android blocks the install Allow installs from your browser (or file manager) for this one time in system settings
Song recognition does nothing Grant the microphone permission and hold the phone closer to the audio source
Local files do not appear Grant the audio permission, then open Library → On this device
No Google sign-in in a self-built app Sync needs the local.properties keys described in Optional: enable sync

Still stuck? Open an issue with what happened and which phone you use.

FAQ

Is gamut really free?

Yes. Streaming, downloads and every feature are free. gamut is open source under the GNU AGPLv3, so you can read the code, change it and share it, as long as your changes stay open under the same license.

Do I need an account?

No. Sign in with Google only if you want your favorites, playlists and followed artists to sync across devices.

Where does the music come from?

gamut searches and streams from YouTube Music, and also plays the files already on your phone, so everything lives in one library.

Does it work offline?

Yes. Songs you download in gamut play with no connection, and the files on your phone always play.

Is there an iPhone or desktop version?

No. gamut is Android only, with support for phones, tablets and Android Auto.

Contributing

Bug reports, ideas and pull requests are all welcome.

  1. Open an issue first for anything non-trivial, so the approach can be agreed on.

  2. Fork the repository and create a branch.

  3. Follow the official Kotlin code style (kotlin.code.style=official) and the existing module boundaries.

  4. Make sure the same checks CI runs pass locally:

    ./gradlew :app:assembleDebug testDebugUnitTest
    supabase test db   # if you touched supabase/
  5. Open a pull request describing what changed and why.

When reporting a bug, include your Android version, device model and the steps that trigger it.

Support

gamut is free and built in the open. If you enjoy it, any of these helps keep it that way:

Acknowledgements

gamut stands on the shoulders of excellent open source projects and public services:

Disclaimer

gamut is an independent project and is not affiliated with, endorsed by or sponsored by YouTube, Google, Shazam, Apple, Deezer, Billboard or any other third party mentioned here. All trademarks and album artwork belong to their respective owners. gamut does not host or distribute any audio. You are responsible for using it in accordance with the terms of the services it connects to and the laws where you live.

License

gamut is licensed under the GNU Affero General Public License v3.0. See LICENSE for the full text.

You are free to use, study, modify and share it, provided that derivative works, including those offered over a network, are released under the same license.


Made with love by @serptail

The gamut name, wordmark and ribbon mark in gamut-brandkit/ identify the project.

About

Free, open source music app for Android. Stream any song, download for offline, identify music with Shazam, equalizer and Mods, synced lyrics, and automatic in-app updates. No ads, no account needed.

Topics

Resources

Stars

21 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Contributors

Languages