#include #include #include #include #include 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(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( 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(bin.data); const int sample_count = static_cast(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 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(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 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(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(encoded_size), &bin_term); std::memcpy(buf, encoded, static_cast(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(bin.data), static_cast(bin.size), &raw, &size, &algorithm, base64) != 1 || raw == nullptr) { if (raw != nullptr) chromaprint_dealloc(raw); return error_tuple(env, ATOM_CHROMAPRINT_FAILED); } std::vector 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(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)