API reference
Collections and I/O
Collections, albums, tracks—the folder-shaped data model.
A collection is a folder tree of audio files. Every directory that
directly contains audio becomes an album (named by its path relative to
the root; files sitting in the root itself form the album "."), and
every audio file a track named by its stem. Metadata tags are deliberately
not required—the folder structure people already keep their music in is
the ground truth here; the optional [tags] extra is reserved for
metadata enrichment.
Track
dataclass
One audio file: its path and the album it belongs to.
Source code in src/musiscape/io.py
29 30 31 32 33 34 35 36 37 38 | |
title
property
Track title, taken from the filename stem.
Album
dataclass
One folder of tracks, named by its path relative to the root.
Source code in src/musiscape/io.py
41 42 43 44 45 | |
Collection
dataclass
A scanned folder tree: the root path and its albums.
Source code in src/musiscape/io.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
tracks
property
Flat list of every track across all albums.
album_names
property
Album names in scan (sorted-path) order.
open_collection(root)
Scan a folder tree into a Collection (albums sorted, tracks sorted).
Source code in src/musiscape/io.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
recording_start_time(path)
When a recording started, as a naive local datetime, or None.
Read from the container's creation_time where there is one, else
from a timestamp in the filename. Video containers store that tag in
UTC, so it is converted to the local zone: what makes a session clock
readable is the wall time of the room, not of Greenwich.
Returns None rather than guessing when neither is present.
Source code in src/musiscape/io.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
album_stem(album)
Filename stem for a per-album output file.
Audio sitting in the collection root forms the album ".". Naming a
file after it directly gives ..png, which Path reads as a dotfile
with no suffix and PIL refuses to save, or ". medley.wav", which is
hidden on every Unix desktop. Leading dots are therefore dropped.
Source code in src/musiscape/io.py
128 129 130 131 132 133 134 135 136 137 | |
list_recordings(root, exclude=())
Recordings under root, in name order, which is playing order.
Cameras number their files sequentially, so sorting by name puts a split concert back in the order it was played. A folder whose files are named otherwise needs the order fixed by renaming.
exclude names folders to skip. The concert tools write audio into
an output folder that normally sits inside the input folder, so without
this a second run would read the first run's songs back as recordings.
Source code in src/musiscape/io.py
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | |
load(track, sr=22050, duration=None)
Load a track as mono float audio at sr (librosa's decoders).
Source code in src/musiscape/io.py
163 164 165 166 167 | |
load_recording(path, sr=22050, offset=0.0, duration=None)
Load any recording, audio file or video container, as mono float.
Audio goes through librosa as everywhere else. Video is decoded by ffmpeg, which is not a package dependency: it is asked for only when a video file is actually handed over, and its absence is reported as a missing program rather than a decode failure.
Source code in src/musiscape/io.py
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | |
load_stereo(track, sr=22050, duration=None)
Load a track as a (2, n) stereo pair; mono files are duplicated.
Source code in src/musiscape/io.py
204 205 206 207 208 209 210 211 212 213 | |
Concerts & long recordings
Songs out of a continuous recording: the concert, not the collection.
The rest of musiscape assumes one file is one track. A concert is the other shape: one long recording, often several when the camera split at a file size limit, holding a sequence of songs separated by applause, tuning and talk. This module finds those songs so the collection tools can be pointed at them.
What separates a song from the space around it is not level. An enthusiastic room is as loud as the band. It is spectral flatness: applause is broadband noise and measures flat, while played music is tonal and measures peaked, typically an order of magnitude lower. The split between the two is taken from each recording's own distribution rather than from a fixed number, because the ratio survives across rooms and microphones while the absolute values do not.
music_mask(y, sr, hop_s=1.0)
Per-hop_s boolean: is this frame played music?
Frames are kept when they are both tonal (spectral flatness below a
threshold taken from the recording's own bimodal distribution) and
audible (within :data:LEVEL_FLOOR_DB of the recording's loud frames).
When the flatness distribution has only one mode, as in a recording
that is music throughout or applause throughout, the flatness test is
dropped rather than invented and level alone decides.
Source code in src/musiscape/concert.py
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | |
segments(mask, hop_s=1.0, min_song_s=60.0, min_gap_s=MIN_GAP_S)
Turn a music mask into song spans.
Runs of music separated by less than min_gap_s are one song, since
a quiet bar or a held breath is not the end of a piece. Spans shorter
than min_song_s are not songs at all, which keeps tuning, a spoken
introduction over a held chord, and a false start out of the listing.
Returns one dict per song with start_s, end_s and
duration_s, in time order.
Source code in src/musiscape/concert.py
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
find_songs(paths, sr=22050, hop_s=1.0, min_song_s=60.0, min_gap_s=MIN_GAP_S, join_tol_s=JOIN_TOL_S)
Locate the songs across an ordered sequence of recording files.
paths must be in playing order. Times are reported on a concert
clock that runs from the start of the first file and treats the files
as butted together: a camera that stops and restarts loses a few
seconds at each join, and that loss is not recoverable from the audio,
so the clock drifts behind wall time by however long the changeovers
took. Within a song, parts carries the true offsets into each
source file, which is what the clips are cut from.
A span reaching the end of one file and resuming at the start of the next is one song, because the camera splits at a size limit rather than at a musical boundary. The minimum-length test is applied after that join, so a song cut ten seconds before its end is not discarded as a fragment.
Source code in src/musiscape/concert.py
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | |
split_recording(paths, out_dir, sr=22050, write_sr=44100, hop_s=1.0, min_song_s=60.0, min_gap_s=MIN_GAP_S, join_tol_s=JOIN_TOL_S)
Cut a concert into one audio file per song, plus a manifest.
Writes <out_dir>/songs/NN-<source>-<mmss>.flac and returns the path
to <out_dir>/songs.json. The songs folder is an ordinary musiscape
collection: point report, thumbnails or any other verb at it.
Detection runs at sr; the clips are written at write_sr so they
stay worth listening to when you check a boundary by ear.
Source code in src/musiscape/concert.py
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | |
region_features(y, sr, hop_s=1.0)
Per-hop_s level, flatness, flatness variability and centroid.
Source code in src/musiscape/concert.py
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 | |
classify_regions(y, sr, hop_s=1.0)
Label every hop_s frame with one of :data:REGION_CLASSES.
Music must pass :func:music_mask and be tonal in absolute terms, since
that function compares a recording against itself and has no way to tell
an all-applause recording from an all-music one. The rest is sorted by
flatness and by how much that flatness moves.
other is not a dustbin for what is left over; it is what the frame
gets when it is audible but matches no class cleanly, and it should be
read as the classifier declining to guess.
Source code in src/musiscape/concert.py
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | |
regions(labels, hop_s=1.0, min_s=5.0)
Merge a label sequence into spans, dropping flickers.
A span shorter than min_s is absorbed into whichever neighbour it
interrupts, because a second or two of a different label mid-song is the
classifier wobbling rather than an event in the hall.
Source code in src/musiscape/concert.py
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 | |
map_regions(paths, sr=22050, hop_s=1.0, min_s=5.0, songs=None, method='heuristic', device=None)
Label a whole concert, on the concert clock.
Returns {"spans": [...], "total_s": float, "level_db": [...]}, where
the spans run continuously from the first file to the last and the level
is one value per hop_s. Files are butted together exactly as
:func:find_songs butts them, so the two share a clock.
Pass songs (what :func:find_songs returned) to let the setlist
decide where the music is. It bridges a gap of a few seconds mid-song
and the frame classifier does not, so without this a single song with a
quiet bar in it is drawn as two or three. Everything outside the songs
is still classified frame by frame.
method="panns" labels from AudioSet posteriors instead of spectral
flatness (:mod:musiscape.tagging, needs ambiscape[ml]): slower, and
right about loud rock and noise music where the heuristic is not. Music
edges are then snapped to songs rather than replaced by them, and the
onsets are refined to where the sound starts.
Source code in src/musiscape/concert.py
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 | |
export_regions(paths, out_dir, spans, sr=44100, exclude=('music',), hop_s=1.0, start_time=None, suffix='.flac')
Write every non-music span as its own file, for a soundscape tool.
musiscape describes music; what happens between the songs is a
soundscape question, and ambiscape is the toolbox for those. The two
meet at the file boundary rather than by importing one another, so this
writes an ordinary folder of WAVs that ambiscape analyze reads as
one session.
Spans are cut from the concert clock, so one crossing a file boundary is assembled from both files. FLAC by default: lossless, about half the size of WAV, and read natively by the tools on both sides.
start_time puts the recording's wall clock into the filenames, which
is what lets the other tool lay the evening out on a timeline instead of
stacking every span at the same second.
Source code in src/musiscape/concert.py
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | |
Feature extraction
Per-track feature extraction—the interpretable descriptor set.
Every number here has a musicological reading: note density (plucked events
per second), brightness (spectral centroid), inharmonic texture (spectral
flatness), dynamic range, harmonic/percussive balance, estimated key
(Krumhansl–Schmuckler), pitch-class entropy, pulse clarity and tonal focus
(circular statistics via :mod:micromotion.circular), and a Schaeffer TARTYP
object profile. The set is deliberately small enough to explain; it does
not compete with embedding models on raw similarity.
extract_collection caches to features.json in the output folder and
runs tracks in parallel; delete the file to force re-extraction.
Two descriptors answer even when there is nothing to answer, and both must be gated before use. This matters whenever the input is not a music collection: field recordings, broadcast audio, anything where "music" was decided by a detector rather than by a track listing.
tempo_bpm is librosa's prior-based estimator and it has no failure value:
given an onset envelope with no periodicity it returns the tempogram bin
nearest its 120 BPM prior. White noise returns 123.05 BPM, reproducibly. On
704 five-minute spans of domestic television audio it returned five distinct
values in all, 93 % of them exactly 123.0, and the other four were the
adjacent grid points, a result indistinguishable from noise. Read pulse_R
first: below about 0.1 there is no pulse for a tempo to describe, and
tempo_bpm is reporting the prior rather than the track.
key and key_conf degrade the same way. The Krumhansl--Schmuckler
correlation is taken against whatever chroma vector arrives, including a
near-uniform one. On the same material chroma_entropy sat at a median
3.541 against a maximum of log2(12) = 3.585, with 80 % of spans within 2 % of
that ceiling: no tonal centre exists, so the estimate falls to whichever tiny
bias survives, and it does so consistently: 78 % of spans came back minor and
one key took a quarter of them. Consistency is not confidence here. Read
chroma_entropy first; near the ceiling the key is an artefact, and
splitting by key_conf will not reveal it, because the artefact is
confident.
The spectral and temporal descriptors are unaffected and stay usable on such material: onset rate, centroid, flatness, zero-crossing rate, percussive ratio and dynamic range all varied normally on the same spans.
Both gates are whole-track averages, which is a second way to be wrong. They
catch a descriptor answering about noise, but they do not distinguish that
from a descriptor answering about four minutes of real music at too long a
timescale. On live material the second case is the common one: a band
drifting a few BPM collapses pulse_R while playing a steady beat, and a
full band in a reverberant room flattens mean chroma far past a threshold
calibrated on solo instrumental recordings.
:mod:musiscape.stability measures the same two quantities per window and
reports how far the windows agree, which separates the cases. Its results
travel beside the gated numbers here: key_agreement and key_windowed
beside key, tempo_agreement and tempo_windowed_bpm beside
tempo_bpm. They answer whether an estimate holds still across the track.
Nothing answers whether a track has a pulse at all; see that module.
feats_2hz(y, sr)
Chroma, MFCC and RMS aggregated to 2 Hz frames.
Shared by the visual cards and the sonic thumbnails. It lives here
rather than in :mod:thumbnails so that :mod:sonic, which has no
visual output, need not import the plotting stack to use it.
Source code in src/musiscape/features.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | |
estimate_key(chroma_mean)
Krumhansl–Schmuckler key estimate (name, correlation).
Source code in src/musiscape/features.py
103 104 105 106 107 108 109 110 111 | |
extract_track(y, sr)
All per-track descriptors from decoded audio.
Source code in src/musiscape/features.py
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
extract_collection(coll, out_dir, sr=22050, duration=None, workers=4, force=False, retry_cap_s=600.0)
Extract every track (parallel, cached) → <out_dir>/features.json.
Tracks whose worker dies without raising, an out-of-memory kill on a
very long track being the case seen in practice, are retried one at a
time in their own process, with the analysis window capped to
retry_cap_s seconds so the retry fits in memory. A capped result
records analysis_capped_s so the shortened window is visible in the
output rather than implied by a duration. Pass retry_cap_s=None to
retry at full length, which will usually be killed again.
Nothing is capped on the first attempt, so ordinary collections are extracted exactly as before.
Source code in src/musiscape/features.py
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | |
load_features(path)
Read a features.json produced by :func:extract_collection.
Source code in src/musiscape/features.py
302 303 304 | |
Time-course of a recording
How a recording changes over its own length: pitch, harmony, pulse and timbre, second by second.
:mod:features describes a track by one number per descriptor, and :mod:stability asks
whether that number holds still. This module gives the time-course itself, at one frame per
second, for a recording that is meant to change: a concert, a long improvisation, a session in
which the interesting question is not "what key is it in" but "when does it move, and in what".
Everything here is per second, on one clock, so the arrays can sit beside a motion or gaze track from another toolbox and be correlated, segmented and drawn on the same width. The descriptors:
- Chroma of the harmonic component (HPSS first, so a piano's attacks and a room's noise do not smear the pitch classes), normalised per second; chroma entropy as harmonic complexity; tonal clarity as how far one pitch class stands out.
- Key in overlapping windows by correlation with the Krumhansl–Kessler profiles, reported as a list of (time, key, correlation), because a key that changes every window is a statement about the music (modal, wandering) and not a failure.
- Harmonic change as the tonnetz distance between consecutive seconds (Harte's HCDF).
- Tempogram (onset-strength autocorrelation) with its per-second argmax as local tempo
and its peak-to-mean ratio as pulse clarity; a windowed beat-tracked tempo alongside. On
non-metric music these are honest reports of the absence of a pulse, and the module does not
pretend otherwise: read
pulse_claritybefore readinglocal_tempo. - Timbre: MFCCs, spectral centroid, flatness, and the harmonic share from HPSS; timbre novelty from a Foote kernel on the MFCC self-similarity.
- Register as the energy-weighted MIDI pitch of the harmonic constant-Q spectrum, with its spread, so "the violin went up" is a number.
:func:section_summary folds the time-course into one row per section for a table, and
:func:timecourse_figures draws the three standard pictures (chromagram, tempogram,
time-course). The functions take audio, not a track, so they work on a cut of a concert as
readily as on a file. The time-course of the live-painting concert this was written for showed a
music organised around pedal points rather than progressions, which the whole-track key estimate
had hidden behind a single "C minor".
key_from_chroma(chroma_mean)
The best-matching key for a 12-bin chroma vector, and its profile correlation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chroma_mean
|
Twelve values, C first. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
str
|
|
float
|
the 24 rotated profiles. A flat chroma gives a low |
Source code in src/musiscape/timecourse.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
foote_novelty(X, half_window)
Foote (2000) novelty of a (time, features) matrix with a checkerboard kernel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X
|
ndarray
|
Standardised features, one row per frame. |
required |
half_window
|
int
|
Kernel half-size in frames. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
numpy.ndarray: Novelty per frame, scaled to a maximum of 1; zero within |
ndarray
|
|
Source code in src/musiscape/timecourse.py
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | |
music_timecourse(y, sr, hop=512, key_window_s=30.0, key_step_s=10.0, tempo_window_s=30.0, tempo_range=(40.0, 200.0), novelty_half_window_s=30.0)
The per-second time-course of a recording's pitch, harmony, pulse and timbre.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
Mono audio. |
required | |
sr
|
int
|
Sample rate. |
required |
hop
|
int
|
STFT hop in samples for the underlying frame analysis. Defaults to 512. |
512
|
key_window_s
|
float
|
Window for the key estimates. Defaults to 30 s. |
30.0
|
key_step_s
|
float
|
Step between key windows. Defaults to 10 s. |
10.0
|
tempo_window_s
|
float
|
Window for the beat-tracked tempo. Defaults to 30 s. |
30.0
|
tempo_range
|
tuple
|
BPM range considered for local tempo and pulse clarity. |
(40.0, 200.0)
|
novelty_half_window_s
|
float
|
Half-size of the timbre-novelty kernel in seconds. |
30.0
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
|
dict
|
|
|
dict
|
|
|
dict
|
|
|
dict
|
|
|
dict
|
|
Source code in src/musiscape/timecourse.py
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
section_summary(tc, boundaries)
One row per section between boundaries (seconds): key, tempo, harmony, timbre, register.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tc
|
dict
|
From :func: |
required |
boundaries
|
Section boundary times in seconds; the recording's start and end are added. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
list |
list[dict]
|
Dicts with |
list[dict]
|
|
|
list[dict]
|
|
Source code in src/musiscape/timecourse.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 | |
timecourse_figures(tc, out_dir, prefix='', title='', boundaries=())
Write the chromagram, tempogram and time-course figures, and the raw strips.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tc
|
dict
|
From :func: |
required |
out_dir
|
Folder for the PNGs. |
required | |
prefix
|
str
|
Filename prefix. |
''
|
title
|
str
|
Figure title prefix. |
''
|
boundaries
|
Section boundaries to draw as vertical lines. |
()
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
Paths of the files written ( |
dict
|
|
Source code in src/musiscape/timecourse.py
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | |
Foreground proxies
Which sound is in front, second by second, when there is no multitrack.
A concert recorded from one microphone gives one mixture. When the question is "what did the painter do when the violin led, and when the electronics led", the honest answer without stems is a set of proxies: envelopes that track the kind of sound each instrument makes rather than the instrument itself. This module computes three such envelopes per second and says plainly what each one is.
- pitched --- energy of the harmonic component (HPSS) between
pitched_band, weighted by the voicing probability of a pitch tracker (pYIN) run on that component. A bowed string, a voice or a wind instrument playing notes scores high; a synthesiser drone with a clear pitch scores high too, which is why this is a proxy and not a separation. - low --- energy below
low_hz. Bass, sub-bass and the weight of a PA. - noise --- the percussive/residual component's energy plus the spectral flatness of the mixture: attacks, noise textures, applause, brushes on canvas.
Each envelope is scaled to its own 99th percentile, and :func:foreground_labels turns the three
into one label per second ("pitched", "low", "noise", "mixed" or "quiet")
with a margin, so a second is labelled only when one proxy clearly leads. On the live-painting
concert this was written for, the pitched proxy followed the violin and the low+noise proxies
the electronics; that reading was checked against the photographs and the AudioSet tagger, not
assumed. Check yours the same way before calling a proxy an instrument.
instrument_foreground(y, sr, hop=512, pitched_band=(180.0, 4000.0), low_hz=180.0, fmin=150.0, fmax=3000.0)
Per-second proxies for pitched, low and noise-like foreground.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
Mono audio. |
required | |
sr
|
int
|
Sample rate. |
required |
hop
|
int
|
Hop for the frame analysis. Defaults to 512. |
512
|
pitched_band
|
tuple
|
Frequency band, Hz, of the harmonic energy behind |
(180.0, 4000.0)
|
low_hz
|
float
|
Upper edge of the |
180.0
|
fmin, fmax
|
float
|
Pitch-tracker range in Hz. Defaults to 150–3000 Hz. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
|
dict
|
its 99th percentile), |
|
dict
|
(median voiced pitch per second, NaN where unvoiced) and |
Source code in src/musiscape/foreground.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | |
foreground_labels(fg, margin=0.15, quiet_db=-55.0)
One label per second from the three proxies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fg
|
dict
|
From :func: |
required |
margin
|
float
|
How far the leading proxy must exceed the runner-up. Defaults to 0.15. |
0.15
|
quiet_db
|
float
|
Seconds below this level are |
-55.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
numpy.ndarray: Strings |
Source code in src/musiscape/foreground.py
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | |
Piano transcription
Notes, not onsets: piano transcription for the analyses that need to know what was played.
Everything else in this toolbox works from the signal. For a piano recording that is a loss: an onset detector fires on attacks without saying how many notes, at what pitch, how loud, and it fires on the pianist's chair as readily as on a chord. This module transcribes the piano part to note events --- onset, offset, MIDI pitch, velocity --- with the high-resolution transcription model of Kong et al. (2021), which runs on a CPU at a few times real time and was trained on solo piano. It is only for piano, and only for recordings in which the piano dominates; on a mixture it returns the notes it believes it hears, which may be many.
Two products follow from the notes. :func:notes_per_second folds them onto the one-second
clock the rest of the toolbox uses (density, mean pitch, mean velocity, pitch spread), so
they sit beside a motion or gaze track. The note onsets themselves are the events that
MGT-python's event_alignment tests strokes and gestures against; on the painter--pianist
session this was written for, the transcription found 58, 260 and 300 notes per minute in the
three takes where the onset detector had found 32, 78 and 83, because chords and fast
passages had been merged into single onsets.
The model is an optional dependency: pip install "musiscape[transcribe]". Its checkpoint
(about 170 MB) is downloaded on first use to ~/piano_transcription_inference_data.
transcription_available()
Whether the optional transcription package is installed.
Source code in src/musiscape/transcribe.py
32 33 34 35 36 37 38 | |
transcribe_piano(y, sr, midi_path=None, device='cpu', threads=None)
Transcribe a piano recording to note events.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
Mono audio. |
required | |
sr
|
int
|
Its sample rate; resampled to the model's 16 kHz. |
required |
midi_path
|
optional
|
Where to write a MIDI file of the result. Defaults to none. |
None
|
device
|
str
|
|
'cpu'
|
threads
|
int
|
Torch threads to use. Defaults to torch's choice. |
None
|
Returns:
| Type | Description |
|---|---|
|
pandas.DataFrame: One row per note with |
|
|
|
Source code in src/musiscape/transcribe.py
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | |
notes_per_second(notes, duration_s, bin_s=1.0)
Fold note events onto a per-bin clock.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
notes
|
The table from :func: |
required | |
duration_s
|
float
|
Length of the recording. |
required |
bin_s
|
float
|
Bin width in seconds. Defaults to 1.0. |
1.0
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
|
dict
|
|
|
dict
|
within the bin, NaN with fewer than two notes), |
|
dict
|
bin, counting offsets). |
Source code in src/musiscape/transcribe.py
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
Estimate stability
Does an estimate hold up across the track, or only on average?
:mod:features gates two descriptors on whole-track statistics: near-uniform
chroma makes key an artefact, and an onset envelope with no periodicity
makes tempo_bpm a report of librosa's prior. Both gates catch real
failures. Both are also averages over the whole track, which is a second way
to be wrong: not by measuring noise, but by measuring something real over too
long a window.
Live music is where the difference shows. pulse_R folds an entire take at
one global period, so a band that drifts a few BPM across four minutes
collapses the resultant while playing a steady beat. chroma_entropy
averages chroma over the whole take, and a full band in a reverberant room
flattens that average far past a threshold calibrated on solo instrumental
material.
This module measures the same two quantities per window and reports how far the windows agree. High agreement on a gated track means the gate was too coarse for the material. Low agreement means the track really does wander, which is worth knowing and is a different statement from "unmeasurable".
The question answered is therefore narrow and answerable: not "is there a
pulse" but "does the estimate hold still". No descriptor here reports whether
a track has a beat at all, because on real material no statistic of
periodicity can tell one. Applause is rhythmic, so a room clapping in
near-unison scores in the same range as the band it is applauding, whether
measured by beat strength or by tempogram peak prominence. Separating the two
is what :mod:musiscape.concert uses spectral flatness for, and on a single
track the tempogram figure read by eye is the honest answer.
Both functions take features already computed elsewhere (a chromagram, an
onset envelope) rather than audio, so adding them to an extraction costs
almost nothing: :func:features.extract_track has both in hand.
key_stability(chroma, sr, hop=512, win_s=WIN_S)
Krumhansl--Schmuckler key per window, and how often they agree.
chroma is a (12, frames) chromagram, chroma_cqt on the harmonic
component as :mod:features computes it. Returns the modal key across
windows, the share of windows holding it, and the window count.
agreement is None when only one window fits: a single window
agrees with itself trivially, and reporting 1.0 for a short track would
make the least evidence look like the most.
Source code in src/musiscape/stability.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
tempo_stability(onset_env, sr, hop=512, win_s=WIN_S, tol=TEMPO_TOL)
Tempo per window, and how often the windows agree.
onset_env is an onset-strength envelope. Returns the median
windowed tempo, the share of windows within tol of it, and the
number of windows.
Nothing is returned for "is there a beat at all"; see the module docstring for why that question has no reliable answer here.
Windowed tempos are folded by metrical octave before being compared. A window heard at double or half time agrees about where the beat is, and counting it as disagreement would make every syncopated track look unstable.
Source code in src/musiscape/stability.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | |
Corpus statistics
Corpus-level statistics—the questions per-track tools do not answer.
The questions answered here are about the collection: how do its albums differ (fingerprints), which tracks resemble which (similarity, landscape), how internally consistent is each album, and how tightly does each cluster in key space (a circular statistic—key centres have no linear mean).
albums_of(feats)
Album names in first-appearance order.
Source code in src/musiscape/corpus.py
15 16 17 18 19 20 | |
feature_matrix(feats)
Standardised (z-scored, log-compressed where skewed) feature matrix.
Source code in src/musiscape/corpus.py
23 24 25 26 27 28 29 | |
album_stats(feats)
Per-album mean/std/min/max of every feature, plus keys and counts.
Source code in src/musiscape/corpus.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | |
landscape(feats)
PCA of the standardised features: 2-D coords, variance, loadings.
Source code in src/musiscape/corpus.py
51 52 53 54 55 56 57 58 59 60 61 62 63 | |
similarity(feats)
Track cosine-similarity matrix + album affinity and consistency.
affinity[a][b] is the mean similarity between the tracks of albums
a and b; the diagonal (mean pairwise similarity within an album) is
its internal consistency—one instrument and one mood score high,
an eclectic album scores near zero.
Source code in src/musiscape/corpus.py
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | |
tonal_spread(feats)
Per-album concentration of tonal centres on the circle of fifths.
Source code in src/musiscape/corpus.py
95 96 97 98 99 100 101 102 | |
Categorisation
Categorisation that can explain itself.
Tracks are clustered in the standardised feature space (k-means, k chosen by silhouette unless given), and every cluster is described by the features that most distinguish it from the rest of the corpus—so a category is never just "cluster 3", it is "sparse, dark, drone-like". This is the interpretable counterpart to embedding-space clustering: fewer dimensions, weaker similarity, but every axis has a musical name.
cluster(feats, k=None, seed=0)
K-means clustering with named-feature descriptions per cluster.
Returns labels aligned with feats, the silhouette score, and for
each cluster its size, member tracks, and the three most distinguishing
features as signed z-scores (e.g. onset_rate -1.2 = far sparser
than the corpus norm).
Source code in src/musiscape/categorize.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | |
Figures
Overview figures: fingerprints, landscape, affinity.
Categorical album colours use a fixed, colour-blind-validated order (never cycled); the affinity matrix is a blue/red diverging scale around zero. Past eight albums the palette folds—facet or filter rather than invent a ninth hue.
album_colors(names)
Stable album→colour map in first-appearance order.
Source code in src/musiscape/figures.py
35 36 37 | |
fingerprints(stats, out_path, title='')
Small-multiple bars: one panel per measure, one bar per album.
Source code in src/musiscape/figures.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
landscape_plot(feats, land, out_path, title='')
PCA scatter, one colour per album, direct legend.
Source code in src/musiscape/figures.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
affinity_plot(affinity, out_path, title='')
Album-affinity matrix, diverging around zero, values in cells.
Source code in src/musiscape/figures.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
draw_tempogram(ax, y, sr, hop=512, mark_bpm=None)
Autocorrelation tempogram onto ax, labelled in BPM.
Bright horizontal bands are the periodicities the onsets actually hold: a band that stays level across the whole width is a steady tempo, and one that bends is a band speeding up or slowing down. A tempo is drawn over it as a dashed line so the two can be compared.
mark_bpm sets which tempo that line shows; the default is this
figure's own estimate. Callers quoting a tempo elsewhere on the page
should pass theirs, since the two come from different onset envelopes
and would otherwise disagree in print.
Source code in src/musiscape/figures.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
draw_chromagram(ax, y, sr, hop=512)
Chromagram onto ax, labelled with the twelve pitch classes.
A tonal centre reads as one or two rows staying lit across the width; a modulation moves that pattern bodily up or down the axis.
Source code in src/musiscape/figures.py
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | |
tempogram_plot(y, sr, out_path, width_px=1920, height_px=640, title='')
Labelled tempogram → out_path, exactly width_px wide.
Source code in src/musiscape/figures.py
216 217 218 219 220 221 222 223 224 225 226 | |
chromagram_plot(y, sr, out_path, width_px=1920, height_px=640, title='')
Labelled chromagram → out_path, exactly width_px wide.
Source code in src/musiscape/figures.py
229 230 231 232 233 234 235 236 237 238 239 | |
draw_concert_timeline(ax, spans, total_s, level=None)
Labelled timeline of a concert's regions onto ax.
spans is what :func:musiscape.concert.regions returns. With
level, a per-second dB array, the class colour is carried by the
waveform itself rather than by a separate ribbon: one lane reads faster,
and it shows an applause swell dying away where a block only shows that
applause happened. Without a level, spans are drawn as plain blocks.
The legend names only the classes that occur, since an entry for a class that never happens invites the reader to hunt for it.
Source code in src/musiscape/figures.py
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | |
concert_timeline(spans, total_s, out_path, width_px=1920, height_px=300, title='', level=None)
Concert timeline → out_path, exactly width_px wide.
Source code in src/musiscape/figures.py
314 315 316 317 318 319 320 321 322 | |
Thumbnails & posters
Per-track visual thumbnails: a piece at a glance.
Each track becomes one card, in a choice of representations:
mel/chroma/tempo/combo—the spectrogram family (timbre & texture, harmony over time, rhythmic periodicity, all three);barcode—harmony as colour: each moment's hue is its position on the circle of fifths, saturation its tonal focus, brightness its loudness;ssm—self-similarity matrix: musical form as texture (repetition blocks, sections, drone slabs);trajectory—the piece as a smoothed path through its own timbre space (MFCC PCA), coloured start → end;keyscape—Sapp-style triangle: every analysis window at every time scale coloured by its Krumhansl–Schmuckler key (hue = tonic on the circle of fifths, light = major, dark = minor);rhythm—Poincaré portrait of successive inter-onset intervals: metric playing collapses to points, rubato spreads into clouds;wave—Freesound-style waveform: the amplitude envelope with each moment coloured by its spectral centroid (dark blue = dark timbre, red = bright), so timbre rides on the waveform itself;vinyl—the track as a tonality disc (12 o'clock = start, clockwise; hue = harmony on the circle of fifths, radius = loudness), with the Freesound-style centroid-coloured waveform as the strip underneath;spiral—time-integrated energy on the Shepard helix (angle = pitch class, radius = octave): the only view that shows register;tonnetz—the harmony's path on the circle-of-fifths plane of the tonal centroid (Harte's tonnetz), coloured start → end;-
arcs—Shape-of-Song-style arc diagram: repeated sections found in the self-similarity structure joined by arcs over the timeline. -
stereo—the stereo field: a pan-by-frequency spectrogram (blue = left, red = right, ink strength = energy) with a goniometer inset, over a width-and-correlation timeline. For multichannel and ambisonic spatial analysis see the ambiscape toolbox; tarsom—the track's position on Schaeffer's seven morphological criteria (TARSOM: masse, timbre harmonique, grain, allure, dynamique, profil mélodique, profil de masse) as a centre–periphery rose: each criterion a sector radiating from the centre pole (tonic, dark, smooth, slow, percussive, static, fixed) toward its periphery pole (complex, bright, granular, fast, soft, mobile, evolving);schaeffer—the track's sound objects on a typo-morphology (TARTYP) timeline: three mass lanes (N tonic / Y variable / X complex), facture as mark style (impulse ticks, hatched iterations, solid held blocks), with a TARTYP-grid fingerprint inset. Uses the same signal proxies and thresholds asmusiscape.music.tartyp_profile.
The rhythm card carries a beat-wheel inset: onset phases on the
dominant-period circle with the pulse-clarity resultant arrow.
Albums additionally get a contact sheet, and :func:poster stacks every
track's barcode into a single collection image where albums read as colour
families. Thumbnails are meant for browsing a collection visually.
barcode_rgb(C, rms)
RGB strip (n×3) from 2 Hz chroma + RMS—the barcode's colours.
Source code in src/musiscape/thumbnails.py
86 87 88 89 90 91 92 93 | |
wave_colors(y, sr, cols=1200)
Amplitude envelope + turbo-mapped spectral-centroid colours.
Source code in src/musiscape/thumbnails.py
96 97 98 99 100 101 102 103 104 105 106 | |
keyscape_rgb(C, levels=48)
Sapp-style keyscape image (levels×n×3) from 2 Hz chroma.
Source code in src/musiscape/thumbnails.py
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | |
render_track(track, color, out_path, sr=22050, note='', style='mel')
One card: the chosen representation over a waveform strip.
Source code in src/musiscape/thumbnails.py
637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 | |
contact_sheet(paths, out_path, cols=3)
Tile thumbnails into one album overview image.
Source code in src/musiscape/thumbnails.py
742 743 744 745 746 747 748 749 750 751 752 | |
render_collection(coll, out_dir, notes=None, workers=4, style='mel')
All thumbnails → <out_dir>/thumbnails/<album>/<track>.png
plus a contact sheet per album. notes maps (album, title) to a
short annotation (e.g. the estimated key from features.json).
Source code in src/musiscape/thumbnails.py
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 | |
poster(coll, out_dir, workers=4, strip_w=1200, strip_h=16, style='barcode')
One image for the whole collection. style="barcode" stacks every
track as a horizontal colour strip; style="vinyl" lays the tracks
out as a grid of disc glyphs. Albums read as colour families either
way. → <out_dir>/poster.png
Source code in src/musiscape/thumbnails.py
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 | |
notes_from_features(feats)
(album, track) → "key · bpm" annotation for thumbnail title bars.
The BPM shown is the perceptually-weighted tempo estimate
(tempo_bpm, librosa's prior-based estimator, which targets the
felt beat rather than the subdivision the phase-lock peaks on);
when pulse clarity is low (R < 0.1, i.e. rubato or drifting
material) it is prefixed with ~—a nominal tempo, not a felt
one. Falls back to pulse_bpm when tempo_bpm is absent.
Source code in src/musiscape/thumbnails.py
912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 | |
Sonic thumbnails
Sonic thumbnails: a short audio summary of each track.
Where the visual thumbnails answer "what does this piece look like", the sonic thumbnail answers "what does it sound like" in ~12 seconds: a montage of up to three segments chosen deterministically from the track's own structure—the most representative passage (the window whose features are closest to the whole track, in the audio-thumbnailing tradition of Bartsch & Wakefield), the climax (peak energy), and the most contrasting section that still carries energy. Segments are placed in chronological order and joined with equal-power crossfades, so the summary preserves the piece's own dramaturgy.
Everything is explainable: no learned model decides what matters.
sonic_thumbnail(y, sr, n_segments=3, seg_s=SEG_S, fade_s=FADE_S)
A ~12 s audio summary montage of y (mono float array).
Source code in src/musiscape/sonic.py
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | |
export_collection(coll, out_dir, workers=4, sr=22050)
Sonic thumbnails for every track → <out_dir>/sonic/<album>/,
plus one concatenated medley file per album.
Source code in src/musiscape/sonic.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
Report
Per-collection report: one README.md that answers "what is this collection?"
Runs the full pipeline (extract → stats → landscape → similarity → tonal spread → clusters), writes the figures, and renders a markdown report with an overview table, album fingerprints, affinity, categories, and notable extremes—the file to open first when handed a folder of music.
run(coll, out_dir, workers=4, duration=None, k=None)
Full pipeline → <out_dir>/README.md (+ features.json, figures).
Source code in src/musiscape/report.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | |
PDF report
One PDF: a summary table, then a page of figures per track.
The Markdown report in :mod:report is for reading on screen next to the
audio. This is the one to hand someone: a front table that fits an entire
concert on a page, and behind it one page per track carrying the figures
the table's numbers came from.
Every estimate is printed with its cross-check beside it rather than alone.
key and tempo_bpm each travel with the share of 20-second windows
that agreed on them. That is the whole point of the layout: a number in
this report is never presented as more certain than it is, and the reader
can see which tracks the analysis is confident about without knowing
anything about how it works.
No column claims to say whether a track has a pulse, because no measure
here distinguishes a band from an audience clapping along. See
:mod:stability.
Written with matplotlib's PdfPages, so no PDF library is needed beyond
what the package already depends on.
confidence(agreement)
Word for a window-agreement share, or a dash when unmeasured.
Source code in src/musiscape/pdfreport.py
41 42 43 44 45 46 47 48 49 | |
build(coll, out_dir, workers=4, duration=None, title=None)
Summary table + one figure page per track → <out_dir>/report.pdf.
Features are extracted (and cached) exactly as every other verb does,
so running this after report costs only the drawing.
Source code in src/musiscape/pdfreport.py
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | |
Command line
The verbs and options are in Command line; this is the entry point itself.
Command line: musiscape <verb> <collection-folder>.
main(argv=None)
Entry point for the musiscape command.
Source code in src/musiscape/cli.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | |
Concert segmentation from AudioSet tags
Concert segmentation from AudioSet posteriors (PANNs through ambiscape).
:mod:musiscape.concert labels a concert from spectral flatness and level,
which costs nothing per frame and was calibrated on one camera-mic concert. It
mistakes loud rock for applause and hears noise music as "other". This module
does the same job from a sound-event tagger: ambiscape.ml.tag_frames gives
AudioSet posteriors every couple of seconds, and the chain below turns them
into the same spans vocabulary (music, applause, voices,
quiet, other) so :func:musiscape.figures.concert_timeline and
:func:musiscape.concert.export_regions work unchanged.
Needs ambiscape[ml] (PANNs, torch). Roughly a minute for a 90-minute
concert on a laptop GPU (device="auto"), half an hour on CPU.
tag_frames(y, sr, win_s=4.0, hop_s=2.0, device=None)
(times, probs, names) from ambiscape.ml.tag_frames; a clear error without the extra.
Source code in src/musiscape/tagging.py
43 44 45 46 47 48 49 50 | |
frame_level_db(y, sr, times, win_s)
RMS in dBFS of the window centred on each time.
Source code in src/musiscape/tagging.py
53 54 55 56 57 58 59 60 61 | |
group_scores(P, names, groups=GROUPS)
Max posterior over each group's labels, per frame. Labels missing from names are ignored.
Source code in src/musiscape/tagging.py
64 65 66 67 68 69 70 71 | |
decide_frames(scores, level_db, weights=WEIGHTS, quiet_db=QUIET_DBFS, other_floor=OTHER_FLOOR)
One region label per frame: weighted argmax, level gate for quiet, floor for other.
Source code in src/musiscape/tagging.py
74 75 76 77 78 79 80 81 82 | |
mode_filter(lab, width=SMOOTH_FRAMES)
Sliding majority vote; a tie keeps the centre label.
Source code in src/musiscape/tagging.py
85 86 87 88 89 90 91 92 93 94 95 | |
runs_to_spans(lab, hop_s, win_s, total_s, scores=None)
Run-length encode frame labels into contiguous spans covering [0, total_s].
Source code in src/musiscape/tagging.py
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
enforce_min_duration(spans, min_s=MIN_DURATION_S)
Absorb spans shorter than their class minimum into the longer neighbour, shortest first, until stable.
Source code in src/musiscape/tagging.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | |
absorb_other(spans)
other next to music and not next to voices is part of the performance.
Noise music, laptop pieces and extended techniques score low on "Music" and come out as loud sound that is neither speech, applause nor silence; between or beside music, that is the piece.
Source code in src/musiscape/tagging.py
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | |
snap_to_songs(spans, songs, snap_s=SNAP_S)
Move music edges onto :func:concert.find_songs boundaries when the two agree within snap_s.
Source code in src/musiscape/tagging.py
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
refine_music_onsets(spans, y, sr, look_back_s=10.0, rise_db=8.0, frame_s=0.1)
Move each music span's start back to where the sound actually begins.
Tag windows are seconds long and the majority filter is longer, so a detected start
trails the first note by a few seconds. Within look_back_s before the detected start,
the onset is the earliest frame from which the level stays rise_db above the floor
of that window until the detected start.
Source code in src/musiscape/tagging.py
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | |
segment_concert(y, sr, songs=None, win_s=4.0, hop_s=2.0, device=None, min_s=MIN_DURATION_S, refine_onsets=True)
The whole chain on one mono array.
Returns {"spans": [...], "total_s": float, "frames": {"t", "music", "voices", "applause", "quiet", "level_db"}}
with spans in the :data:musiscape.concert.REGION_CLASSES vocabulary, contiguous from 0 to the end.
Source code in src/musiscape/tagging.py
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | |
Setlist alignment
Aligning detected pieces with the setlist.
A concert's running order says what was planned; the recording says what happened, in
what order, and what was dropped. Given the pieces a segmenter found and the text of the
spoken introduction before each (from any transcriber), this matches names heard to
names planned and fills the rest in running order. Names the host thanks belong to
the act that just finished and are ignored; a name right after "vær så god" or "ved"
is the act being introduced and counts extra. Acts nobody was matched to come back as
not_detected: cancelled, or not a musical number.
The setlist is a JSON list of acts {"nr", "act", "performers", "work", "composer",
"contact"} or a .docx whose first table has such columns (the IMV kjøreplan
template: Nr. / Innslag / Komponist / Låt / Medvirkende).
load_setlist(path)
Acts from a .json list or the first suitable table of a .docx.
Source code in src/musiscape/setlist.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | |
thanked_names(text)
Names the host thanks: the previous act. A transcript that is nothing but the thanks is left alone.
Source code in src/musiscape/setlist.py
87 88 89 90 91 92 93 94 | |
intro_names(text)
(token, relative position, cue bonus) for the words of an introduction that could be names.
Source code in src/musiscape/setlist.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
name_score(intro_text, act)
(score, position): best fuzzy match between the intro and the act; full names count more.
Source code in src/musiscape/setlist.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | |
align_setlist(pieces, acts, min_score=0.8)
Match detected pieces ({"id", "intro": text} in playing order) to acts.
Returns assignments (piece id -> act index or None), how (name / order /
continues), not_detected (act indices) and the per-act scores of every piece.
Source code in src/musiscape/setlist.py
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | |
act_title(act)
"3. Menuett fra Suite op. 20 – Øyvin Dybsand": the work when there is one, else the act name.
Source code in src/musiscape/setlist.py
175 176 177 178 179 180 181 182 183 | |