project init

This commit is contained in:
2026-05-20 14:35:34 +02:00
commit 4778a17822
12 changed files with 567 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
use flake
+4
View File
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
+26
View File
@@ -0,0 +1,26 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Temporary files, for example, from tests.
/tmp/
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
chromaprint-*.tar
/.direnv/
/.state/
+3
View File
@@ -0,0 +1,3 @@
[submodule "c_src/chromaprint"]
path = c_src/chromaprint
url = https://github.com/acoustid/chromaprint.git
+36
View File
@@ -0,0 +1,36 @@
PRIV_DIR := $(MIX_APP_PATH)/priv
BUILD_DIR := $(MIX_APP_PATH)/cmake_build
NIF_SO := $(PRIV_DIR)/chromaprint_nif.so
JOBS ?= $(shell nproc 2>/dev/null || echo 4)
CMAKE_BUILD_TYPE ?= Release
CHROMAPRINT_USE_SYSTEM ?= OFF
CMAKE_EXTRA_ARGS ?=
ifneq ($(strip $(CHROMAPRINT_INCLUDE_DIR)),)
CMAKE_EXTRA_ARGS += -DCHROMAPRINT_INCLUDE_DIR=$(CHROMAPRINT_INCLUDE_DIR)
endif
ifneq ($(strip $(CHROMAPRINT_LIB_DIR)),)
CMAKE_EXTRA_ARGS += -DCMAKE_LIBRARY_PATH=$(CHROMAPRINT_LIB_DIR)
endif
.PHONY: all clean
all: $(NIF_SO)
$(NIF_SO): $(BUILD_DIR)/CMakeCache.txt
@mkdir -p $(PRIV_DIR)
cmake --build $(BUILD_DIR) --config $(CMAKE_BUILD_TYPE) -j $(JOBS)
cmake --install $(BUILD_DIR)
$(BUILD_DIR)/CMakeCache.txt:
@mkdir -p $(BUILD_DIR)
cmake -S c_src -B $(BUILD_DIR) \
-DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \
-DERTS_INCLUDE_DIR=$(ERTS_INCLUDE_DIR) \
-DPRIV_DIR=$(PRIV_DIR) \
-DCHROMAPRINT_USE_SYSTEM=$(CHROMAPRINT_USE_SYSTEM) \
$(CMAKE_EXTRA_ARGS)
clean:
rm -rf $(BUILD_DIR) $(NIF_SO)
+21
View File
@@ -0,0 +1,21 @@
# Chromaprint
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `chromaprint` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:chromaprint, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/chromaprint>.
+65
View File
@@ -0,0 +1,65 @@
cmake_minimum_required(VERSION 3.10)
project(chromaprint_nif LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(NOT ERTS_INCLUDE_DIR)
message(FATAL_ERROR "ERTS_INCLUDE_DIR is required (path to erts-x.y.z/include)")
endif()
if(NOT PRIV_DIR)
message(FATAL_ERROR "PRIV_DIR is required (mix app priv/ directory)")
endif()
option(CHROMAPRINT_USE_SYSTEM "Link against a system-provided libchromaprint" OFF)
if(CHROMAPRINT_USE_SYSTEM)
find_path(CHROMAPRINT_INCLUDE_DIR chromaprint.h)
find_library(CHROMAPRINT_LIBRARY NAMES chromaprint)
if(NOT CHROMAPRINT_INCLUDE_DIR OR NOT CHROMAPRINT_LIBRARY)
message(FATAL_ERROR
"CHROMAPRINT_USE_SYSTEM=ON but chromaprint.h or libchromaprint not found. "
"Set CHROMAPRINT_INCLUDE_DIR and CMAKE_LIBRARY_PATH (or CMAKE_PREFIX_PATH).")
endif()
message(STATUS "Using system chromaprint: ${CHROMAPRINT_LIBRARY}")
add_library(chromaprint_dep INTERFACE)
target_include_directories(chromaprint_dep INTERFACE "${CHROMAPRINT_INCLUDE_DIR}")
target_link_libraries(chromaprint_dep INTERFACE "${CHROMAPRINT_LIBRARY}")
else()
message(STATUS "Building vendored chromaprint (c_src/chromaprint)")
set(BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_FRAMEWORK OFF CACHE BOOL "" FORCE)
set(FFT_LIB "kissfft" CACHE STRING "" FORCE)
# FindKissFFT.cmake searches CMAKE_SOURCE_DIR/src/3rdparty/kissfft, which would
# resolve to c_src/src/... when chromaprint is added as a subdirectory. Point
# it at the vendored copy inside the submodule.
set(KISSFFT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/chromaprint/src/3rdparty/kissfft"
CACHE PATH "" FORCE)
add_subdirectory(chromaprint EXCLUDE_FROM_ALL)
add_library(chromaprint_dep INTERFACE)
target_link_libraries(chromaprint_dep INTERFACE chromaprint)
target_include_directories(chromaprint_dep INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/chromaprint/src")
endif()
add_library(chromaprint_nif SHARED chromaprint_nif.cpp)
set_target_properties(chromaprint_nif PROPERTIES
PREFIX ""
SUFFIX ".so"
)
target_include_directories(chromaprint_nif PRIVATE "${ERTS_INCLUDE_DIR}")
target_link_libraries(chromaprint_nif PRIVATE chromaprint_dep)
if(APPLE)
set_target_properties(chromaprint_nif PROPERTIES
LINK_FLAGS "-flat_namespace -undefined suppress")
endif()
install(TARGETS chromaprint_nif
LIBRARY DESTINATION "${PRIV_DIR}"
RUNTIME DESTINATION "${PRIV_DIR}")
+282
View File
@@ -0,0 +1,282 @@
#include <chromaprint.h>
#include <erl_nif.h>
#include <cstdint>
#include <cstring>
#include <vector>
namespace {
ErlNifResourceType *CHROMAPRINT_CTX_RES = nullptr;
struct ChromaprintCtxRes {
ChromaprintContext *ctx;
};
ERL_NIF_TERM ATOM_OK;
ERL_NIF_TERM ATOM_ERROR;
ERL_NIF_TERM ATOM_TRUE;
ERL_NIF_TERM ATOM_FALSE;
ERL_NIF_TERM ATOM_BADARG;
ERL_NIF_TERM ATOM_ALLOC_FAILED;
ERL_NIF_TERM ATOM_CHROMAPRINT_FAILED;
ERL_NIF_TERM ATOM_NIL;
inline ERL_NIF_TERM mk_atom(ErlNifEnv *env, const char *name) {
ERL_NIF_TERM atom;
if (enif_make_existing_atom(env, name, &atom, ERL_NIF_LATIN1)) return atom;
return enif_make_atom(env, name);
}
inline ERL_NIF_TERM ok_tuple(ErlNifEnv *env, ERL_NIF_TERM value) {
return enif_make_tuple2(env, ATOM_OK, value);
}
inline ERL_NIF_TERM error_tuple(ErlNifEnv *env, ERL_NIF_TERM reason) {
return enif_make_tuple2(env, ATOM_ERROR, reason);
}
void destruct_ctx(ErlNifEnv * /*env*/, void *obj) {
auto *res = static_cast<ChromaprintCtxRes *>(obj);
if (res->ctx != nullptr) {
chromaprint_free(res->ctx);
res->ctx = nullptr;
}
}
bool get_bool(ErlNifEnv *env, ERL_NIF_TERM term, int *out) {
if (enif_compare(term, ATOM_TRUE) == 0) {
*out = 1;
return true;
}
if (enif_compare(term, ATOM_FALSE) == 0) {
*out = 0;
return true;
}
return false;
}
ERL_NIF_TERM new_context(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
int algorithm;
if (!enif_get_int(env, argv[0], &algorithm)) {
return enif_make_badarg(env);
}
ChromaprintContext *ctx = chromaprint_new(algorithm);
if (ctx == nullptr) {
return error_tuple(env, ATOM_ALLOC_FAILED);
}
auto *res = static_cast<ChromaprintCtxRes *>(
enif_alloc_resource(CHROMAPRINT_CTX_RES, sizeof(ChromaprintCtxRes)));
res->ctx = ctx;
ERL_NIF_TERM term = enif_make_resource(env, res);
enif_release_resource(res);
return ok_tuple(env, term);
}
ERL_NIF_TERM nif_start(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
ChromaprintCtxRes *res;
int sample_rate;
int channels;
if (!enif_get_resource(env, argv[0], CHROMAPRINT_CTX_RES, (void **)&res) ||
!enif_get_int(env, argv[1], &sample_rate) ||
!enif_get_int(env, argv[2], &channels)) {
return enif_make_badarg(env);
}
if (chromaprint_start(res->ctx, sample_rate, channels) != 1) {
return error_tuple(env, ATOM_CHROMAPRINT_FAILED);
}
return ATOM_OK;
}
ERL_NIF_TERM nif_feed(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
ChromaprintCtxRes *res;
ErlNifBinary bin;
if (!enif_get_resource(env, argv[0], CHROMAPRINT_CTX_RES, (void **)&res) ||
!enif_inspect_binary(env, argv[1], &bin)) {
return enif_make_badarg(env);
}
if (bin.size % 2 != 0) {
return error_tuple(env, ATOM_BADARG);
}
const auto *samples = reinterpret_cast<const int16_t *>(bin.data);
const int sample_count = static_cast<int>(bin.size / 2);
if (chromaprint_feed(res->ctx, samples, sample_count) != 1) {
return error_tuple(env, ATOM_CHROMAPRINT_FAILED);
}
return ATOM_OK;
}
ERL_NIF_TERM nif_finish(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
ChromaprintCtxRes *res;
if (!enif_get_resource(env, argv[0], CHROMAPRINT_CTX_RES, (void **)&res)) {
return enif_make_badarg(env);
}
if (chromaprint_finish(res->ctx) != 1) {
return error_tuple(env, ATOM_CHROMAPRINT_FAILED);
}
return ATOM_OK;
}
ERL_NIF_TERM nif_get_fingerprint(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
ChromaprintCtxRes *res;
if (!enif_get_resource(env, argv[0], CHROMAPRINT_CTX_RES, (void **)&res)) {
return enif_make_badarg(env);
}
char *fp = nullptr;
if (chromaprint_get_fingerprint(res->ctx, &fp) != 1 || fp == nullptr) {
if (fp != nullptr) chromaprint_dealloc(fp);
return error_tuple(env, ATOM_CHROMAPRINT_FAILED);
}
const std::size_t len = std::strlen(fp);
ERL_NIF_TERM bin_term;
unsigned char *buf = enif_make_new_binary(env, len, &bin_term);
std::memcpy(buf, fp, len);
chromaprint_dealloc(fp);
return ok_tuple(env, bin_term);
}
ERL_NIF_TERM nif_get_raw_fingerprint(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
ChromaprintCtxRes *res;
if (!enif_get_resource(env, argv[0], CHROMAPRINT_CTX_RES, (void **)&res)) {
return enif_make_badarg(env);
}
uint32_t *fp = nullptr;
int size = 0;
if (chromaprint_get_raw_fingerprint(res->ctx, &fp, &size) != 1 || fp == nullptr) {
if (fp != nullptr) chromaprint_dealloc(fp);
return error_tuple(env, ATOM_CHROMAPRINT_FAILED);
}
std::vector<ERL_NIF_TERM> items;
items.reserve(size);
for (int i = 0; i < size; ++i) {
items.push_back(enif_make_uint(env, fp[i]));
}
chromaprint_dealloc(fp);
ERL_NIF_TERM list =
items.empty() ? enif_make_list(env, 0)
: enif_make_list_from_array(env, items.data(),
static_cast<unsigned>(items.size()));
return ok_tuple(env, list);
}
ERL_NIF_TERM nif_get_fingerprint_hash(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
ChromaprintCtxRes *res;
if (!enif_get_resource(env, argv[0], CHROMAPRINT_CTX_RES, (void **)&res)) {
return enif_make_badarg(env);
}
uint32_t hash = 0;
if (chromaprint_get_fingerprint_hash(res->ctx, &hash) != 1) {
return error_tuple(env, ATOM_CHROMAPRINT_FAILED);
}
return ok_tuple(env, enif_make_uint(env, hash));
}
ERL_NIF_TERM nif_encode_fingerprint(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
unsigned int list_len = 0;
if (!enif_get_list_length(env, argv[0], &list_len)) {
return enif_make_badarg(env);
}
int algorithm;
int base64;
if (!enif_get_int(env, argv[1], &algorithm) || !get_bool(env, argv[2], &base64)) {
return enif_make_badarg(env);
}
std::vector<uint32_t> raw;
raw.reserve(list_len);
ERL_NIF_TERM head;
ERL_NIF_TERM tail = argv[0];
while (enif_get_list_cell(env, tail, &head, &tail)) {
unsigned int val;
if (!enif_get_uint(env, head, &val)) {
return enif_make_badarg(env);
}
raw.push_back(val);
}
char *encoded = nullptr;
int encoded_size = 0;
if (chromaprint_encode_fingerprint(raw.data(), static_cast<int>(raw.size()), algorithm,
&encoded, &encoded_size, base64) != 1 ||
encoded == nullptr) {
if (encoded != nullptr) chromaprint_dealloc(encoded);
return error_tuple(env, ATOM_CHROMAPRINT_FAILED);
}
ERL_NIF_TERM bin_term;
unsigned char *buf =
enif_make_new_binary(env, static_cast<std::size_t>(encoded_size), &bin_term);
std::memcpy(buf, encoded, static_cast<std::size_t>(encoded_size));
chromaprint_dealloc(encoded);
return ok_tuple(env, bin_term);
}
ERL_NIF_TERM nif_decode_fingerprint(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM argv[]) {
ErlNifBinary bin;
int base64;
if (!enif_inspect_binary(env, argv[0], &bin) || !get_bool(env, argv[1], &base64)) {
return enif_make_badarg(env);
}
uint32_t *raw = nullptr;
int size = 0;
int algorithm = 0;
if (chromaprint_decode_fingerprint(reinterpret_cast<const char *>(bin.data),
static_cast<int>(bin.size), &raw, &size, &algorithm,
base64) != 1 ||
raw == nullptr) {
if (raw != nullptr) chromaprint_dealloc(raw);
return error_tuple(env, ATOM_CHROMAPRINT_FAILED);
}
std::vector<ERL_NIF_TERM> items;
items.reserve(size);
for (int i = 0; i < size; ++i) {
items.push_back(enif_make_uint(env, raw[i]));
}
chromaprint_dealloc(raw);
ERL_NIF_TERM list =
items.empty() ? enif_make_list(env, 0)
: enif_make_list_from_array(env, items.data(),
static_cast<unsigned>(items.size()));
ERL_NIF_TERM pair = enif_make_tuple2(env, enif_make_int(env, algorithm), list);
return ok_tuple(env, pair);
}
ERL_NIF_TERM nif_version(ErlNifEnv *env, int /*argc*/, const ERL_NIF_TERM * /*argv*/) {
const char *v = chromaprint_get_version();
if (v == nullptr) return ATOM_NIL;
const std::size_t len = std::strlen(v);
ERL_NIF_TERM bin_term;
unsigned char *buf = enif_make_new_binary(env, len, &bin_term);
std::memcpy(buf, v, len);
return bin_term;
}
int load(ErlNifEnv *env, void ** /*priv_data*/, ERL_NIF_TERM /*load_info*/) {
CHROMAPRINT_CTX_RES = enif_open_resource_type(env, nullptr, "ChromaprintContext",
destruct_ctx, ERL_NIF_RT_CREATE, nullptr);
if (CHROMAPRINT_CTX_RES == nullptr) return -1;
ATOM_OK = mk_atom(env, "ok");
ATOM_ERROR = mk_atom(env, "error");
ATOM_TRUE = mk_atom(env, "true");
ATOM_FALSE = mk_atom(env, "false");
ATOM_BADARG = mk_atom(env, "badarg");
ATOM_ALLOC_FAILED = mk_atom(env, "alloc_failed");
ATOM_CHROMAPRINT_FAILED = mk_atom(env, "chromaprint_failed");
ATOM_NIL = mk_atom(env, "nil");
return 0;
}
ErlNifFunc funcs[] = {
{"new_context", 1, new_context, 0},
{"start", 3, nif_start, 0},
{"feed", 2, nif_feed, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"finish", 1, nif_finish, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"get_fingerprint", 1, nif_get_fingerprint, 0},
{"get_raw_fingerprint", 1, nif_get_raw_fingerprint, 0},
{"get_fingerprint_hash", 1, nif_get_fingerprint_hash, 0},
{"encode_fingerprint", 3, nif_encode_fingerprint, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"decode_fingerprint", 2, nif_decode_fingerprint, ERL_NIF_DIRTY_JOB_CPU_BOUND},
{"version", 0, nif_version, 0},
};
} // namespace
ERL_NIF_INIT(Elixir.Chromaprint.NIF, funcs, load, nullptr, nullptr, nullptr)
Generated
+26
View File
@@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1774386573,
"narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+30
View File
@@ -0,0 +1,30 @@
{
inputs = { nixpkgs.url = "nixpkgs/nixos-unstable"; };
outputs = { self, nixpkgs }: {
devShell.x86_64-linux = let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
beam = pkgs.beam.packages.erlang_28;
elixir = beam.elixir_1_20;
elixir-ls = (beam.elixir-ls.override { inherit elixir; });
in pkgs.mkShell {
buildInputs = [
elixir
elixir-ls
pkgs.cmake
pkgs.gnumake
pkgs.git
];
shellHook = ''
mkdir -p .state/mix .state/hex
export MIX_HOME=$PWD/.state/mix
export HEX_HOME=$PWD/.state/hex
export PATH=$MIX_HOME/bin:$MIX_HOME/escripts:$HEX_HOME/bin:$PATH
mix local.hex --if-missing --force
export LANG=en_US.UTF-8
export ERL_AFLAGS="-kernel shell_history enabled -kernel shell_history_path '\"$PWD/.state\"' -kernel shell_history_file_bytes 1024000"
'';
};
};
}
+70
View File
@@ -0,0 +1,70 @@
defmodule Chromaprint.MixProject do
use Mix.Project
def project do
[
app: :chromaprint,
version: "0.1.0",
elixir: "~> 1.20-rc",
start_permanent: Mix.env() == :prod,
deps: deps(),
compilers: [:elixir_make] ++ Mix.compilers(),
make_targets: ["all"],
make_clean: ["clean"],
make_env: &make_env/0
]
end
def application do
[
extra_applications: [:logger]
]
end
defp deps do
[
{:elixir_make, "~> 0.9", runtime: false},
]
end
defp make_env do
verify_chromaprint_source!()
%{
"ERTS_INCLUDE_DIR" => erts_include_dir(),
"CHROMAPRINT_USE_SYSTEM" =>
if(System.get_env("CHROMAPRINT_USE_SYSTEM") in ~w(1 true on ON YES yes),
do: "ON",
else: "OFF"
)
}
end
defp erts_include_dir do
Path.join([
:code.root_dir() |> to_string(),
"erts-#{:erlang.system_info(:version)}",
"include"
])
end
defp verify_chromaprint_source! do
use_system = System.get_env("CHROMAPRINT_USE_SYSTEM") in ~w(1 true on ON YES yes)
submodule_cmake = Path.join([__DIR__, "c_src", "chromaprint", "CMakeLists.txt"])
if not use_system and not File.exists?(submodule_cmake) do
Mix.raise("""
The chromaprint source submodule is missing at c_src/chromaprint/.
Either initialise it:
git submodule update --init c_src/chromaprint
Or, to link against a system-provided libchromaprint (recommended for
Nix and other sandboxed builds), set CHROMAPRINT_USE_SYSTEM=1 and ensure
chromaprint.h and libchromaprint can be found via the compiler's default
paths (or CHROMAPRINT_INCLUDE_DIR / CHROMAPRINT_LIB_DIR if not).
""")
end
end
end
+3
View File
@@ -0,0 +1,3 @@
%{
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
}