From 11c9fc653d764d44b3f96d360aa9dc7c3fce6084 Mon Sep 17 00:00:00 2001 From: Christopher Arndt Date: Wed, 22 May 2024 19:14:34 +0200 Subject: [PATCH] Some Atom handling refactoring Signed-off-by: Christopher Arndt --- examples/miditranspose.nim | 10 +-- src/nymph/atom.nim | 19 ++--- src/nymph/atom/util.nim | 89 ++++++++++++++++-------- src/nymph/urid.nim | 137 +++++++++++++++++++------------------ 4 files changed, 143 insertions(+), 112 deletions(-) diff --git a/examples/miditranspose.nim b/examples/miditranspose.nim index df5c14c..da7b6ec 100644 --- a/examples/miditranspose.nim +++ b/examples/miditranspose.nim @@ -29,7 +29,7 @@ proc instantiate(descriptor: ptr Lv2Descriptor; sampleRate: cdouble; if amp.map == nil: return nil - amp.midi_urid = amp.map[].map(amp.map[].handle, lv2MidiMidiEvent) + amp.midi_urid = amp.map.map(amp.map.handle, lv2MidiMidiEvent) return cast[Lv2Handle](amp) @@ -56,17 +56,17 @@ proc run(instance: Lv2Handle; nSamples: cuint) {.cdecl.} = atomSequenceClear(amp.output) amp.output.atom.type = amp.input.atom.type - if amp.input.atom.size > 8: + if not atomSequenceIsEmpty(amp.input): #echo &"Event sequence size: {amp.input.atom.size}" + let noteOffset = clamp(floor(amp.transposition[] + 0.5), -12, 12).uint8 - for ev in items(amp.input): - if Urid(ev.body.`type`) == amp.midi_urid: + for ev in amp.input: + if ev.body.`type` == amp.midi_urid: var msg = cast[ptr UncheckedArray[uint8]](atomContents(AtomEvent, ev)) #echo &"0x{toHex(msg[0], 2)} 0x{toHex(msg[1], 2)} 0x{toHex(msg[2], 2)}" case midiGetMessageType(msg[]): of midiMsgNoteOff, midiMsgNoteOn, midiMsgNotePressure: - let noteOffset = clamp(floor(amp.transposition[] + 0.5), -12, 12).uint8 msg[1] = clamp(msg[1] + noteOffset, 0, 127).uint8 else: discard diff --git a/src/nymph/atom.nim b/src/nymph/atom.nim index be316f0..fd4e002 100644 --- a/src/nymph/atom.nim +++ b/src/nymph/atom.nim @@ -7,6 +7,7 @@ ## import ptrmath +import urid const lv2AtomBaseUri ="http://lv2plug.in/ns/ext/atom" @@ -64,7 +65,7 @@ type ## The header of an atom:Atom. Atom* {.bycopy.} = object size*: uint32 ## Size in bytes, not including type and size. - `type`*: uint32 ## of this atom (mapped URI). + `type`*: Urid ## Type of this atom (mapped URI). ## An atom:Int or atom:Bool. May be cast to Atom. AtomInt* {.bycopy.} = object @@ -92,7 +93,7 @@ type ## An atom:URID. May be cast to Atom. AtomUrid* {.bycopy.} = object atom*: Atom ## Atom header. - body*: uint32 ## URID. + body*: Urid ## Urid. ## An atom:String. May be cast to Atom. AtomString* {.bycopy.} = object @@ -101,8 +102,8 @@ type ## The body of an atom:Literal. AtomLiteralBody* {.bycopy.} = object - datatype*: uint32 ## DataURID. - lang*: uint32 ## Language URID. + datatype*: Urid ## Datatype Urid. + lang*: Urid ## Language Urid. ## Contents (a null-terminated UTF-8 string) follow here. ## An atom:Literal. May be cast to Atom. @@ -128,8 +129,8 @@ type ## The body of an atom:Property (typically in an atom:Object). AtomPropertyBody* {.bycopy.} = object - key*: uint32 ## Key (predicate) (mapped URI). - context*: uint32 ## Context URID (may be, and generally is, 0). + key*: Urid ## Key (predicate) (mapped URI). + context*: Urid ## Context URID (may be, and generally is, 0). value*: Atom ## Value atom header. ## Value atom body follows here. @@ -140,8 +141,8 @@ type ## The body of an atom:Object. May be cast to Atom. AtomObjectBody* {.bycopy.} = object - id*: uint32 ## URID, or 0 for blank. - otype*: uint32 ## URID (same as rdf:type, for fast dispatch). + id*: Urid ## Urid, or 0 for blank. + otype*: Urid ## Urid (same as rdf:type, for fast dispatch). ## Contents (a series of property bodies) follow here. ## An atom:Object. May be cast to Atom. @@ -176,7 +177,7 @@ type ## ## AtomSequenceBody* {.bycopy.} = object - unit*: uint32 ## URID of unit of event time stamps. + unit*: Urid ## Urid of unit of event time stamps. pad*: uint32 ## Currently unused. ## Contents (a series of events) follow here. diff --git a/src/nymph/atom/util.nim b/src/nymph/atom/util.nim index 6df4228..4e38f3e 100644 --- a/src/nymph/atom/util.nim +++ b/src/nymph/atom/util.nim @@ -13,6 +13,7 @@ from system/ansi_c import c_memcmp, c_memcpy import ../atom import ../ptrmath +import ../urid ## ## Pad a size to 64 bits. @@ -31,7 +32,7 @@ proc atomTotalSize*(atom: ptr Atom): uint32 {.inline.} = ## Return true iff `atom` is null. ## proc atomIsNull*(atom: ptr Atom): bool {.inline.} = - return atom == nil or (atom.`type` == 0 and atom.size == 0) + return atom == nil or (atom.`type` == Urid(0) and atom.size == 0) ## ## Return true iff `a` is equal to `b`. @@ -46,31 +47,30 @@ proc atomEquals*(a: ptr Atom; b: ptr Atom): bool {.inline.} = ## ## Get an iterator pointing to the first event in a Sequence body. ## -proc atomSequenceBegin*(body: ptr AtomSequenceBody): ptr AtomEvent {.inline.} = - return cast[ptr AtomEvent](body + 1) - +template atomSequenceBegin*(body: ptr AtomSequenceBody): ptr AtomEvent = + cast[ptr AtomEvent](body + 1) ## ## Get an iterator pointing to the end of a Sequence body. ## -proc atomSequenceEnd*(body: ptr AtomSequenceBody; size: uint32): ptr AtomEvent {.inline.} = - return cast[ptr AtomEvent](cast[ptr uint8](body) + atomPadSize(size)) +template atomSequenceEnd*(body: ptr AtomSequenceBody; size: Natural): ptr AtomEvent = + cast[ptr AtomEvent](cast[ptr uint8](body) + atomPadSize(size.uint32)) ## ## Return true iff `i` has reached the end of `body`. ## -proc atomSequenceIsEnd*(body: ptr AtomSequenceBody; size: Natural; i: ptr AtomEvent): bool {.inline.} = - return cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.uint) +template atomSequenceIsEnd*(body: ptr AtomSequenceBody; size: Natural; i: ptr AtomEvent): bool = + cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.uint) ## ## Return an iterator to the element following `i`. ## -proc atomSequenceNext*(i: ptr AtomEvent): ptr AtomEvent {.inline.} = - return cast[ptr AtomEvent](cast[ptr uint8](i) + sizeof(AtomEvent) + atomPadSize(i.body.size)) +template atomSequenceNext*(i: ptr AtomEvent): ptr AtomEvent = + cast[ptr AtomEvent](cast[ptr uint8](i) + sizeof(AtomEvent) + atomPadSize(i.body.size)) ## -## An iterator for looping over all events in a Sequence. -## @param seq The sequence to iterate over +## An iterator for looping over all events in an AtomSequence. +## @param seq Pointer to the sequence to iterate over ## iterator items*(seq: ptr AtomSequence): ptr AtomEvent {.inline.} = var event = atomSequenceBegin(seq.body.addr) @@ -78,11 +78,28 @@ iterator items*(seq: ptr AtomSequence): ptr AtomEvent {.inline.} = yield event event = atomSequenceNext(event) -## TODO: Like LV2_ATOM_SEQUENCE_FOREACH but for a headerless sequence body. +## +## An iterator for looping over all events in an AtomSequenceBody. +## @param body Pointer to the sequence body to iterate over +## @param size Size in bytes of the sequence body (including the 8 bytes of AtomSequenceBody) +## +iterator items*(body: ptr AtomSequenceBody, size: Natural): ptr AtomEvent {.inline.} = + var event = atomSequenceBegin(body) + while not atomSequenceIsEnd(body, size, event): + yield event + event = atomSequenceNext(event) ## ## Sequence Utilities ## + +## +## Test if AtomSequence is empty, i.e the body has no events +## +template atomSequenceIsEmpty*(seq: ptr AtomSequence): bool = + seq.atom.size == sizeof(AtomSequenceBody).uint32 + +## ## Clear all events from `sequence`. ## ## This simply resets the size field, the other fields are left untouched. @@ -118,48 +135,60 @@ proc atomSequenceAppendEvent*(seq: ptr AtomSequence; capacity: uint32; ## ## Get an iterator pointing to the first element in `tup`. ## -proc atomTupleBegin*(tup: ptr AtomTuple): ptr Atom {.inline.} = - return cast[ptr Atom](atomContents(Atom, tup)) +template atomTupleBegin*(tup: ptr AtomTuple): ptr Atom = + cast[ptr Atom](atomContents(Atom, tup)) ## ## Return true iff `i` has reached the end of `body`. ## -proc atomTupleIsEnd*(body: pointer; size: Natural; i: ptr Atom): bool {.inline.} = - return cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.int) +template atomTupleIsEnd*(body: pointer; size: Natural; i: ptr Atom): bool = + cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.uint) ## ## Return an iterator to the element following `i`. ## -proc atomTupleNext*(i: ptr Atom): ptr Atom {.inline.} = - return cast[ptr Atom](cast[ptr uint8](i) + sizeof(Atom) + atomPadSize(i.size)) +template atomTupleNext*(i: ptr Atom): ptr Atom = + cast[ptr Atom](cast[ptr uint8](i) + sizeof(Atom) + atomPadSize(i.size)) - -## TODO: ## -## A iterator for looping over all properties of a Tuple. -## @param tuple The tuple to iterate over +## An iterator for looping over all elements of an AtomTuple. +## @param tuple Pointer to the tuple to iterate over +## +iterator items*(tup: ptr AtomTuple): ptr Atom {.inline.} = + var atom = atomTupleBegin(tup) + while not atomTupleIsEnd(atomBody(tup), tup.atom.size, atom): + yield atom + atom = atomTupleNext(atom) -## TODO: -## Like LV2_ATOM_TUPLE_FOREACH but for a headerless tuple body. +## +## An iterator for looping over all elements of a headerless tuple. +## @param tuple Pointer to the first Atom of the tuple to iterate over +## @param size Size in bytes of the tuple body +## +iterator items*(tup: ptr Atom, size: Natural): ptr Atom {.inline.} = + var atom = tup + while not atomTupleIsEnd(tup, size, atom): + yield atom + atom = atomTupleNext(atom) ## ## Object Iterator ## ## Return a pointer to the first property in `body`. ## -proc atomObjectBegin*(body: ptr AtomObjectBody): ptr AtomPropertyBody {.inline.} = - return cast[ptr AtomPropertyBody](body + 1) +template atomObjectBegin*(body: ptr AtomObjectBody): ptr AtomPropertyBody = + cast[ptr AtomPropertyBody](body + 1) ## ## Return true iff `i` has reached the end of `obj`. ## -proc atomObjectIsEnd*(body: ptr AtomObjectBody; size: Natural; i: ptr AtomPropertyBody): bool {.inline.} = - return cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.int) +template atomObjectIsEnd*(body: ptr AtomObjectBody; size: Natural; i: ptr AtomPropertyBody): bool = + cast[ptr uint8](i) >= (cast[ptr uint8](body) + size.uint) ## ## Return an iterator to the property following `i`. ## -proc atomObjectNext*(i: ptr AtomPropertyBody): ptr AtomPropertyBody {.inline.} = +template atomObjectNext*(i: ptr AtomPropertyBody): ptr AtomPropertyBody = let value = cast[ptr Atom](cast[ptr uint8](i) + 2 * sizeof(uint32)) return cast[ptr AtomPropertyBody](cast[ptr uint8](i) + atomPadSize(sizeof(AtomPropertyBody).uint32 + value.size)) diff --git a/src/nymph/urid.nim b/src/nymph/urid.nim index e6e1c63..d7788e5 100644 --- a/src/nymph/urid.nim +++ b/src/nymph/urid.nim @@ -14,81 +14,82 @@ const lv2UridMap* = lv2UridPrefix & "map" lv2UridUnmap* = lv2UridPrefix & "unmap" -## -## Opaque pointer to host data for UridMap. -## -type UridMapHandle* = pointer +type + ## + ## URI mapped to an integer. + ## + Urid* = distinct uint32 -## -## Opaque pointer to host data for uridUnmap. -## -type UridUnmapHandle* = pointer + ## + ## Opaque pointer to host data for UridMap. + ## + UridMapHandle* = pointer -## -## URI mapped to an integer. -## -type Urid* = distinct uint32 + ## + ## Opaque pointer to host data for uridUnmap. + ## + UridUnmapHandle* = pointer -## -## URID Map Feature (lv2UridMap) -## -type UridMap* {.bycopy.} = object ## - ## Opaque pointer to host data. + ## URID Map Feature (lv2UridMap) ## - ## This MUST be passed to map_uri() whenever it is called. - ## Otherwise, it must not be interpreted in any way. - ## - handle*: UridMapHandle - ## - ## Get the numeric ID of a URI. - ## - ## If the ID does not already exist, it will be created. - ## - ## This function is referentially transparent; any number of calls with the - ## same arguments is guaranteed to return the same value over the life of a - ## plugin instance. Note, however, that several URIs MAY resolve to the - ## same ID if the host considers those URIs equivalent. - ## - ## This function is not necessarily very fast or RT-safe: plugins SHOULD - ## cache any IDs they might need in performance critical situations. - ## - ## The return value 0 is reserved and indicates that an ID for that URI - ## could not be created for whatever reason. However, hosts SHOULD NOT - ## return 0 from this function in non-exceptional circumstances (i.e. the - ## URI map SHOULD be dynamic). - ## - ## @param handle Must be the callback_data member of this struct. - ## @param uri The URI to be mapped to an integer ID. - ## - map*: proc (handle: UridMapHandle; uri: cstring): Urid + UridMap* {.bycopy.} = object + ## + ## Opaque pointer to host data. + ## + ## This MUST be passed to map_uri() whenever it is called. + ## Otherwise, it must not be interpreted in any way. + ## + handle*: UridMapHandle + ## + ## Get the numeric ID of a URI. + ## + ## If the ID does not already exist, it will be created. + ## + ## This function is referentially transparent; any number of calls with the + ## same arguments is guaranteed to return the same value over the life of a + ## plugin instance. Note, however, that several URIs MAY resolve to the + ## same ID if the host considers those URIs equivalent. + ## + ## This function is not necessarily very fast or RT-safe: plugins SHOULD + ## cache any IDs they might need in performance critical situations. + ## + ## The return value 0 is reserved and indicates that an ID for that URI + ## could not be created for whatever reason. However, hosts SHOULD NOT + ## return 0 from this function in non-exceptional circumstances (i.e. the + ## URI map SHOULD be dynamic). + ## + ## @param handle Must be the callback_data member of this struct. + ## @param uri The URI to be mapped to an integer ID. + ## + map*: proc (handle: UridMapHandle; uri: cstring): Urid -## -## URI Unmap Feature (lv2UridUnmap) -## -type UridUnmap* {.bycopy.} = object ## - ## Opaque pointer to host data. + ## URI Unmap Feature (lv2UridUnmap) ## - ## This MUST be passed to unmap() whenever it is called. - ## Otherwise, it must not be interpreted in any way. - ## - handle*: UridUnmapHandle - ## - ## Get the URI for a previously mapped numeric ID. - ## - ## Returns NULL if `urid` is not yet mapped. Otherwise, the corresponding - ## URI is returned in a canonical form. This MAY not be the exact same - ## string that was originally passed to LV2_URID_Map::map(), but it MUST be - ## an identical URI according to the URI syntax specification (RFC3986). A - ## non-NULL return for a given `urid` will always be the same for the life - ## of the plugin. Plugins that intend to perform string comparison on - ## unmapped URIs SHOULD first canonicalise URI strings with a call to - ## map_uri() followed by a call to unmap_uri(). - ## - ## @param handle Must be the callback_data member of this struct. - ## @param urid The ID to be mapped back to the URI string. - ## - unmap*: proc (handle: UridUnmapHandle; urid: Urid): cstring + UridUnmap* {.bycopy.} = object + ## + ## Opaque pointer to host data. + ## + ## This MUST be passed to unmap() whenever it is called. + ## Otherwise, it must not be interpreted in any way. + ## + handle*: UridUnmapHandle + ## + ## Get the URI for a previously mapped numeric ID. + ## + ## Returns NULL if `urid` is not yet mapped. Otherwise, the corresponding + ## URI is returned in a canonical form. This MAY not be the exact same + ## string that was originally passed to LV2_URID_Map::map(), but it MUST be + ## an identical URI according to the URI syntax specification (RFC3986). A + ## non-NULL return for a given `urid` will always be the same for the life + ## of the plugin. Plugins that intend to perform string comparison on + ## unmapped URIs SHOULD first canonicalise URI strings with a call to + ## map_uri() followed by a call to unmap_uri(). + ## + ## @param handle Must be the callback_data member of this struct. + ## @param urid The ID to be mapped back to the URI string. + ## + unmap*: proc (handle: UridUnmapHandle; urid: Urid): cstring proc `==`* (x: Urid, y: Urid): bool {.borrow.}