Skip to content

Commit

Permalink
Merge pull request #36 from iluvcapra/feature-smpl
Browse files Browse the repository at this point in the history
Feature: smpl Metadata
  • Loading branch information
iluvcapra authored Nov 25, 2024
2 parents c6f66b2 + 299f79a commit 1d499d9
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 12 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ it is not supported, please submit an issue!
and Dolby Atmos `dbmd` metadata for re-renders and mixdowns.
* Wave embedded [cue markers][cues], cue marker labels, notes and timed ranges as used
by Zoom, iZotope RX, etc.
* Wave embedded [sampler][smpl] and sample loop metadata.
* The [wav format][format] is also parsed, so you can access the basic sample rate
and channel count information.


[format]:https://wavinfo.readthedocs.io/en/latest/classes.html#wavinfo.wave_reader.WavAudioFormat
[cues]:https://wavinfo.readthedocs.io/en/latest/scopes/cue.html
[bext]:https://wavinfo.readthedocs.io/en/latest/scopes/bext.html
[smpl]:https://wavinfo.readthedocs.io/en/latest/scopes/smpl.html
[smpte_330m2011]:https://wavinfo.readthedocs.io/en/latest/scopes/bext.html#wavinfo.wave_bext_reader.WavBextReader.umid
[adm]:https://wavinfo.readthedocs.io/en/latest/scopes/adm.html
[ebu3285s6]:https://wavinfo.readthedocs.io/en/latest/scopes/dolby.html
Expand Down
14 changes: 14 additions & 0 deletions docs/source/scopes/smpl.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

Sampler Metadata
=================

Class Reference
---------------

.. automodule:: wavinfo.wave_smpl_reader

.. autoclass:: wavinfo.wave_smpl_reader.WavSmplReader
:members:

.. autoclass:: wavinfo.wave_smpl_reader.WaveSmplLoop
:members:
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "wavinfo"
version = "3.0.1"
version = "3.1.0"
description = "Probe WAVE files for all metadata"
authors = ["Jamie Hardt <[email protected]>"]
license = "MIT"
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion wavinfo/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def default(self, o):
if isinstance(o, Enum):
return o._name_
elif isinstance(o, bytes):
return b64encode(o).decode('ascii')
return 'base64:' + b64encode(o).decode('ascii')
else:
return super().default(o)

Expand Down
23 changes: 13 additions & 10 deletions wavinfo/wave_smpl_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class WaveSmplLoop(NamedTuple):
loop_type: int
start: int
end: int
fraction: int
detune_cents: int
repetition_count: int

def loop_type_desc(self):
Expand All @@ -30,7 +30,7 @@ def to_dict(self):
'loop_type_description': self.loop_type_desc(),
'start_samples': self.start,
'end_samples': self.end,
'fraction': self.fraction,
'detune_cents': self.detune_cents,
'repetition_count': self.repetition_count,
}

Expand All @@ -42,8 +42,8 @@ def __init__(self, smpl_data: bytes):
Read sampler metadata from smpl chunk.
"""

header_field_fmt = "<IIIIIIbbbbII"
loop_field_fmt = "<IIIIII"
header_field_fmt = "<IIIIiIbbbbII"
loop_field_fmt = "<IIIIiI"
header_size = struct.calcsize(header_field_fmt)
loop_size = struct.calcsize(loop_field_fmt)

Expand All @@ -65,7 +65,7 @@ def __init__(self, smpl_data: bytes):
self.midi_note: int = unpacked_data[3]

#: The number of semitones above the MIDI note the loops tune for.
self.midi_pitch_fraction_semis: int = unpacked_data[4]
self.midi_pitch_detune_cents: int = unpacked_data[4]

#: SMPTE timecode format, one of (0, 24, 25, 29, 30)
self.smpte_format: int = unpacked_data[5]
Expand All @@ -89,21 +89,24 @@ def __init__(self, smpl_data: bytes):
loop_type=unpacked_loop[1],
start=unpacked_loop[2],
end=unpacked_loop[3],
fraction=unpacked_loop[4],
detune_cents=unpacked_loop[4],
repetition_count=unpacked_loop[5]))

#: Sampler-specific user data.
self.sampler_udata: bytes = smpl_data[
header_size + loop_size * loop_count:
header_size + loop_size * loop_count + sampler_udata_length]
self.sampler_udata: bytes | None = None

if sampler_udata_length > 0:
self.sampler_udata = smpl_data[
header_size + loop_size * loop_count:
header_size + loop_size * loop_count + sampler_udata_length]

def to_dict(self):
return {
'manufactuer': self.manufacturer,
'product': self.product,
'sample_period_ns': self.sample_period_ns,
'midi_note': self.midi_note,
'midi_pitch_fraction_semis': self.midi_pitch_fraction_semis,
'midi_pitch_detune_cents': self.midi_pitch_detune_cents,
'smpte_format': self.smpte_format,
'smpte_offset': "%02i:%02i:%02i:%02i" % self.smpte_offset,
'loops': [x.to_dict() for x in self.sample_loops],
Expand Down

0 comments on commit 1d499d9

Please sign in to comment.