Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## v0.22.0

### Added

- **Seed lists on `similar` and `sfxSimilar`** — pass a list instead of one asset (`AudioScape:similar(playlist.tracks, { limit = 20 })`, or `{ asset_ids = { ... }, limit = 20 }`) and results sound like the set as a whole rather than any one track: playlist continuation for music, variety packs for SFX. Each element can be anything the single-seed form accepts (asset_id string, Track, `Sound`, `AudioPlayer`); up to 100 seeds; seeds are never returned. `result.meta.seeds_used` counts the seeds that resolved and `result.meta.seeds_ignored` lists the ids skipped (unknown, not yet analyzed, or not public). Available through `AudioScapeClient.similar` / `sfxSimilar` too.

### Fixed

- **`AudioScapeClient.similar` and `sfxSimilar` now honour `max_score` and `dedupe`.** The client methods sent both, but the server-side remotes dropped them before calling the API, so near-duplicate filtering never applied to client-initiated calls.

## v0.21.0

### Changed
Expand Down
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Add to your `wally.toml`:

```toml
[server-dependencies]
AudioScape = "this-fifo/audioscape-sdk@0.21.0"
AudioScape = "this-fifo/audioscape-sdk@0.22.0"
```

Then run:
Expand Down Expand Up @@ -168,7 +168,8 @@ local result, err = AudioScape:lookup({

Find tracks that sound similar to a given track. `input` is anything that
carries an asset_id — a string, a Track from a previous response, a `Sound`,
or an `AudioPlayer`.
or an `AudioPlayer` — or a list of those (up to 100), in which case results
sound like the set as a whole.

```lua
local result, err = AudioScape:similar({
Expand All @@ -188,6 +189,13 @@ local result = AudioScape:similar(playlist.tracks[1], { limit = 10 })

-- Shorthand: pass a playing Sound directly
local result = AudioScape:similar(soundInstance)

-- Playlist continuation: pass a list (strings, Tracks, Sounds) and get
-- tracks that sound like the whole set. Seeds are never returned; page with
-- `offset` to build the next playlist.
local result = AudioScape:similar(playlist.tracks, { limit = 20 })
-- result.meta.seeds_used -- seeds that resolved and were blended
-- result.meta.seeds_ignored -- ids skipped: unknown, not yet analyzed, or not public
```

### `AudioScape:browse(options)`
Expand Down Expand Up @@ -291,7 +299,8 @@ local result, err = AudioScape:sfxSearch({
### `AudioScape:sfxSimilar(input, extras?)`

Find sound effects acoustically similar to a given asset. Same polymorphic
input as `AudioScape:similar`.
input as `AudioScape:similar`, including a list of seeds to grow a variety
pack from the clips you already picked.

```lua
local result, err = AudioScape:sfxSimilar({
Expand All @@ -308,6 +317,9 @@ local result, err = AudioScape:sfxSimilar({

-- Or pass an SfxTrack / Sound / asset_id string:
local result = AudioScape:sfxSimilar(sfx.tracks[1], { limit = 10 })

-- Or a list of seeds — results sound like the set as a whole:
local result = AudioScape:sfxSimilar({ "9083837523", "18672006515" }, { limit = 10 })
```

### `AudioScape:getSfxTaxonomy()`
Expand Down
69 changes: 57 additions & 12 deletions src/AudioScapeClient.luau
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ end
-- beyond asset_id) typecheck under strict mode.
export type AssetIdResolvable = string | { asset_id: string, [string]: any } | Sound | AudioPlayer

-- Seed-list forms of similar / sfxSimilar (mirrors src/init.luau): an array
-- of AssetIdResolvable, or an options table carrying `asset_ids`.
export type SeedList = { AssetIdResolvable }
export type SimilarInput = AssetIdResolvable | SeedList | { asset_ids: SeedList, [string]: any }

type AudioScapeClientInstance = typeof(setmetatable(
{} :: {
-- Optional because the module itself starts unresolved and fills this
Expand Down Expand Up @@ -110,6 +115,35 @@ local function resolveAssetId(input: any): string?
return nil
end

-- Mirrored from src/init.luau (see resolveSeedIds there): (nil, nil) when
-- `input` is not a seed list, (nil, reason) when an element doesn't resolve,
-- (ids, nil) otherwise.
local function resolveSeedIds(input: any): ({ string }?, string?)
if type(input) ~= "table" or input.asset_id ~= nil then
return nil, nil
end
local list: any
if type(input.asset_ids) == "table" then
list = input.asset_ids
elseif #input > 0 then
list = input
else
return nil, nil
end
local ids: { string } = {}
for i, item in ipairs(list) do
local id = resolveAssetId(item)
if not id then
return nil, string.format("asset_ids[%d] is not an asset_id string, a track table, a Sound, or an AudioPlayer", i)
end
table.insert(ids, id)
end
if #ids == 0 then
return nil, "asset_ids must be a non-empty list"
end
return ids, nil
end

-- Search the catalog. Pass `query` for free-text/natural-language search, or
-- `asset_ids` for a direct batch lookup of up to 100 IDs (filters bypassed,
-- results in input order, missing IDs surfaced in `meta.missing_ids`).
Expand Down Expand Up @@ -138,13 +172,14 @@ function AudioScapeClient.lookup(self: AudioScapeClientInstance, options: { asse
end

-- Find tracks that sound similar to a given track. Polymorphic input —
-- pass an asset_id string, a Track table, a Sound, or an AudioPlayer; pass
-- `extras` when you want limit/offset/filters and didn't use the legacy
-- options-table form. `max_score`/`dedupe` filter near-duplicate matches
-- of the seed track from the results.
-- pass an asset_id string, a Track table, a Sound, or an AudioPlayer, or a
-- list of those to get tracks that sound like the whole set; pass `extras`
-- when you want limit/offset/filters and didn't use the legacy options-table
-- form. `max_score`/`dedupe` filter near-duplicate matches of the seed track
-- from the results.
function AudioScapeClient.similar(
self: AudioScapeClientInstance,
input: AssetIdResolvable,
input: SimilarInput,
extras: {
limit: number?,
offset: number?,
Expand All @@ -153,13 +188,18 @@ function AudioScapeClient.similar(
dedupe: boolean?,
}?
): (any, string?)
local assetId = resolveAssetId(input)
if not assetId then
return nil, "similar requires an asset_id string, a track table, a Sound, or an AudioPlayer"
local seedIds, seedErr = resolveSeedIds(input)
if seedErr then
return nil, "similar " .. seedErr
end
local assetId = if seedIds then nil else resolveAssetId(input)
if not seedIds and not assetId then
return nil, "similar requires an asset_id string, a track table, a Sound, or an AudioPlayer, or a list of them"
end
local opts: any = extras or (type(input) == "table" and input or {})
return invoke(getRemotes(self), "Similar", {
asset_id = assetId,
asset_ids = seedIds,
limit = opts.limit,
offset = opts.offset,
filters = opts.filters,
Expand All @@ -182,7 +222,7 @@ end
-- SFX libraries with many uploads of the same generic clip.
function AudioScapeClient.sfxSimilar(
self: AudioScapeClientInstance,
input: AssetIdResolvable,
input: SimilarInput,
extras: {
limit: number?,
offset: number?,
Expand All @@ -191,13 +231,18 @@ function AudioScapeClient.sfxSimilar(
dedupe: boolean?,
}?
): (any, string?)
local assetId = resolveAssetId(input)
if not assetId then
return nil, "sfxSimilar requires an asset_id string, a track table, a Sound, or an AudioPlayer"
local seedIds, seedErr = resolveSeedIds(input)
if seedErr then
return nil, "sfxSimilar " .. seedErr
end
local assetId = if seedIds then nil else resolveAssetId(input)
if not seedIds and not assetId then
return nil, "sfxSimilar requires an asset_id string, a track table, a Sound, or an AudioPlayer, or a list of them"
end
local opts: any = extras or (type(input) == "table" and input or {})
return invoke(getRemotes(self), "SfxSimilar", {
asset_id = assetId,
asset_ids = seedIds,
limit = opts.limit,
offset = opts.offset,
filters = opts.filters,
Expand Down
Loading