aboutsummaryrefslogtreecommitdiff
path: root/Src/replicant/nswasabi
diff options
context:
space:
mode:
Diffstat (limited to 'Src/replicant/nswasabi')
-rw-r--r--Src/replicant/nswasabi/APEv2Metadata.cpp306
-rw-r--r--Src/replicant/nswasabi/APEv2Metadata.h29
-rw-r--r--Src/replicant/nswasabi/Android.mk12
-rw-r--r--Src/replicant/nswasabi/ApplicationBase.cpp168
-rw-r--r--Src/replicant/nswasabi/ApplicationBase.h44
-rw-r--r--Src/replicant/nswasabi/AutoCharNX.h184
-rw-r--r--Src/replicant/nswasabi/ComponentManagerBase.cpp145
-rw-r--r--Src/replicant/nswasabi/ComponentManagerBase.h31
-rw-r--r--Src/replicant/nswasabi/ID3v1Metadata.cpp187
-rw-r--r--Src/replicant/nswasabi/ID3v1Metadata.h26
-rw-r--r--Src/replicant/nswasabi/ID3v2Metadata.cpp1087
-rw-r--r--Src/replicant/nswasabi/ID3v2Metadata.h34
-rw-r--r--Src/replicant/nswasabi/Makefile48
-rw-r--r--Src/replicant/nswasabi/MetadataChain.h97
-rw-r--r--Src/replicant/nswasabi/MetadataEditorChain.h100
-rw-r--r--Src/replicant/nswasabi/ObjectFactory.h57
-rw-r--r--Src/replicant/nswasabi/PlaybackBase.cpp409
-rw-r--r--Src/replicant/nswasabi/PlaybackBase.h91
-rw-r--r--Src/replicant/nswasabi/PlaybackBase2.cpp490
-rw-r--r--Src/replicant/nswasabi/PlaybackBase2.h78
-rw-r--r--Src/replicant/nswasabi/ReferenceCounted.h232
-rw-r--r--Src/replicant/nswasabi/ServiceName.h21
-rw-r--r--Src/replicant/nswasabi/VERSION1
-rw-r--r--Src/replicant/nswasabi/XMLString.cpp44
-rw-r--r--Src/replicant/nswasabi/XMLString.h21
-rw-r--r--Src/replicant/nswasabi/nswasabi.sln82
-rw-r--r--Src/replicant/nswasabi/nswasabi.vcxproj159
-rw-r--r--Src/replicant/nswasabi/nswasabi.xcodeproj/project.pbxproj499
-rw-r--r--Src/replicant/nswasabi/nswasabi.xcodeproj/xcshareddata/xcschemes/nswasabi.xcscheme59
-rw-r--r--Src/replicant/nswasabi/precomp.h32
-rw-r--r--Src/replicant/nswasabi/singleton.h117
31 files changed, 4890 insertions, 0 deletions
diff --git a/Src/replicant/nswasabi/APEv2Metadata.cpp b/Src/replicant/nswasabi/APEv2Metadata.cpp
new file mode 100644
index 00000000..979a987a
--- /dev/null
+++ b/Src/replicant/nswasabi/APEv2Metadata.cpp
@@ -0,0 +1,306 @@
+#include "APEv2Metadata.h"
+#include "metadata/MetadataKeys.h"
+#include "nu/ByteReader.h"
+#include "nswasabi/ReferenceCounted.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+static inline bool TestFlag(int flags, int flag_to_check)
+{
+ if (flags & flag_to_check)
+ return true;
+ return false;
+}
+
+api_metadata *APEv2Metadata::metadata_api=0;
+
+APEv2Metadata::APEv2Metadata()
+{
+ apev2_tag=0;
+}
+
+APEv2Metadata::~APEv2Metadata()
+{
+}
+
+int APEv2Metadata::Initialize(api_metadata *metadata_api)
+{
+ APEv2Metadata::metadata_api = metadata_api;
+ return NErr_Success;
+}
+
+int APEv2Metadata::Initialize(nsapev2_tag_t tag)
+{
+ apev2_tag = tag;
+ return NErr_Success;
+}
+
+
+/* ifc_metadata implementation */
+int APEv2Metadata::Metadata_GetField(int field, unsigned int index, nx_string_t *value)
+{
+ if (!apev2_tag)
+ return NErr_Unknown;
+
+ switch (field)
+ {
+ case MetadataKeys::TRACK_GAIN:
+ return NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_TRACK_GAIN", index, value);
+ case MetadataKeys::TRACK_PEAK:
+ return NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_TRACK_PEAK", index, value);
+ case MetadataKeys::ALBUM_GAIN:
+ return NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_ALBUM_GAIN", index, value);
+ case MetadataKeys::ALBUM_PEAK:
+ return NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_ALBUM_PEAK", index, value);
+ }
+
+ return NErr_Unknown;
+}
+
+int APEv2Metadata::Metadata_GetInteger(int field, unsigned int index, int64_t *value)
+{
+ if (!apev2_tag)
+ return NErr_Unknown;
+
+ return NErr_Unknown;
+}
+
+int APEv2Metadata::Metadata_GetReal(int field, unsigned int index, double *value)
+{
+ if (!apev2_tag)
+ return NErr_Unknown;
+
+ int ret;
+ nx_string_t str;
+ switch (field)
+ {
+ case MetadataKeys::TRACK_GAIN:
+ ret = NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_TRACK_GAIN", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::TRACK_PEAK:
+ ret = NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_TRACK_PEAK", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::ALBUM_GAIN:
+ ret = NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_ALBUM_GAIN", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::ALBUM_PEAK:
+ ret = NSAPEv2_Tag_GetString(apev2_tag, "REPLAYGAIN_ALBUM_PEAK", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ }
+ return NErr_Unknown;
+}
+
+#ifdef _WIN32
+#define strcasecmp _stricmp
+#endif
+
+static const char *APEv2_GetMIME(const char *extension)
+{
+ if (!extension)
+ return 0;
+
+ if (strcasecmp(extension, "JPG") == 0 || strcasecmp(extension, "JPEG") == 0)
+ return "image/jpeg";
+ if (strcasecmp(extension, "PNG") == 0)
+ return "image/png";
+ if (strcasecmp(extension, "GIF") == 0)
+ return "image/gif";
+ if (strcasecmp(extension, "BMP") == 0)
+ return "image/bmp";
+
+ return 0;
+}
+
+static int APEv2_ParseArt(const void *bytes, size_t length, artwork_t *out_data, data_flags_t flags)
+{
+ if (out_data)
+ {
+ nx_data_t data=0;
+ if (flags != DATA_FLAG_NONE)
+ {
+ bytereader_s byte_reader;
+ bytereader_init(&byte_reader, bytes, length);
+ if (bytereader_size(&byte_reader) == 0)
+ return NErr_Insufficient;
+
+ const char *description_start = (const char *)bytereader_pointer(&byte_reader);
+ const char *extension_start=0;
+ uint8_t byte;
+ do
+ {
+ if (bytereader_size(&byte_reader) == 0)
+ return NErr_Insufficient;
+ byte = bytereader_read_u8(&byte_reader);
+ if (byte == '.') // found extension
+ {
+ extension_start = (const char *)bytereader_pointer(&byte_reader);
+ }
+ } while (byte && bytereader_size(&byte_reader));
+
+ size_t length = bytereader_size(&byte_reader);
+
+ if (length == 0)
+ return NErr_Empty;
+
+ if (TestFlag(flags, DATA_FLAG_DATA))
+ {
+ int ret = NXDataCreate(&data, bytereader_pointer(&byte_reader), length);
+ if (ret != NErr_Success)
+ return ret;
+ }
+ else
+ {
+ int ret = NXDataCreateEmpty(&data);
+ if (ret != NErr_Success)
+ return ret;
+ }
+
+ if (TestFlag(flags, DATA_FLAG_DESCRIPTION))
+ {
+ ReferenceCountedNXString description;
+ size_t length;
+ if (extension_start)
+ length = (size_t)extension_start - (size_t)description_start - 1;
+ else
+ length = (size_t)bytereader_pointer(&byte_reader) - (size_t)description_start - 1;
+
+ if (length)
+ {
+ int ret = NXStringCreateWithBytes(&description, description_start, length, nx_charset_utf8);
+ if (ret != NErr_Success)
+ {
+ NXDataRelease(data);
+ return ret;
+ }
+ NXDataSetDescription(data, description);
+ }
+ }
+
+ if (TestFlag(flags, DATA_FLAG_MIME))
+ {
+ ReferenceCountedNXString mime_type;
+ const char *mime_string = APEv2_GetMIME(extension_start);
+ if (mime_string)
+ {
+ int ret = NXStringCreateWithUTF8(&mime_type, mime_string);
+ if (ret != NErr_Success)
+ {
+ NXDataRelease(data);
+ return ret;
+ }
+ }
+ }
+ }
+ out_data->data = data;
+ /* we don't know these */
+ out_data->height=0;
+ out_data->width=0;
+ }
+ return NErr_Success;
+}
+
+int APEv2Metadata::Metadata_GetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags)
+{
+ if (!apev2_tag)
+ return NErr_Unknown;
+
+ int ret;
+ const void *bytes;
+ size_t length;
+ switch(field)
+ {
+ case MetadataKeys::ALBUM:
+ ret = NSAPEv2_Tag_GetBinary(apev2_tag, "Cover Art (front)", index, &bytes, &length);
+ if (ret == NErr_Success)
+ return APEv2_ParseArt(bytes, length, data, flags);
+ return ret;
+ }
+ return NErr_Unknown;
+}
+
+int APEv2Metadata::MetadataEditor_SetField(int field, unsigned int index, nx_string_t value)
+{
+ switch (field)
+ {
+ case MetadataKeys::TRACK_GAIN:
+ return NSAPEv2_Tag_SetString(apev2_tag, "REPLAYGAIN_TRACK_GAIN", index, value);
+ case MetadataKeys::TRACK_PEAK:
+ return NSAPEv2_Tag_SetString(apev2_tag, "REPLAYGAIN_TRACK_PEAK", index, value);
+ case MetadataKeys::ALBUM_GAIN:
+ return NSAPEv2_Tag_SetString(apev2_tag, "REPLAYGAIN_ALBUM_GAIN", index, value);
+ case MetadataKeys::ALBUM_PEAK:
+ return NSAPEv2_Tag_SetString(apev2_tag, "REPLAYGAIN_ALBUM_PEAK", index, value);
+ }
+ return NErr_Unknown;
+}
+
+int APEv2Metadata::MetadataEditor_SetInteger(int field, unsigned int index, int64_t value)
+{
+ return NErr_Unknown;
+}
+
+int APEv2Metadata::MetadataEditor_SetReal(int field, unsigned int index, double value)
+{
+ // TODO: but we need NXStringCreateFromDouble which I don't feel like writing right now
+ return NErr_Unknown;
+}
+
+static void APEv2_GetFilenameForMIME(char *filename, const char *type, nx_string_t mime_type)
+{
+ if (mime_type)
+ {
+ if (NXStringKeywordCompareWithCString(mime_type, "image/jpeg") == NErr_True || NXStringKeywordCompareWithCString(mime_type, "image/jpg") == NErr_True)
+ sprintf(filename, "%s.jpeg", type);
+ else if (NXStringKeywordCompareWithCString(mime_type, "image/png") == NErr_True)
+ sprintf(filename, "%s.png", type);
+ if (NXStringKeywordCompareWithCString(mime_type, "image/gif") == NErr_True)
+ sprintf(filename, "%s.gif", type);
+ if (NXStringKeywordCompareWithCString(mime_type, "image/bmp") == NErr_True)
+ sprintf(filename, "%s.bmp", type);
+ else
+ sprintf(filename, "%s.jpg", type); // TODO: perhaps we could use whatever is after image/
+ }
+ else
+ sprintf(filename, "%s.jpg", type); // ehh, just guess
+}
+
+int APEv2Metadata::MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags)
+{
+ ReferenceCountedNXString mime_type;
+ const void *bytes = 0;
+ size_t length = 0;
+ switch(field)
+ {
+ case MetadataKeys::ALBUM:
+ if (data && NXDataGet(data->data, &bytes, &length) == NErr_Success)
+ {
+ char filename[256] = {0};
+ NXDataGetMIME(data->data, &mime_type);
+ APEv2_GetFilenameForMIME(filename, "cover", mime_type); /* TODO: perhaps use description, instead? */
+ return NSAPEv2_Tag_SetArtwork(apev2_tag, "Cover Art (front)", index, filename, bytes, length);
+ }
+ else
+ return NSAPEv2_Tag_SetArtwork(apev2_tag, "Cover Art (front)", index, 0, 0, 0);
+ }
+ return NErr_Unknown;
+}
diff --git a/Src/replicant/nswasabi/APEv2Metadata.h b/Src/replicant/nswasabi/APEv2Metadata.h
new file mode 100644
index 00000000..ba4e15ac
--- /dev/null
+++ b/Src/replicant/nswasabi/APEv2Metadata.h
@@ -0,0 +1,29 @@
+#pragma once
+#include "metadata/metadata.h"
+#include "nsapev2/nsapev2.h"
+
+/* this class mimics ifc_metadata and ifc_metadata_editor, but doesn't inherit (because it's not given out directly) */
+class APEv2Metadata
+{
+public:
+ APEv2Metadata();
+ ~APEv2Metadata();
+
+ static int Initialize(api_metadata *metadata_api);
+ int Initialize(nsapev2_tag_t tag);
+
+ /* ifc_metadata implementation */
+ int WASABICALL Metadata_GetField(int field, unsigned int index, nx_string_t *value);
+ int WASABICALL Metadata_GetInteger(int field, unsigned int index, int64_t *value);
+ int WASABICALL Metadata_GetReal(int field, unsigned int index, double *value);
+ int WASABICALL Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags);
+
+ /* ifc_metadata_editor implementation */
+ int WASABICALL MetadataEditor_SetField(int field, unsigned int index, nx_string_t value);
+ int WASABICALL MetadataEditor_SetInteger(int field, unsigned int index, int64_t value);
+ int WASABICALL MetadataEditor_SetReal(int field, unsigned int index, double value);
+ int WASABICALL MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags);
+private:
+ nsapev2_tag_t apev2_tag;
+ static api_metadata *metadata_api;
+};
diff --git a/Src/replicant/nswasabi/Android.mk b/Src/replicant/nswasabi/Android.mk
new file mode 100644
index 00000000..39682221
--- /dev/null
+++ b/Src/replicant/nswasabi/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := nswasabi
+LOCAL_ARM_MODE := arm
+LOCAL_C_INCLUDES := $(ROOT_REPLICANT)
+LOCAL_CFLAGS := -fvisibility=hidden
+LOCAL_SRC_FILES := PlaybackBase.cpp XMLString.cpp ID3v2Metadata.cpp ID3v1Metadata.cpp APEv2Metadata.cpp ApplicationBase.cpp
+LOCAL_STATIC_LIBRARIES := nu
+LOCAL_EXPORT_LDLIBS := -llog
+include $(BUILD_STATIC_LIBRARY)
+
diff --git a/Src/replicant/nswasabi/ApplicationBase.cpp b/Src/replicant/nswasabi/ApplicationBase.cpp
new file mode 100644
index 00000000..334c3e24
--- /dev/null
+++ b/Src/replicant/nswasabi/ApplicationBase.cpp
@@ -0,0 +1,168 @@
+#include "ApplicationBase.h"
+#include "foundation/error.h"
+#include "application/features.h"
+#include <stdio.h> // for sprintf
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
+ApplicationBase::ApplicationBase()
+{
+ data_path = 0;
+ all_permissions_enabled = false;
+ device_id = 0;
+}
+
+ApplicationBase::~ApplicationBase()
+{
+ NXURIRelease(data_path);
+ data_path = 0;
+ NXStringRelease(device_id);
+ device_id = 0;
+}
+
+int ApplicationBase::Initialize()
+{
+ return NErr_Success;
+}
+
+/* and call this after doing your own shutdown */
+void ApplicationBase::Shutdown()
+{
+ NXURIRelease(data_path);
+ data_path = 0;
+}
+
+void ApplicationBase::SetDataPath(nx_uri_t new_data_path)
+{
+ nx_uri_t old_path = data_path;
+ data_path = NXURIRetain(new_data_path);
+ NXURIRelease(old_path);
+}
+
+void ApplicationBase::SetPermission(GUID feature)
+{
+ permissions.insert(feature);
+}
+
+void ApplicationBase::RemovePermission(GUID permission)
+{
+ permissions.erase(permission);
+}
+
+void ApplicationBase::EnableAllPermissions()
+{
+ all_permissions_enabled=true;
+}
+
+void ApplicationBase::ClearPermissions()
+{
+ permissions.clear();
+}
+
+void ApplicationBase::NotifyPermissions(api_syscb *system_callbacks)
+{
+ if (system_callbacks)
+ system_callbacks->IssueCallback(Features::event_type, Features::permissions_changed);
+}
+
+int ApplicationBase::Application_GetDataPath(nx_uri_t *path)
+{
+ *path=NXURIRetain(data_path);
+ if (data_path)
+ return NErr_Success;
+ else
+ return NErr_Empty;
+}
+
+int ApplicationBase::Application_GetPermission(GUID feature)
+{
+ if (all_permissions_enabled)
+ return NErr_True;
+ else if (permissions.find(feature) == permissions.end())
+ return NErr_False;
+ else
+ return NErr_True;
+}
+
+int ApplicationBase::Application_GetFeature(GUID feature)
+{
+ if (features.find(feature) == features.end())
+ return NErr_False;
+ else
+ return NErr_True;
+}
+
+void ApplicationBase::Application_SetFeature(GUID feature)
+{
+ features.insert(feature);
+}
+
+void ApplicationBase::SetDeviceID(nx_string_t device_id)
+{
+ nx_string_t old = this->device_id;
+ this->device_id = NXStringRetain(device_id);
+ NXStringRelease(old);
+}
+
+static void GUIDtoCString(const GUID &guid, char *target)
+{
+ //{2E9CE2F8-E26D-4629-A3FF-5DF619136B2C}
+ sprintf(target, "{%08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x}",
+ (int)guid.Data1, (int)guid.Data2, (int)guid.Data3,
+ (int)guid.Data4[0], (int)guid.Data4[1],
+ (int)guid.Data4[2], (int)guid.Data4[3],
+ (int)guid.Data4[4], (int)guid.Data4[5],
+ (int)guid.Data4[6], (int)guid.Data4[7] );
+
+}
+
+static const char *GetFeatureName(const GUID &guid)
+{
+ if (guid == Features::aac_playback)
+ return "AAC Playback";
+ else if (guid == Features::gapless)
+ return "Gapless Playback";
+ else if (guid == Features::flac_playback)
+ return "FLAC Playback";
+ else if (guid == Features::gracenote_autotag)
+ return "Gracenote Autotagger";
+ else
+ return 0; /* the lack of of return 0 by default here was why it was crashing */
+
+}
+
+void ApplicationBase::DumpPermissions()
+{
+#ifdef __ANDROID__
+ char guid_string[64];
+ FeatureList::iterator itr;
+ for (itr=features.begin();itr!=features.end();itr++)
+ {
+ GUIDtoCString(*itr, guid_string);
+ const char *feature_name = GetFeatureName(*itr);
+ if (feature_name)
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[Feature] %s (%s)", guid_string, feature_name);
+ else
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[Feature] %s", guid_string);
+ }
+
+ for (itr=permissions.begin();itr!=permissions.end();itr++)
+ {
+ GUIDtoCString(*itr, guid_string);
+ const char *feature_name = GetFeatureName(*itr);
+ if (feature_name)
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[Permission] %s (%s)", guid_string, feature_name);
+ else
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[Permission] %s", guid_string);
+ }
+#endif
+}
+
+int ApplicationBase::Application_GetDeviceID(nx_string_t *value)
+{
+ if (!device_id)
+ return NErr_Empty;
+ *value = NXStringRetain(device_id);
+ return NErr_Success;
+}
diff --git a/Src/replicant/nswasabi/ApplicationBase.h b/Src/replicant/nswasabi/ApplicationBase.h
new file mode 100644
index 00000000..f82f0b3a
--- /dev/null
+++ b/Src/replicant/nswasabi/ApplicationBase.h
@@ -0,0 +1,44 @@
+#pragma once
+#include "application/api_application.h"
+#include <set>
+#include "syscb/api_syscb.h"
+
+/* implements non-platform-specific methods of api_application.
+You can derive your Application implementation from this to ease your life */
+class ApplicationBase : public api_application
+{
+public:
+ ApplicationBase();
+ ~ApplicationBase();
+
+ /* call this (and check the return value) before doing your own initialization */
+ int Initialize();
+
+ /* and call this after doing your own shutdown */
+ void Shutdown();
+
+ void SetDataPath(nx_uri_t data_path);
+ void SetPermission(GUID feature);
+ void RemovePermission(GUID permission);
+ void SetDeviceID(nx_string_t device_id);
+ void EnableAllPermissions();
+ void ClearPermissions();
+ void NotifyPermissions(api_syscb *system_callbacks); /* pass in the syscb API to avoid a dependency */
+ void DumpPermissions(); /* dumps permissions list to the log file */
+protected:
+ /* api_application implementation */
+ int Application_GetDataPath(nx_uri_t *path);
+ int Application_GetPermission(GUID feature);
+ int Application_GetFeature(GUID feature);
+ void Application_SetFeature(GUID feature);
+ int Application_GetDeviceID(nx_string_t *value);
+
+private:
+ typedef std::set<GUID> FeatureList;
+ FeatureList features;
+ FeatureList permissions;
+ bool all_permissions_enabled; /* bypass for developer/QA testing */
+ nx_uri_t data_path;
+ nx_string_t device_id;
+
+};
diff --git a/Src/replicant/nswasabi/AutoCharNX.h b/Src/replicant/nswasabi/AutoCharNX.h
new file mode 100644
index 00000000..86e83b84
--- /dev/null
+++ b/Src/replicant/nswasabi/AutoCharNX.h
@@ -0,0 +1,184 @@
+#pragma once
+#include "../nx/nxstring.h"
+#include "../nx/nxuri.h"
+#include "../foundation/error.h"
+#include <stdlib.h>
+
+template <nx_charset_t charset>
+class AutoCharNX
+{
+public:
+ AutoCharNX()
+ {
+ Init();
+ }
+
+ AutoCharNX(size_t bytes)
+ {
+ Init();
+ ptr = (char *)malloc(bytes);
+ malloc_size = bytes;
+ }
+
+ AutoCharNX(nx_string_t string)
+ {
+ Init();
+
+ Set(string);
+ }
+
+ AutoCharNX(nx_uri_t filename)
+ {
+ Init();
+
+ Set(filename);
+ }
+
+ ~AutoCharNX()
+ {
+ if (owned)
+ free(ptr);
+ if (reference_string)
+ NXStringRelease(reference_string);
+ }
+
+ int Set(nx_string_t string)
+ {
+ if (reference_string == string)
+ return NErr_Success;
+
+ if (reference_string)
+ NXStringRelease(reference_string);
+ reference_string=0;
+
+ size_t byte_count=0;
+ int ret = NXStringGetBytesSize(&byte_count, string, charset, nx_string_get_bytes_size_null_terminate);
+ if(ret == NErr_DirectPointer)
+ {
+ if (owned)
+ {
+ free(ptr);
+ ptr=0;
+ length=0;
+ malloc_size=0;
+ }
+ ret = NXStringGetBytesDirect((const void **)&ptr, &length, string, charset, nx_string_get_bytes_size_null_terminate);
+ reference_string = NXStringRetain(string);
+ owned=false;
+ }
+ else if (ret == NErr_Success)
+ {
+ if (owned)
+ {
+ if (byte_count > malloc_size)
+ {
+ ptr = (char *)realloc(ptr, byte_count);
+ malloc_size = byte_count;
+ }
+ }
+ else
+ {
+ /* not owned. need to allocate */
+ ptr = (char *)malloc(byte_count);
+ malloc_size = byte_count;
+ owned=true;
+ }
+
+ if (ptr)
+ {
+ ret = NXStringGetBytes(&length, string, ptr, byte_count, charset, nx_string_get_bytes_size_null_terminate);
+ }
+ else
+ {
+ return NErr_OutOfMemory;
+ }
+ }
+ else
+ {
+ Clear();
+ }
+ return ret;
+ }
+
+ int Set(nx_uri_t filename)
+ {
+ int ret;
+ nx_string_t string;
+ ret = NXURIGetNXString(&string, filename);
+ if (ret == NErr_Success)
+ {
+ ret = Set(string);
+ NXStringRelease(string);
+ }
+ else
+ {
+ Clear();
+ // failed! we need to clean up
+ }
+ return ret;
+ }
+
+ operator const char *() const
+ {
+ if (length)
+ return ptr;
+ else
+ return 0;
+ }
+
+ /* this one will never return a NULL, always a valid string */
+ const char *GetValidString() const
+ {
+ if (length)
+ return ptr;
+ else
+ return "";
+ }
+
+ /* the Clear function clears the string but doesn't deallocate memory */
+ void Clear()
+ {
+ if (!owned)
+ ptr=0;
+ length=0;
+
+ if (reference_string)
+ NXStringRelease(reference_string);
+ reference_string=0;
+ }
+
+ size_t size()
+ {
+ if (length)
+ return length-1;
+ else
+ return 0;
+ }
+private:
+ void Init()
+ {
+ ptr=0;
+ length=0;
+ owned=false;
+ reference_string=0;
+ malloc_size=0;
+ }
+ char *ptr;
+ size_t length;
+ size_t malloc_size;
+ bool owned;
+ nx_string_t reference_string;
+};
+
+typedef AutoCharNX<nx_charset_utf8> AutoCharUTF8;
+#define AutoCharPrintfUTF8(x) (AutoCharUTF8(x).GetValidString())
+class AutoCharNative
+{
+public:
+};
+
+class AutoFilename
+{
+public:
+};
+
diff --git a/Src/replicant/nswasabi/ComponentManagerBase.cpp b/Src/replicant/nswasabi/ComponentManagerBase.cpp
new file mode 100644
index 00000000..aa8d3f43
--- /dev/null
+++ b/Src/replicant/nswasabi/ComponentManagerBase.cpp
@@ -0,0 +1,145 @@
+#include "ComponentManagerBase.h"
+#include "foundation/error.h"
+#include "nx/nxuri.h"
+
+ComponentManagerBase::ComponentManagerBase()
+{
+ phase=PHASE_INITIALIZE;
+ service_api=0;
+ component_sync=0;
+}
+
+int ComponentManagerBase::LateLoad(ifc_component *component)
+{
+ int ret;
+
+ if (phase >= PHASE_REGISTERED)
+ {
+ ret = component->RegisterServices(service_api);
+ if (ret != NErr_Success)
+ {
+ int ret2 = component->Quit(service_api);
+ if (ret2 == NErr_TryAgain)
+ {
+ component_sync->Wait(1);
+ }
+ return ret;
+ }
+ }
+
+ if (phase >= PHASE_LOADING)
+ {
+ ret = component->OnLoading(service_api);
+ if (ret != NErr_Success)
+ {
+ int ret2 = component->Quit(service_api);
+ if (ret2 == NErr_TryAgain)
+ {
+ component_sync->Wait(1);
+ }
+ return ret;
+ }
+ }
+
+ if (phase >= PHASE_LOADED)
+ {
+ ret = component->OnLoaded(service_api);
+ if (ret != NErr_Success)
+ {
+ int ret2 = component->Quit(service_api);
+ if (ret2 == NErr_TryAgain)
+ {
+ component_sync->Wait(1);
+ }
+ return ret;
+ }
+ }
+ return NErr_Success;
+}
+
+void ComponentManagerBase::SetServiceAPI(api_service *service_api)
+{
+ this->service_api = service_api;
+ service_api->QueryInterface(&component_sync);
+}
+
+int ComponentManagerBase::Load()
+{
+ if (phase != PHASE_INITIALIZE)
+ return NErr_Error;
+
+ int ret;
+
+ /* RegisterServices phase */
+ for (ComponentList::iterator itr=components.begin();itr!=components.end();)
+ {
+ ifc_component *component = *itr;
+ ComponentList::iterator next=itr;
+ next++;
+ ret = component->RegisterServices(service_api);
+ if (ret != NErr_Success)
+ {
+ int ret2 = component->Quit(service_api);
+ if (ret2 == NErr_TryAgain)
+ {
+ component_sync->Wait(1);
+ }
+ NXURIRelease(component->component_info.filename);
+ CloseComponent(component);
+ components.erase(component);
+
+ }
+ itr=next;
+ }
+
+ phase = PHASE_REGISTERED;
+
+ /* OnLoading phase */
+ for (ComponentList::iterator itr=components.begin();itr!=components.end();)
+ {
+ ifc_component *component = *itr;
+ ComponentList::iterator next=itr;
+ next++;
+ ret = component->OnLoading(service_api);
+ if (ret != NErr_Success)
+ {
+ int ret2 = component->Quit(service_api);
+ if (ret2 == NErr_TryAgain)
+ {
+ component_sync->Wait(1);
+ }
+ NXURIRelease(component->component_info.filename);
+ CloseComponent(component);
+ components.erase(component);
+
+ }
+ itr=next;
+ }
+
+ phase = PHASE_LOADING;
+
+ /* OnLoaded phase */
+ for (ComponentList::iterator itr=components.begin();itr!=components.end();)
+ {
+ ifc_component *component = *itr;
+ ComponentList::iterator next=itr;
+ next++;
+ ret = component->OnLoading(service_api);
+ if (ret != NErr_Success)
+ {
+ int ret2 = component->Quit(service_api);
+ if (ret2 == NErr_TryAgain)
+ {
+ component_sync->Wait(1);
+ }
+ NXURIRelease(component->component_info.filename);
+ CloseComponent(component);
+ components.erase(component);
+ }
+ itr=next;
+ }
+
+ phase = PHASE_LOADED;
+
+ return NErr_Success;
+}
diff --git a/Src/replicant/nswasabi/ComponentManagerBase.h b/Src/replicant/nswasabi/ComponentManagerBase.h
new file mode 100644
index 00000000..00252b36
--- /dev/null
+++ b/Src/replicant/nswasabi/ComponentManagerBase.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "nx/nxuri.h"
+#include "service/api_service.h"
+#include "component/ifc_component.h"
+#include "nu/PtrDeque.h"
+#include "component/ifc_component_sync.h"
+
+class ComponentManagerBase
+{
+public:
+ void SetServiceAPI(api_service *service_api);
+ int Load();
+protected:
+ ComponentManagerBase();
+ int LateLoad(ifc_component *mod);
+ enum Phase
+ {
+ PHASE_INITIALIZE=0, /* components are still being added */
+ PHASE_REGISTERED=1, /* RegisterServices() has been called on all components */
+ PHASE_LOADING=2, /* OnLoading() has been called on all components */
+ PHASE_LOADED=3, /* OnLoaded() has been called on all components */
+ };
+ Phase phase;
+ typedef nu::PtrDeque<ifc_component> ComponentList;
+ ComponentList components;
+ api_service *service_api;
+ ifc_component_sync *component_sync;
+private:
+ /* your implementation needs to override this. You should call FreeLibrary(component->component_info.hModule); or dlclose(component->component_info.dl_handle); or similar */
+ virtual void CloseComponent(ifc_component *component)=0;
+};
diff --git a/Src/replicant/nswasabi/ID3v1Metadata.cpp b/Src/replicant/nswasabi/ID3v1Metadata.cpp
new file mode 100644
index 00000000..2883401e
--- /dev/null
+++ b/Src/replicant/nswasabi/ID3v1Metadata.cpp
@@ -0,0 +1,187 @@
+#include "ID3v1Metadata.h"
+#include "metadata/MetadataKeys.h"
+#include <stdlib.h>
+
+api_metadata *ID3v1Metadata::metadata_api=0;
+
+ID3v1Metadata::ID3v1Metadata()
+{
+ id3v1_tag=0;
+}
+
+ID3v1Metadata::~ID3v1Metadata()
+{
+}
+
+int ID3v1Metadata::Initialize(api_metadata *metadata_api)
+{
+ ID3v1Metadata::metadata_api = metadata_api;
+ return NErr_Success;
+}
+
+int ID3v1Metadata::Initialize(nsid3v1_tag_t tag)
+{
+ id3v1_tag = tag;
+ this->metadata_api = metadata_api;
+ return NErr_Success;
+}
+
+/* ifc_metadata implementation */
+int ID3v1Metadata::Metadata_GetField(int field, unsigned int index, nx_string_t *value)
+{
+ if (!id3v1_tag)
+ return NErr_Unknown;
+
+ switch (field)
+ {
+ case MetadataKeys::TITLE:
+ return index?NErr_EndOfEnumeration:NSID3v1_Get_Title(id3v1_tag, value);
+ case MetadataKeys::ARTIST:
+ return index?NErr_EndOfEnumeration:NSID3v1_Get_Artist(id3v1_tag, value);
+ case MetadataKeys::ALBUM:
+ return index?NErr_EndOfEnumeration:NSID3v1_Get_Album(id3v1_tag, value);
+ case MetadataKeys::YEAR:
+ return index?NErr_EndOfEnumeration:NSID3v1_Get_Year(id3v1_tag, value);
+ case MetadataKeys::COMMENT:
+ return index?NErr_EndOfEnumeration:NSID3v1_Get_Comment(id3v1_tag, value);
+ case MetadataKeys::TRACK:
+ return index?NErr_EndOfEnumeration:NSID3v1_Get_Track(id3v1_tag, value);
+ case MetadataKeys::GENRE:
+ {
+ if (!metadata_api)
+ return NErr_Unknown;
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ uint8_t genre_id;
+ int ret = NSID3v1_Int_Get_Genre(id3v1_tag, &genre_id);
+ if (ret != NErr_Success)
+ return ret;
+
+ nx_string_t genre;
+ ret = metadata_api->GetGenre(genre_id, &genre);
+ if (ret == NErr_Success)
+ {
+ *value = NXStringRetain(genre);
+ return NErr_Success;
+ }
+ else if (ret == NErr_Unknown)
+ {
+ return NErr_Empty;
+ }
+ else
+ {
+ return ret;
+ }
+ }
+ }
+
+ return NErr_Unknown;
+}
+
+int ID3v1Metadata::Metadata_GetInteger(int field, unsigned int index, int64_t *value)
+{
+ if (!id3v1_tag)
+ return NErr_Unknown;
+
+ switch (field)
+ {
+ case MetadataKeys::YEAR:
+ {
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+ unsigned int year;
+ int ret = NSID3v1_Int_Get_Year(id3v1_tag, &year);
+ if (ret == NErr_Success)
+ *value = (int64_t)year;
+ return ret;
+ }
+ case MetadataKeys::TRACK:
+ {
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+ uint8_t track;
+ int ret = NSID3v1_Int_Get_Track(id3v1_tag, &track);
+ if (ret == NErr_Success)
+ *value = (int64_t)track;
+ return ret;
+ }
+ }
+ return NErr_Unknown;
+}
+
+int ID3v1Metadata::Metadata_GetReal(int field, unsigned int index, double *value)
+{
+ if (!id3v1_tag)
+ return NErr_Unknown;
+
+ return NErr_Unknown;
+}
+
+int ID3v1Metadata::MetadataEditor_SetField(int field, unsigned int index, nx_string_t value)
+{
+ if (!id3v1_tag)
+ return NErr_NullPointer;
+
+ switch (field)
+ {
+ case MetadataKeys::TITLE:
+ return index?NErr_EndOfEnumeration:NSID3v1_Set_Title(id3v1_tag, value);
+ case MetadataKeys::ARTIST:
+ return index?NErr_EndOfEnumeration:NSID3v1_Set_Artist(id3v1_tag, value);
+ case MetadataKeys::ALBUM:
+ return index?NErr_EndOfEnumeration:NSID3v1_Set_Album(id3v1_tag, value);
+ case MetadataKeys::YEAR:
+ return index?NErr_EndOfEnumeration:NSID3v1_Set_Year(id3v1_tag, value);
+ case MetadataKeys::COMMENT:
+ return index?NErr_EndOfEnumeration:NSID3v1_Set_Comment(id3v1_tag, value);
+ case MetadataKeys::TRACK:
+ return index?NErr_EndOfEnumeration:NSID3v1_Set_Track(id3v1_tag, value);
+
+ case MetadataKeys::GENRE:
+ {
+ if (!metadata_api)
+ return NErr_Unknown;
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ uint8_t genre_id;
+ int ret = metadata_api->GetGenreID(value, &genre_id);
+ if (ret == NErr_Success)
+ return NSID3v1_Int_Set_Genre(id3v1_tag, genre_id);
+ else
+ return NSID3v1_Int_Set_Genre(id3v1_tag, 0xFF);
+ }
+
+
+ }
+
+ return NErr_Unknown;
+}
+
+int ID3v1Metadata::MetadataEditor_SetInteger(int field, unsigned int index, int64_t value)
+{
+ if (!id3v1_tag)
+ return NErr_NullPointer;
+
+ if (index != 0)
+ return NErr_EndOfEnumeration;
+
+ switch (field)
+ {
+ case MetadataKeys::YEAR:
+ return NSID3v1_Int_Set_Year(id3v1_tag, (unsigned int)value);
+ case MetadataKeys::TRACK:
+ if (value < 0 || value > 255)
+ return NErr_ParameterOutOfRange;
+ return NSID3v1_Int_Set_Track(id3v1_tag, (uint8_t)value);
+ case MetadataKeys::GENRE:
+ if (value < 0 || value > 255)
+ return NErr_ParameterOutOfRange;
+ return NSID3v1_Int_Set_Genre(id3v1_tag, (uint8_t)value);
+ }
+
+ return NErr_Unknown;
+}
+
+#undef DESCRIPTION
diff --git a/Src/replicant/nswasabi/ID3v1Metadata.h b/Src/replicant/nswasabi/ID3v1Metadata.h
new file mode 100644
index 00000000..c6da027c
--- /dev/null
+++ b/Src/replicant/nswasabi/ID3v1Metadata.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "metadata/metadata.h"
+#include "nsid3v1/nsid3v1.h"
+
+/* this class mimics ifc_metadata and ifc_metadata_editor, but doesn't inherit (because it's not given out directly) */
+class ID3v1Metadata
+{
+public:
+ ID3v1Metadata();
+ ~ID3v1Metadata();
+
+ static int Initialize(api_metadata *metadata_api);
+ int Initialize(nsid3v1_tag_t tag);
+
+ /* ifc_metadata implementation */
+ int WASABICALL Metadata_GetField(int field, unsigned int index, nx_string_t *value);
+ int WASABICALL Metadata_GetInteger(int field, unsigned int index, int64_t *value);
+ int WASABICALL Metadata_GetReal(int field, unsigned int index, double *value);
+
+ /* ifc_metadata_editor implementation */
+ int WASABICALL MetadataEditor_SetField(int field, unsigned int index, nx_string_t value);
+ int WASABICALL MetadataEditor_SetInteger(int field, unsigned int index, int64_t value);
+private:
+ nsid3v1_tag_t id3v1_tag;
+ static api_metadata *metadata_api;
+};
diff --git a/Src/replicant/nswasabi/ID3v2Metadata.cpp b/Src/replicant/nswasabi/ID3v2Metadata.cpp
new file mode 100644
index 00000000..a1b49212
--- /dev/null
+++ b/Src/replicant/nswasabi/ID3v2Metadata.cpp
@@ -0,0 +1,1087 @@
+#include "ID3v2Metadata.h"
+#include "metadata/MetadataKeys.h"
+#include "nswasabi/ReferenceCounted.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+api_metadata *ID3v2Metadata::metadata_api=0;
+
+static inline bool TestFlag(int flags, int flag_to_check)
+{
+ if (flags & flag_to_check)
+ return true;
+ return false;
+}
+
+ID3v2Metadata::ID3v2Metadata()
+{
+ id3v2_tag=0;
+
+#ifdef __APPLE__
+ number_formatter = NULL;
+#endif
+}
+
+ID3v2Metadata::~ID3v2Metadata()
+{
+#ifdef __APPLE__
+ if (NULL != number_formatter)
+ CFRelease(number_formatter);
+#endif
+}
+
+int ID3v2Metadata::Initialize(api_metadata *metadata_api)
+{
+ ID3v2Metadata::metadata_api = metadata_api;
+ return NErr_Success;
+}
+
+int ID3v2Metadata::Initialize(nsid3v2_tag_t tag)
+{
+ id3v2_tag = tag;
+
+ return NErr_Success;
+}
+
+int ID3v2Metadata::GetGenre(int index, nx_string_t *value)
+{
+ nx_string_t genre=0;
+ int ret = NSID3v2_Tag_Text_Get(id3v2_tag, NSID3V2_FRAME_CONTENTTYPE, &genre, 0);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ if (genre)
+ {
+ *value = genre;
+#ifdef _WIN32
+ // parse the (##) out of it
+ wchar_t *tmp = genre->string;
+ while (*tmp == ' ') tmp++;
+ if (!wcsncmp(tmp, L"(RX)", 4))
+ {
+ *value = NXStringCreateFromUTF8("Remix");
+ NXStringRelease(genre);
+ if (*value)
+ return NErr_Success;
+ else
+ return NErr_OutOfMemory;
+ }
+ else if (!wcsncmp(tmp, L"(CR)", 4))
+ {
+ *value = NXStringCreateFromUTF8("Cover");
+ NXStringRelease(genre);
+ if (*value)
+ return NErr_Success;
+ else
+ return NErr_OutOfMemory;
+ }
+
+ if (*tmp == '(' || (*tmp >= '0' && *tmp <= '9')) // both (%d) and %d forms
+ {
+ int noparam = 0;
+
+ if (*tmp == '(') tmp++;
+ else noparam = 1;
+ size_t genre_index = _wtoi(tmp);
+ int cnt = 0;
+ while (*tmp >= '0' && *tmp <= '9') cnt++, tmp++;
+ while (*tmp == ' ') tmp++;
+
+ if (((!*tmp && noparam) || (!noparam && *tmp == ')')) && cnt > 0)
+ {
+ if (genre_index < 256 && metadata_api)
+ {
+ int ret = metadata_api->GetGenre(genre_index, value);
+ if (ret == NErr_Success)
+ {
+ NXStringRetain(*value);
+ NXStringRelease(genre);
+ return ret;
+ }
+ }
+ }
+ }
+#elif defined(__APPLE__)
+ int ret = NErr_Success;
+
+ CFMutableStringRef mutable_genre = CFStringCreateMutableCopy(NULL, 0, genre);
+ CFStringTrimWhitespace(mutable_genre);
+
+ CFIndex mutable_genre_length = CFStringGetLength(mutable_genre);
+
+ if (kCFCompareEqualTo == CFStringCompareWithOptionsAndLocale(mutable_genre,
+ CFSTR("(RX)"),
+ CFRangeMake(0, mutable_genre_length),
+ 0,
+ NULL))
+ {
+ NXStringRelease(genre);
+ *value = CFSTR("Remix");
+ ret = NErr_Success;
+ }
+ else if (kCFCompareEqualTo == CFStringCompareWithOptionsAndLocale(mutable_genre,
+ CFSTR("(CR)"),
+ CFRangeMake(0, mutable_genre_length),
+ 0,
+ NULL))
+ {
+ NXStringRelease(genre);
+ *value = CFSTR("Cover");
+ ret = NErr_Success;
+ }
+ else
+ {
+ CFStringTrim(mutable_genre, CFSTR("("));
+ CFStringTrim(mutable_genre, CFSTR(")"));
+ mutable_genre_length = CFStringGetLength(mutable_genre);
+ if (mutable_genre_length > 0
+ && mutable_genre_length < 4)
+ {
+ if (NULL == number_formatter)
+ {
+ CFLocaleRef locale = CFLocaleCreate(NULL, CFSTR("en_US_POSIX"));
+ number_formatter = CFNumberFormatterCreate(NULL, locale, kCFNumberFormatterDecimalStyle);
+ CFRelease(locale);
+ }
+
+ SInt8 genre_index;
+ CFRange number_range = CFRangeMake(0, mutable_genre_length);
+ if (NULL != number_formatter
+ && false != CFNumberFormatterGetValueFromString(number_formatter,
+ mutable_genre,
+ &number_range,
+ kCFNumberSInt8Type,
+ &genre_index)
+ && number_range.length == mutable_genre_length
+ && number_range.location == 0)
+ {
+
+ if (genre_index >= 0
+ && genre_index < 256
+ && metadata_api)
+ {
+ int ret = metadata_api->GetGenre(genre_index, value);
+ if (ret == NErr_Success)
+ {
+ NXStringRetain(*value);
+ NXStringRelease(genre);
+ }
+ ret = NErr_Success;
+ }
+ }
+ }
+ }
+
+ CFRelease(mutable_genre);
+ return ret;
+#elif defined(__linux__)
+ char *tmp = genre->string;
+ while (*tmp == ' ') tmp++;
+
+ if (!strncmp(tmp, "(RX)", 4))
+ {
+ NXStringRelease(genre);
+ return NXStringCreateWithUTF8(value, "Remix");
+ }
+ else if (!strncmp(tmp, "(CR)", 4))
+ {
+ NXStringRelease(genre);
+ return NXStringCreateWithUTF8(value, "Cover");
+ }
+
+ if (*tmp == '(' || (*tmp >= '0' && *tmp <= '9')) // both (%d) and %d forms
+ {
+ int noparam = 0;
+
+ if (*tmp == '(') tmp++;
+ else noparam = 1;
+ size_t genre_index = atoi(tmp);
+ int cnt = 0;
+ while (*tmp >= '0' && *tmp <= '9') cnt++, tmp++;
+ while (*tmp == ' ') tmp++;
+
+ if (((!*tmp && noparam) || (!noparam && *tmp == ')')) && cnt > 0)
+ {
+ if (genre_index < 256 && metadata_api)
+ {
+ int ret = metadata_api->GetGenre(genre_index, value);
+ if (ret == NErr_Success)
+ {
+ NXStringRetain(*value);
+ NXStringRelease(genre);
+ return ret;
+ }
+ }
+ }
+ }
+#else
+#error port me!
+#endif
+ }
+ return NErr_Success;
+}
+
+static int ID3v2_GetText(nsid3v2_tag_t id3v2_tag, int frame_enum, unsigned int index, nx_string_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Empty;
+
+ nsid3v2_frame_t frame;
+ int ret = NSID3v2_Tag_GetFrame(id3v2_tag, frame_enum, &frame);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ return NSID3v2_Frame_Text_Get(frame, value, 0);
+}
+
+static int ID3v2_GetTXXX(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Empty;
+
+ nsid3v2_frame_t frame;
+ int ret = NSID3v2_Tag_TXXX_Find(id3v2_tag, description, &frame, 0);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ return NSID3v2_Frame_UserText_Get(frame, 0, value, 0);
+}
+
+static int ID3v2_GetComments(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Empty;
+
+ nsid3v2_frame_t frame;
+ int ret = NSID3v2_Tag_Comments_Find(id3v2_tag, description, &frame, 0);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ return NSID3v2_Frame_Comments_Get(frame, 0, 0, value, 0);
+}
+
+// only one of value1 or value2 should be non-NULL
+static int SplitSlash(nx_string_t track, nx_string_t *value1, nx_string_t *value2)
+{
+ char track_utf8[64];
+ size_t bytes_copied;
+ int ret;
+ ret = NXStringGetBytes(&bytes_copied, track, track_utf8, 64, nx_charset_utf8, nx_string_get_bytes_size_null_terminate);
+ if (ret == NErr_Success)
+ {
+ size_t len = strcspn(track_utf8, "/");
+
+ if (value2)
+ {
+ const char *second = &track_utf8[len];
+ if (*second)
+ second++;
+
+ if (!*second)
+ return NErr_Empty;
+
+ return NXStringCreateWithUTF8(value2, second);
+ }
+ else
+ {
+ if (len == 0)
+ return NErr_Empty;
+ return NXStringCreateWithBytes(value1, track_utf8, len, nx_charset_utf8);
+ }
+
+ return NErr_Success;
+ }
+
+ return ret;
+}
+
+/* ifc_metadata implementation */
+int ID3v2Metadata::Metadata_GetField(int field, unsigned int index, nx_string_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Unknown;
+
+ int ret;
+
+ switch (field)
+ {
+ case MetadataKeys::ARTIST:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_LEADARTIST, index, value);
+
+ case MetadataKeys::ALBUM_ARTIST:
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BAND, index, value); /* Windows Media Player style */
+ if (ret == NErr_Success || ret == NErr_EndOfEnumeration)
+ return ret;
+
+ ret = ID3v2_GetTXXX(id3v2_tag, "ALBUM ARTIST", index, value); /* foobar 2000 style */
+ if (ret == NErr_Success || ret == NErr_EndOfEnumeration)
+ return ret;
+
+ ret = ID3v2_GetTXXX(id3v2_tag, "ALBUMARTIST", index, value); /* mp3tag style */
+ if (ret == NErr_Success || ret == NErr_EndOfEnumeration)
+ return ret;
+
+ return ID3v2_GetTXXX(id3v2_tag, "Band", index, value); /* audacity style */
+
+ case MetadataKeys::ALBUM:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_ALBUM, index, value);
+
+ case MetadataKeys::TITLE:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TITLE, index, value);
+
+ case MetadataKeys::GENRE:
+ return GetGenre(index, value);
+
+ case MetadataKeys::TRACK:
+ {
+ ReferenceCountedNXString track;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track);
+ if (ret == NErr_Success)
+ return SplitSlash(track, value, 0);
+
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::TRACKS:
+{
+ ReferenceCountedNXString track;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track);
+ if (ret == NErr_Success)
+ return SplitSlash(track, 0, value);
+
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::YEAR:
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, value);
+ if (ret == NErr_Success || ret == NErr_EndOfEnumeration)
+ return ret;
+
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_YEAR, index, value);
+
+ case MetadataKeys::DISC:
+ {
+ ReferenceCountedNXString track;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track);
+ if (ret == NErr_Success)
+ return SplitSlash(track, value, 0);
+
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::DISCS:
+ {
+ ReferenceCountedNXString track;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track);
+ if (ret == NErr_Success)
+ return SplitSlash(track, 0, value);
+
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::COMPOSER:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_COMPOSER, index, value);
+
+ case MetadataKeys::PUBLISHER:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PUBLISHER, index, value);
+
+ case MetadataKeys::BPM:
+ return ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BPM, index, value);
+
+ case MetadataKeys::COMMENT:
+ return ID3v2_GetComments(id3v2_tag, "", index, value);
+ // TODO case MetadataKeys::PLAY_COUNT:
+ // TODO case MetadataKeys::RATING:
+
+ case MetadataKeys::TRACK_GAIN:
+ return ID3v2_GetTXXX(id3v2_tag, "replaygain_track_gain", index, value);
+
+ case MetadataKeys::TRACK_PEAK:
+ return ID3v2_GetTXXX(id3v2_tag, "replaygain_track_peak", index, value);
+
+ case MetadataKeys::ALBUM_GAIN:
+ return ID3v2_GetTXXX(id3v2_tag, "replaygain_album_gain", index, value);
+
+ case MetadataKeys::ALBUM_PEAK:
+ return ID3v2_GetTXXX(id3v2_tag, "replaygain_album_peak", index, value);
+ }
+
+ return NErr_Unknown;
+}
+
+static int IncSafe(const char *&value, size_t &value_length, size_t increment_length)
+{
+ /* eat leading spaces */
+ while (*value == ' ' && value_length)
+ {
+ value++;
+ value_length--;
+ }
+
+ if (increment_length > value_length)
+ return NErr_NeedMoreData;
+
+ value += increment_length;
+ value_length -= increment_length;
+ /* eat trailing spaces */
+ while (*value == ' ' && value_length)
+ {
+ value++;
+ value_length--;
+ }
+
+ return NErr_Success;
+}
+
+static int SplitSlashInteger(nx_string_t track, unsigned int *value1, unsigned int *value2)
+{
+ char track_utf8[64];
+ size_t bytes_copied;
+ int ret;
+ ret = NXStringGetBytes(&bytes_copied, track, track_utf8, 64, nx_charset_utf8, nx_string_get_bytes_size_null_terminate);
+ if (ret == NErr_Success)
+ {
+ size_t len = strcspn(track_utf8, "/");
+
+ if (track_utf8[len])
+ *value2 = strtoul(&track_utf8[len+1], 0, 10);
+ else
+ *value2 = 0;
+
+ track_utf8[len]=0;
+ *value1 = strtoul(track_utf8, 0, 10);
+
+ return NErr_Success;
+ }
+
+ return ret;
+}
+
+int ID3v2Metadata::Metadata_GetInteger(int field, unsigned int index, int64_t *value)
+{
+ if (!id3v2_tag)
+ return NErr_Unknown;
+
+ switch(field)
+ {
+ case MetadataKeys::TRACK:
+ {
+ ReferenceCountedNXString track;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track);
+ if (ret == NErr_Success)
+ {
+ unsigned int itrack, itracks;
+ ret = SplitSlashInteger(track, &itrack, &itracks);
+ if (ret == NErr_Success)
+ {
+ if (itrack == 0)
+ return NErr_Empty;
+
+ *value = itrack;
+ return NErr_Success;
+
+ }
+ }
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::TRACKS:
+ {
+ ReferenceCountedNXString track;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, &track);
+ if (ret == NErr_Success)
+ {
+ unsigned int itrack, itracks;
+ ret = SplitSlashInteger(track, &itrack, &itracks);
+ if (ret == NErr_Success)
+ {
+ if (itracks == 0)
+ return NErr_Empty;
+
+ *value = itracks;
+ return NErr_Success;
+ }
+ }
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::DISC:
+ {
+ ReferenceCountedNXString track;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track);
+ if (ret == NErr_Success)
+ {
+ unsigned int idisc, idiscs;
+ ret = SplitSlashInteger(track, &idisc, &idiscs);
+ if (ret == NErr_Success)
+ {
+ if (idisc == 0)
+ return NErr_Empty;
+
+ *value = idisc;
+ return NErr_Success;
+
+ }
+ }
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::DISCS:
+ {
+ ReferenceCountedNXString track;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, &track);
+ if (ret == NErr_Success)
+ {
+ unsigned int idisc, idiscs;
+ ret = SplitSlashInteger(track, &idisc, &idiscs);
+ if (ret == NErr_Success)
+ {
+ if (idiscs == 0)
+ return NErr_Empty;
+
+ *value = idiscs;
+ return NErr_Success;
+
+ }
+ }
+ return ret;
+ }
+ break;
+
+ case MetadataKeys::BPM:
+ {
+ ReferenceCountedNXString bpm;
+ int ret;
+ ret = ID3v2_GetText(id3v2_tag, NSID3V2_FRAME_BPM, index, &bpm);
+ if (ret == NErr_Success)
+ {
+ /* TODO: benski> implement NXStringGetInt64Value */
+ int value32;
+ ret = NXStringGetIntegerValue(bpm, &value32);
+ if (ret != NErr_Success)
+ return ret;
+ *value = value32;
+ return NErr_Success;
+ }
+ return ret;
+ }
+ case MetadataKeys::PREGAP:
+ {
+ ReferenceCountedNXString str;
+ char language[3];
+ int ret = NSID3v2_Tag_Comments_Get(id3v2_tag, "iTunSMPB", language, &str, 0);
+ if (ret == NErr_Success)
+ {
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ const char *itunsmpb;
+ size_t itunsmpb_length;
+ char temp[64] = {0};
+ if (NXStringGetCString(str, temp, sizeof(temp)/sizeof(*temp), &itunsmpb, &itunsmpb_length) == NErr_Success)
+ {
+ /* skip first set of meaningless values */
+ if (IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8)
+ {
+ /* read pre-gap */
+ *value = strtoul(itunsmpb, 0, 16);
+ return NErr_Success;
+ }
+ }
+ return NErr_Error;
+ }
+ else
+ return ret;
+
+ }
+ case MetadataKeys::POSTGAP:
+ {
+ ReferenceCountedNXString str;
+ char language[3];
+ int ret = NSID3v2_Tag_Comments_Get(id3v2_tag, "iTunSMPB", language, &str, 0);
+ if (ret == NErr_Success)
+ {
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ const char *itunsmpb;
+ size_t itunsmpb_length;
+ char temp[64] = {0};
+ if (NXStringGetCString(str, temp, sizeof(temp)/sizeof(*temp), &itunsmpb, &itunsmpb_length) == NErr_Success)
+ {
+ /* two separate calls so we can skip spaces properly */
+ if (IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8
+ && IncSafe(itunsmpb, itunsmpb_length, 8) == NErr_Success && itunsmpb_length >= 8)
+ {
+ *value = strtoul(itunsmpb, 0, 16);
+ return NErr_Success;
+ }
+
+ }
+ return NErr_Error;
+ }
+ else
+ return ret;
+
+ }
+ }
+ return NErr_Unknown;
+}
+
+int ID3v2Metadata::Metadata_GetReal(int field, unsigned int index, double *value)
+{
+ if (!id3v2_tag)
+ return NErr_Unknown;
+
+ int ret;
+ nx_string_t str;
+ switch (field)
+ {
+ case MetadataKeys::TRACK_GAIN:
+ ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_track_gain", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::TRACK_PEAK:
+ ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_track_peak", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::ALBUM_GAIN:
+ ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_album_gain", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ case MetadataKeys::ALBUM_PEAK:
+ ret = ID3v2_GetTXXX(id3v2_tag, "replaygain_album_peak", index, &str);
+ if (ret == NErr_Success)
+ {
+ ret = NXStringGetDoubleValue(str, value);
+ NXStringRelease(str);
+ }
+ return ret;
+ }
+ return NErr_Unknown;
+}
+
+static int ArtLookupType(uint8_t *id3v2_type, int metadata_key)
+{
+ switch(metadata_key)
+ {
+ case MetadataKeys::ALBUM:
+ *id3v2_type = 3;
+ return NErr_Success;
+ }
+ return NErr_Unknown;
+}
+
+static int NXStringCreateWithMIME(nx_string_t *mime_type, nx_string_t in)
+{
+ if (!mime_type)
+ return NErr_Success;
+
+ char temp[128];
+ size_t copied;
+ int ret = NXStringGetBytes(&copied, in, temp, 128, nx_charset_ascii, nx_string_get_bytes_size_null_terminate);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (strstr(temp, "/") != 0)
+ {
+ *mime_type = NXStringRetain(in);
+ return NErr_Success;
+ }
+ else
+ {
+ char temp2[128];
+#ifdef _WIN32
+ _snprintf(temp2, 127, "image/%s", temp);
+#else
+ snprintf(temp2, 127, "image/%s", temp);
+#endif
+ temp2[127]=0;
+
+ return NXStringCreateWithUTF8(mime_type, temp2);
+ }
+}
+
+int ID3v2Metadata::Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags)
+{
+ if (!id3v2_tag)
+ return NErr_Unknown;
+
+ uint8_t id3v2_picture_type;
+ int ret = ArtLookupType(&id3v2_picture_type, field);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (!id3v2_tag)
+ return NErr_Empty;
+
+ bool found_one=false;
+ nsid3v2_frame_t frame=0;
+ ret = NSID3v2_Tag_GetFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, &frame);
+ if (ret != NErr_Success)
+ return ret;
+
+ for (;;)
+ {
+ uint8_t this_type;
+ if (NSID3v2_Frame_Picture_Get(frame, 0, &this_type, 0, 0, 0, 0) == NErr_Success && (this_type == id3v2_picture_type || (id3v2_picture_type == 3 && this_type == 0)))
+ {
+ found_one=true;
+ if (index == 0)
+ {
+
+ if (artwork)
+ {
+ nx_data_t data=0;
+
+ if (flags != DATA_FLAG_NONE)
+ {
+ const void *picture_data;
+ size_t picture_length;
+ ReferenceCountedNXString mime_local, description;
+
+ ret = NSID3v2_Frame_Picture_Get(frame, TestFlag(flags, DATA_FLAG_MIME)?(&mime_local):0, &this_type, TestFlag(flags, DATA_FLAG_DESCRIPTION)?(&description):0, &picture_data, &picture_length, 0);
+ if (ret != NErr_Success)
+ return ret;
+
+ if (TestFlag(flags, DATA_FLAG_DATA))
+ {
+ ret = NXDataCreate(&data, picture_data, picture_length);
+ if (ret != NErr_Success)
+ return ret;
+ }
+ else
+ {
+ ret = NXDataCreateEmpty(&data);
+ if (ret != NErr_Success)
+ return ret;
+ }
+
+ if (mime_local)
+ {
+ ReferenceCountedNXString mime_type;
+ ret = NXStringCreateWithMIME(&mime_type, mime_local);
+ if (ret != NErr_Success)
+ {
+ NXDataRelease(data);
+ return ret;
+ }
+ NXDataSetMIME(data, mime_type);
+ }
+
+ if (description)
+ {
+ NXDataSetDescription(data, description);
+ }
+ }
+ artwork->data = data;
+ /* id3v2 doesn't store height and width, so zero these */
+ artwork->width=0;
+ artwork->height=0;
+ }
+ return NErr_Success;
+ }
+ else
+ {
+ index--; // keep looking
+ }
+ }
+
+ if (NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &frame) != NErr_Success)
+ {
+ if (found_one)
+ return NErr_EndOfEnumeration;
+ else
+ return NErr_Empty;
+ }
+ }
+}
+
+static int SetText(nsid3v2_tag_t id3v2_tag, int frame_id, unsigned int index, nx_string_t value)
+{
+ if (index > 0)
+ return NErr_Success;
+
+ if (!value)
+ {
+ nsid3v2_frame_t frame;
+ if (NSID3v2_Tag_GetFrame(id3v2_tag, frame_id, &frame) == NErr_Success)
+ {
+ for(;;)
+ {
+ nsid3v2_frame_t next;
+ int ret = NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &next);
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ if (ret != NErr_Success)
+ break;
+ frame=next;
+ }
+ }
+ return NErr_Success;
+ }
+ else
+ {
+ return NSID3v2_Tag_Text_Set(id3v2_tag, frame_id, value, 0);
+ }
+}
+
+static int SetTXXX(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t value, int text_flags)
+{
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ if (!value)
+ {
+ nsid3v2_frame_t frame;
+ for(;;)
+ {
+ if (NSID3v2_Tag_TXXX_Find(id3v2_tag, description, &frame, text_flags) == NErr_Success)
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ else
+ return NErr_Success;
+ }
+ }
+ else
+ {
+ return NSID3v2_Tag_TXXX_Set(id3v2_tag, description, value, 0);
+ }
+}
+
+static int SetComments(nsid3v2_tag_t id3v2_tag, const char *description, unsigned int index, nx_string_t value, int text_flags)
+{
+ if (index > 0)
+ return NErr_EndOfEnumeration;
+
+ if (!value)
+ {
+ nsid3v2_frame_t frame;
+ for(;;)
+ {
+ if (NSID3v2_Tag_Comments_Find(id3v2_tag, description, &frame, text_flags) == NErr_Success)
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ else
+ return NErr_Success;
+ }
+ }
+ else
+ {
+ return NSID3v2_Tag_Comments_Set(id3v2_tag, description, "\0\0\0", value, 0);
+ }
+}
+
+int ID3v2Metadata::MetadataEditor_SetField(int field, unsigned int index, nx_string_t value)
+{
+ int ret;
+
+ switch (field)
+ {
+ case MetadataKeys::ARTIST:
+ return SetText(id3v2_tag, NSID3V2_FRAME_LEADARTIST, index, value);
+
+ case MetadataKeys::ALBUM_ARTIST:
+ ret = SetText(id3v2_tag, NSID3V2_FRAME_BAND, index, value);
+ /* delete some of the alternates */
+ SetTXXX(id3v2_tag, "ALBUM ARTIST", index, 0, 0); /* foobar 2000 style */
+ SetTXXX(id3v2_tag, "ALBUMARTIST", index, 0, 0); /* mp3tag style */
+
+ if (!value) /* this might be a valid field, so only delete it if we're specifically deleting album artist (because otherwise, if it's here it's going to get picked up by GetField */
+ SetTXXX(id3v2_tag, "Band", index, 0, 0); /* audacity style */
+
+ return ret;
+
+ case MetadataKeys::ALBUM:
+ return SetText(id3v2_tag, NSID3V2_FRAME_ALBUM, index, value);
+
+ case MetadataKeys::TITLE:
+ return SetText(id3v2_tag, NSID3V2_FRAME_TITLE, index, value);
+
+ case MetadataKeys::GENRE:
+ return SetText(id3v2_tag, NSID3V2_FRAME_CONTENTTYPE, index, value);
+
+ case MetadataKeys::YEAR:
+ /* try to set "newer" style TDRC, first */
+ ret = SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, value);
+ if (ret == NErr_Success)
+ {
+ /* if it succeeded, remove the older TYER tag */
+ SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, 0);
+ return ret;
+ }
+
+ /* fall back to using TYER */
+ return SetText(id3v2_tag, NSID3V2_FRAME_RECORDINGTIME, index, 0);
+
+ case MetadataKeys::TRACK:
+ return SetText(id3v2_tag, NSID3V2_FRAME_TRACK, index, value);
+
+ case MetadataKeys::DISC:
+ return SetText(id3v2_tag, NSID3V2_FRAME_PARTOFSET, index, value);
+
+ case MetadataKeys::COMPOSER:
+ return SetText(id3v2_tag, NSID3V2_FRAME_COMPOSER, index, value);
+
+ case MetadataKeys::PUBLISHER:
+ return SetText(id3v2_tag, NSID3V2_FRAME_PUBLISHER, index, value);
+
+ case MetadataKeys::BPM:
+ return SetText(id3v2_tag, NSID3V2_FRAME_BPM, index, value);
+
+ case MetadataKeys::COMMENT:
+ return SetComments(id3v2_tag, "", index, value, 0);
+
+ case MetadataKeys::TRACK_GAIN:
+ return SetTXXX(id3v2_tag, "replaygain_track_gain", index, value, 0);
+
+ case MetadataKeys::TRACK_PEAK:
+ return SetTXXX(id3v2_tag, "replaygain_track_peak", index, value, 0);
+
+ case MetadataKeys::ALBUM_GAIN:
+ return SetTXXX(id3v2_tag, "replaygain_album_gain", index, value, 0);
+
+ case MetadataKeys::ALBUM_PEAK:
+ return SetTXXX(id3v2_tag, "replaygain_album_peak", index, value, 0);
+ }
+
+ return NErr_Unknown;
+}
+
+static int ID3v2_SetPicture(nsid3v2_frame_t frame, uint8_t id3v2_picture_type, artwork_t *artwork, data_flags_t flags)
+{
+ int ret;
+
+ const void *picture_data;
+ size_t picture_length;
+ ret = NXDataGet(artwork->data, &picture_data, &picture_length);
+ if (ret != NErr_Success)
+ return ret;
+ ReferenceCountedNXString mime_type, description;
+ if (TestFlag(flags, DATA_FLAG_MIME))
+ NXDataGetMIME(artwork->data, &mime_type);
+ if (TestFlag(flags, DATA_FLAG_DESCRIPTION))
+ NXDataGetDescription(artwork->data, &description);
+ return NSID3v2_Frame_Picture_Set(frame, mime_type, id3v2_picture_type, description, picture_data, picture_length, 0);
+}
+
+int ID3v2Metadata::MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags)
+{
+ uint8_t id3v2_picture_type;
+ int ret = ArtLookupType(&id3v2_picture_type, field);
+ if (ret != NErr_Success)
+ return ret;
+
+
+ nsid3v2_frame_t frame=0;
+ ret = NSID3v2_Tag_GetFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, &frame);
+ if (ret != NErr_Success)
+ {
+ if (artwork && artwork->data)
+ {
+ /* create a new one and store */
+ int ret = NSID3v2_Tag_CreateFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, 0, &frame);
+ if (ret == NErr_Success)
+ {
+ ret = ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags);
+ if (ret == NErr_Success)
+ {
+ ret = NSID3v2_Tag_AddFrame(id3v2_tag, frame);
+ if (ret != NErr_Success)
+ {
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ }
+ }
+ }
+ return ret;
+ }
+ else
+ return NErr_Success;
+ }
+
+ for (;;)
+ {
+ /* iterate now, because we might delete the current frame */
+ nsid3v2_frame_t next_frame=0;
+ if (NSID3v2_Tag_GetNextFrame(id3v2_tag, frame, &next_frame) != NErr_Success)
+ next_frame=0; /* just in case */
+
+ uint8_t this_type;
+ if (NSID3v2_Frame_Picture_Get(frame, 0, &this_type, 0, 0, 0, 0) == NErr_Success && (this_type == id3v2_picture_type || (id3v2_picture_type == 3 && this_type == 0)))
+ {
+ if (index == 0)
+ {
+ if (artwork && artwork->data)
+ {
+ return ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags);
+ }
+ else
+ {
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ }
+ }
+ else
+ {
+ index--; // keep looking
+ }
+ }
+
+ if (!next_frame)
+ {
+ if (!artwork || !artwork->data)
+ return NErr_Success;
+ else
+ {
+ /* create a new one and store */
+ int ret = NSID3v2_Tag_CreateFrame(id3v2_tag, NSID3V2_FRAME_PICTURE, 0, &frame);
+ if (ret != NErr_Success)
+ return ret;
+
+ ret = ID3v2_SetPicture(frame, id3v2_picture_type, artwork, flags);
+ if (ret != NErr_Success)
+ return ret;
+ ret = NSID3v2_Tag_AddFrame(id3v2_tag, frame);
+ if (ret != NErr_Success)
+ {
+ NSID3v2_Tag_RemoveFrame(id3v2_tag, frame);
+ }
+ return ret;
+ }
+ }
+ frame = next_frame;
+ }
+
+ return NErr_NotImplemented;
+}
diff --git a/Src/replicant/nswasabi/ID3v2Metadata.h b/Src/replicant/nswasabi/ID3v2Metadata.h
new file mode 100644
index 00000000..9fdf23b3
--- /dev/null
+++ b/Src/replicant/nswasabi/ID3v2Metadata.h
@@ -0,0 +1,34 @@
+#pragma once
+#include "metadata/metadata.h"
+#include "nsid3v2/nsid3v2.h"
+
+/* this class mimics ifc_metadata and ifc_metadata_editor, but doesn't inherit (because it's not given out directly) */
+class ID3v2Metadata
+{
+public:
+ ID3v2Metadata();
+ ~ID3v2Metadata();
+
+ static int Initialize(api_metadata *metadata_api);
+ int Initialize(nsid3v2_tag_t tag);
+
+ /* ifc_metadata implementation */
+ int WASABICALL Metadata_GetField(int field, unsigned int index, nx_string_t *value);
+ int WASABICALL Metadata_GetInteger(int field, unsigned int index, int64_t *value);
+ int WASABICALL Metadata_GetReal(int field, unsigned int index, double *value);
+ int WASABICALL Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags);
+
+ /* ifc_metadata_editor implementation */
+ int WASABICALL MetadataEditor_SetField(int field, unsigned int index, nx_string_t value);
+ int WASABICALL MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags);
+private:
+ nsid3v2_tag_t id3v2_tag;
+
+ int GetGenre(int index, nx_string_t *value);
+
+ static api_metadata *metadata_api;
+
+#ifdef __APPLE__
+ CFNumberFormatterRef number_formatter;
+#endif
+}; \ No newline at end of file
diff --git a/Src/replicant/nswasabi/Makefile b/Src/replicant/nswasabi/Makefile
new file mode 100644
index 00000000..2a44c708
--- /dev/null
+++ b/Src/replicant/nswasabi/Makefile
@@ -0,0 +1,48 @@
+MODULE_NAME := nswasabi
+
+CPPSOURCES := ComponentManagerBase.cpp ApplicationBase.cpp ID3v2Metadata.cpp ID3v1Metadata.cpp PlaybackBase.cpp APEv2Metadata.cpp
+#CSOURCES := utf.c ByteReader.c ByteWriter.c
+
+LIBRARY_FILENAME := lib$(MODULE_NAME).a
+OUTPUT_PATH := ../build/$(MODULE_NAME)
+ARCHIVE_PATH := ../build/lib
+LIBRARY_FILEPATH := ../build/lib/$(LIBRARY_FILENAME)
+
+CPPOBJS := $(patsubst %.cpp,$(OUTPUT_PATH)/%.o,$(CPPSOURCES))
+CPPDEPS := $(patsubst %.o,$(OUTPUT_PATH)/%.d,$(CPPOBJS))
+COBJS := $(patsubst %.c,$(OUTPUT_PATH)/%.o,$(CSOURCES))
+CDEPS := $(patsubst %.o,$(OUTPUT_PATH)/%.d,$(COBJS))
+
+OBJS := $(CPPOBJS) $(COBJS)
+DEPS := $(CPPDEPS) $(CDEPS)
+
+CFLAGS=-I.. -fPIC #-fvisibility=hidden
+CPPFLAGS := ${CFLAGS}
+
+
+
+build: build-dir $(LIBRARY_FILEPATH)
+
+build-dir:
+ @mkdir -p $(OUTPUT_PATH)/linux > /dev/null 2> /dev/null
+ @mkdir -p $(OUTPUT_PATH) > /dev/null 2> /dev/null
+ @mkdir -p $(ARCHIVE_PATH) > /dev/null 2> /dev/null
+
+dep:
+ @rm ${DEPS}
+
+$(OUTPUT_PATH)/%.o: %.cpp
+ #@echo Compiling $*.cpp
+ @$(CXX) $(CPPFLAGS) -MMD -MF $(OUTPUT_PATH)/$*.d -MT $(OUTPUT_PATH)/$*.o -c $*.cpp -o $(OUTPUT_PATH)/$*.o
+
+$(OUTPUT_PATH)/%.o: %.c
+ #@echo Compiling $*.c
+ @$(CC) $(CFLAGS) -MMD -MF $(OUTPUT_PATH)/$*.d -MT $(OUTPUT_PATH)/$*.o -c $*.c -o $(OUTPUT_PATH)/$*.o
+
+$(LIBRARY_FILEPATH): ${OBJS}
+ @$(AR) rcs $@ ${OBJS}
+
+clean:
+ -rm -f ${OBJS} $(LIBRARY_FILENAME) ${DEPS}
+
+-include $(DEPS)
diff --git a/Src/replicant/nswasabi/MetadataChain.h b/Src/replicant/nswasabi/MetadataChain.h
new file mode 100644
index 00000000..cc6de698
--- /dev/null
+++ b/Src/replicant/nswasabi/MetadataChain.h
@@ -0,0 +1,97 @@
+#pragma once
+#include "metadata/ifc_metadata.h"
+
+
+template <class metadata_t>
+class MetadataChain : public metadata_t
+{
+public:
+ MetadataChain()
+ {
+ parent_metadata=0;
+ }
+
+ ~MetadataChain()
+ {
+ if (parent_metadata)
+ parent_metadata->Release();
+ }
+
+ static bool Unhandled(ns_error_t ret)
+ {
+ return (ret == NErr_NotImplemented || ret == NErr_Empty || ret == NErr_Unknown);
+ }
+
+ ns_error_t SetParentMetadata(ifc_metadata *new_parent_metadata)
+ {
+ if (parent_metadata)
+ parent_metadata->Release();
+ parent_metadata=new_parent_metadata;
+ if (parent_metadata)
+ parent_metadata->Retain();
+ return NErr_Success;
+ }
+protected:
+ ifc_metadata *parent_metadata;
+private:
+ ns_error_t WASABICALL Metadata_GetField(int field, unsigned int index, nx_string_t *value)
+ {
+ ns_error_t ret = metadata_t::Metadata_GetField(field, index, value);
+ if (Unhandled(ret) && parent_metadata)
+ return parent_metadata->GetField(field, index, value);
+ else
+ return ret;
+
+ }
+
+ ns_error_t WASABICALL Metadata_GetInteger(int field, unsigned int index, int64_t *value)
+ {
+ ns_error_t ret = metadata_t::Metadata_GetInteger(field, index, value);
+ if (Unhandled(ret) && parent_metadata)
+ return parent_metadata->GetInteger(field, index, value);
+ else
+ return ret;
+
+ }
+
+ ns_error_t WASABICALL Metadata_GetReal(int field, unsigned int index, double *value)
+ {
+ ns_error_t ret = metadata_t::Metadata_GetReal(field, index, value);
+ if (Unhandled(ret) && parent_metadata)
+ return parent_metadata->GetReal(field, index, value);
+ else
+ return ret;
+
+ }
+
+ ns_error_t WASABICALL Metadata_GetArtwork(int field, unsigned int index, artwork_t *artwork, data_flags_t flags)
+ {
+ ns_error_t ret = metadata_t::Metadata_GetArtwork(field, index, artwork, flags);
+ if (Unhandled(ret) && parent_metadata)
+ return parent_metadata->GetArtwork(field, index, artwork, flags);
+ else
+ return ret;
+
+ }
+
+ ns_error_t WASABICALL Metadata_GetBinary(int field, unsigned int index, nx_data_t *data)
+ {
+ ns_error_t ret = metadata_t::Metadata_GetBinary(field, index, data);
+ if (Unhandled(ret) && parent_metadata)
+ return parent_metadata->GetBinary(field, index, data);
+ else
+ return ret;
+
+ }
+
+ ns_error_t WASABICALL Metadata_GetMetadata(int field, unsigned int index, ifc_metadata **metadata)
+ {
+ ns_error_t ret = metadata_t::Metadata_GetMetadata(field, index, metadata);
+ if (Unhandled(ret) && parent_metadata)
+ return parent_metadata->GetMetadata(field, index, metadata);
+ else
+ return ret;
+
+ }
+ // TODO: ns_error_t WASABICALL Metadata_Serialize(nx_data_t *data) { return NErr_NotImplemented; }
+};
diff --git a/Src/replicant/nswasabi/MetadataEditorChain.h b/Src/replicant/nswasabi/MetadataEditorChain.h
new file mode 100644
index 00000000..7142acca
--- /dev/null
+++ b/Src/replicant/nswasabi/MetadataEditorChain.h
@@ -0,0 +1,100 @@
+#pragma once
+#include "metadata/ifc_metadata_editor.h"
+
+
+template <class metadata_t>
+class MetadataEditorChain : public metadata_t
+{
+public:
+ MetadataEditorChain()
+ {
+ parent_metadata=0;
+ }
+
+ ~MetadataEditorChain()
+ {
+ if (parent_metadata)
+ parent_metadata->Release();
+ }
+
+ static bool Unhandled(ns_error_t ret)
+ {
+ return (ret == NErr_NotImplemented || ret == NErr_Empty || ret == NErr_Unknown);
+ }
+
+ ns_error_t SetParentMetadata(ifc_metadata_editor *new_parent_metadata)
+ {
+ if (parent_metadata)
+ parent_metadata->Release();
+ parent_metadata=new_parent_metadata;
+ if (parent_metadata)
+ parent_metadata->Retain();
+ return NErr_Success;
+ }
+protected:
+ ifc_metadata_editor *parent_metadata;
+private:
+ int WASABICALL MetadataEditor_Save()
+ {
+ if (parent_metadata)
+ return parent_metadata->Save();
+ else
+ return NErr_NotImplemented;
+ }
+
+ int WASABICALL MetadataEditor_SetField(int field, unsigned int index, nx_string_t value)
+ {
+ bool known=false;
+ if (parent_metadata && parent_metadata->SetField(field, index, value) == NErr_Success)
+ known=true;
+ if (metadata_t::MetadataEditor_SetField(field, index, value) == NErr_Success)
+ known=true;
+
+ if (known)
+ return NErr_Success;
+ else
+ return NErr_Unknown;
+ }
+
+ int WASABICALL MetadataEditor_SetInteger(int field, unsigned int index, int64_t value)
+ {
+ bool known=false;
+ if (parent_metadata && parent_metadata->SetInteger(field, index, value) == NErr_Success)
+ known=true;
+ if (metadata_t::MetadataEditor_SetInteger(field, index, value) == NErr_Success)
+ known=true;
+
+ if (known)
+ return NErr_Success;
+ else
+ return NErr_Unknown;
+ }
+
+ int WASABICALL MetadataEditor_SetReal(int field, unsigned int index, double value)
+ {
+ bool known=false;
+ if (parent_metadata && parent_metadata->SetReal(field, index, value) == NErr_Success)
+ known=true;
+ if (metadata_t::MetadataEditor_SetReal(field, index, value) == NErr_Success)
+ known=true;
+
+ if (known)
+ return NErr_Success;
+ else
+ return NErr_Unknown;
+ }
+
+ int WASABICALL MetadataEditor_SetArtwork(int field, unsigned int index, artwork_t *data, data_flags_t flags)
+ {
+ bool known=false;
+ if (parent_metadata && parent_metadata->SetArtwork(field, index, data, flags) == NErr_Success)
+ known=true;
+ if (metadata_t::MetadataEditor_SetArtwork(field, index, data, flags) == NErr_Success)
+ known=true;
+
+ if (known)
+ return NErr_Success;
+ else
+ return NErr_Unknown;
+ }
+}; \ No newline at end of file
diff --git a/Src/replicant/nswasabi/ObjectFactory.h b/Src/replicant/nswasabi/ObjectFactory.h
new file mode 100644
index 00000000..55a99820
--- /dev/null
+++ b/Src/replicant/nswasabi/ObjectFactory.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "service/ifc_servicefactory.h"
+#include "ReferenceCounted.h"
+/*
+====== Usage ======
+disp_t: your Dispatchable base class
+implt_t: your implementation class
+
+ObjectFactory<disp_t, impl_t> myFactory;
+impl_t myImplementation;
+
+//....
+
+//during service registration
+myFactory.Register(WASABI2_API_SVC);
+
+//during service deregistration
+myFactory.Deregister(WASABI2_API_SVC);
+
+==== Class requirements ====
+your base or implementation class requires the following three static methods
+static FOURCC getServiceType(); // return your type (e.g. WaSvc::OBJECT)... might already be defined in the dispatchable base class
+static const char *getServiceName(); // return your service name
+static GUID getServiceGuid(); // return your service GUID
+*/
+
+template <class impl_t, class disp_t>
+class CountableObjectFactory : public ifc_serviceFactory
+{
+public:
+ CountableObjectFactory()
+ {
+ }
+
+ ~CountableObjectFactory()
+ {
+ }
+
+ void Register(api_service *serviceManager)
+ {
+ serviceManager->Register(this);
+ }
+
+ void Deregister(api_service *serviceManager)
+ {
+ serviceManager->Unregister(this);
+ }
+
+private:
+ GUID WASABICALL ServiceFactory_GetServiceType() { return impl_t::GetServiceType(); }
+ nx_string_t WASABICALL ServiceFactory_GetServiceName() { return impl_t::GetServiceName(); }
+ GUID WASABICALL ServiceFactory_GetGUID() { return impl_t::GetServiceGUID(); } // GUID per service factory, can be INVALID_GUID
+ void *WASABICALL ServiceFactory_GetInterface() { return static_cast<disp_t *>(new ReferenceCounted<impl_t>); }
+ int WASABICALL ServiceFactory_ServiceNotify(int msg, intptr_t param1 = 0, intptr_t param2 = 0) { return 0; }
+};
+
diff --git a/Src/replicant/nswasabi/PlaybackBase.cpp b/Src/replicant/nswasabi/PlaybackBase.cpp
new file mode 100644
index 00000000..d3a94a6e
--- /dev/null
+++ b/Src/replicant/nswasabi/PlaybackBase.cpp
@@ -0,0 +1,409 @@
+#include "PlaybackBase.h"
+#include <stdlib.h>
+#include <assert.h>
+#ifdef __ANDROID__
+#include <android/log.h> // TODO: replace with generic logging API
+
+#else
+#define ANDROID_LOG_INFO 0
+#define ANDROID_LOG_ERROR 1
+static void __android_log_print(int, const char *, const char *, ...)
+{
+}
+#endif
+PlaybackBase::PlaybackBase()
+{
+ wake_flags=0;
+ last_wake_flags=0;
+ playback_thread=0;
+ secondary_parameters=0;
+ filename=0;
+ player=0;
+ queued_seek=0;
+ output_service=0;
+}
+
+int PlaybackBase::Initialize(nx_uri_t filename, ifc_player *player)
+{
+ this->player = player;
+ this->filename = NXURIRetain(filename);
+ return NErr_Success;
+}
+
+PlaybackBase::~PlaybackBase()
+{
+ if (secondary_parameters)
+ secondary_parameters->Release();
+ if (filename)
+ NXURIRelease(filename);
+ if (queued_seek)
+ free(queued_seek);
+ if (playback_thread)
+ NXThreadJoin(playback_thread, 0);
+}
+
+int PlaybackBase::Playback_Play(svc_output *output, ifc_playback_parameters *secondary_parameters)
+{
+ if (!playback_thread)
+ return 1;
+
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Play");
+
+ output_service = output;
+ threadloop_node_t *apc = thread_loop.GetAPC();
+ if (apc)
+ {
+ this->secondary_parameters = secondary_parameters;
+ if (secondary_parameters)
+ secondary_parameters->Retain();
+
+ apc->func = APC_Play;
+ apc->param1 = this;
+ thread_loop.Schedule(apc);
+ return NErr_Success;
+ }
+ else
+ return NErr_OutOfMemory;
+}
+
+int PlaybackBase::Playback_SeekSeconds(double seconds)
+{
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Seek (%f seconds)", seconds);
+ Agave_Seek *seek = (Agave_Seek *)malloc(sizeof(Agave_Seek));
+ if (seek)
+ {
+ seek->position_type = AGAVE_PLAYPOSITION_SECONDS;
+ seek->position.seconds = seconds;
+ threadloop_node_t *apc = thread_loop.GetAPC();
+ if (apc)
+ {
+ apc->func = APC_Seek;
+ apc->param1 = this;
+ apc->param2 = seek;
+ thread_loop.Schedule(apc);
+ return NErr_Success;
+ }
+ }
+ free(seek);
+ return NErr_OutOfMemory;
+
+}
+
+int PlaybackBase::Playback_Pause()
+{
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Pause");
+ threadloop_node_t *apc = thread_loop.GetAPC();
+ if (apc)
+ {
+ apc->func = APC_Pause;
+ apc->param1 = this;
+ thread_loop.Schedule(apc);
+ return NErr_Success;
+ }
+ else
+ return NErr_OutOfMemory;
+}
+
+int PlaybackBase::Playback_Unpause()
+{
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Unpause");
+ threadloop_node_t *apc = thread_loop.GetAPC();
+ if (apc)
+ {
+ apc->func = APC_Unpause;
+ apc->param1 = this;
+ thread_loop.Schedule(apc);
+ return NErr_Success;
+ }
+ else
+ return NErr_OutOfMemory;
+}
+
+int PlaybackBase::Playback_Stop()
+{
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Stop");
+ threadloop_node_t *apc = thread_loop.GetAPC();
+ if (apc)
+ {
+ apc->func = APC_Stop;
+ apc->param1 = this;
+ thread_loop.Schedule(apc);
+ return NErr_Success;
+ }
+ else
+ return NErr_OutOfMemory;
+}
+
+int PlaybackBase::Playback_Close()
+{
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Close");
+ threadloop_node_t *apc = thread_loop.GetAPC();
+ if (apc)
+ {
+ apc->func = APC_Close;
+ apc->param1 = this;
+ thread_loop.Schedule(apc);
+ return NErr_Success;
+ }
+ else
+ return NErr_OutOfMemory;
+}
+
+
+int PlaybackBase::FileLockCallback_Interrupt()
+{
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] Interrupt");
+ threadloop_node_t *apc = thread_loop.GetAPC();
+ if (apc)
+ {
+ apc->func = APC_Interrupt;
+ apc->param1 = this;
+ thread_loop.Schedule(apc);
+ return NErr_Success;
+ }
+ else
+ return NErr_OutOfMemory;
+}
+
+void PlaybackBase::ToggleFlags(int wake_reason)
+{
+ switch(wake_reason)
+ {
+ case WAKE_KILL:
+ last_wake_flags ^= WAKE_KILL; /* toggle kill flag */
+ break;
+ case WAKE_STOP:
+ last_wake_flags ^= WAKE_STOP; /* toggle stop flag */
+ break;
+ case WAKE_PLAY:
+ last_wake_flags ^= WAKE_PLAY; /* toggle play flag */
+ break;
+ case WAKE_PAUSE:
+ case WAKE_UNPAUSE:
+ last_wake_flags ^= WAKE_PAUSE; /* toggle pause flag */
+ break;
+ case WAKE_INTERRUPT:
+ last_wake_flags ^= WAKE_INTERRUPT; /* toggle interrupt flag */
+ break;
+ }
+}
+
+int PlaybackBase::WakeReason(int mask) const
+{
+ int reason_awoken = last_wake_flags ^ wake_flags;
+
+ reason_awoken = reason_awoken & mask;
+
+ if (reason_awoken & WAKE_INTERRUPT)
+ {
+ if (wake_flags & WAKE_INTERRUPT)
+ return WAKE_INTERRUPT;
+ else
+ return WAKE_RESUME;
+ }
+
+ if (reason_awoken & WAKE_STOP)
+ {
+ return WAKE_STOP;
+ }
+
+ if (reason_awoken & WAKE_KILL)
+ {
+ return WAKE_KILL;
+ }
+
+ if (reason_awoken & WAKE_PLAY)
+ {
+ if (wake_flags & WAKE_PLAY)
+ return WAKE_PLAY;
+ else /* if someone cleared the play flag for whatever reason, just treat it as a 0 */
+ return 0;
+ }
+
+ if (reason_awoken & WAKE_PAUSE)
+ {
+ if (wake_flags & WAKE_PAUSE)
+ return WAKE_PAUSE;
+ else
+ return WAKE_UNPAUSE;
+ }
+
+ return 0;
+}
+
+int PlaybackBase::Wake(int mask)
+{
+ assert(mask != 0); /* make sure they didn't specify a 0 mask (which would make this function potentially never return */
+ assert((mask & WAKE_ALL_MASK) != 0); /* make sure it's a valid mask */
+
+ for (;;)
+ {
+ int reason_awoken = last_wake_flags ^ wake_flags;
+ reason_awoken = reason_awoken & mask;
+
+ if (reason_awoken)
+ {
+ int ret = WakeReason(mask);
+ ToggleFlags(ret); // mark the last-known-state of the wake flags
+
+ return ret;
+
+ }
+
+ if (((mask & WAKE_PLAY) && !(wake_flags & WAKE_PLAY))/* if we're stopped and they asked to be woken up for play */
+ || ((mask & WAKE_PAUSE) && (wake_flags & WAKE_PAUSE)) /* or waiting to be woken up for unpause */
+ || ((mask & WAKE_INTERRUPT) && (wake_flags & WAKE_INTERRUPT))) /* or waiting to be woken up for resume */
+ {
+ thread_loop.Step();
+
+ int ret = WakeReason(mask);
+ if (ret) /* if ret is !0, it means we gotten woken up for a reason we care about */
+ {
+ ToggleFlags(ret); // mark the last-known-state of the wake flags
+ return ret;
+ }
+ }
+ else /* no reason to sleep, so just return 0 (no change) */
+ {
+ return 0;
+ }
+ }
+}
+
+int PlaybackBase::Check(int mask)
+{
+ assert(mask != 0); /* make sure they didn't specify a 0 mask (which would make this function potentially never return */
+ assert((mask & WAKE_ALL_MASK) != 0); /* make sure it's a valid mask */
+
+ int reason_awoken = last_wake_flags ^ wake_flags;
+ reason_awoken = reason_awoken & mask;
+
+ int ret = 0;
+ if (reason_awoken)
+ {
+ ret = WakeReason(mask);
+ ToggleFlags(ret); // mark the last-known-state of the wake flags
+ }
+ return ret;
+}
+
+
+int PlaybackBase::Wait(unsigned int milliseconds, int mask)
+{
+ int reason_awoken = last_wake_flags ^ wake_flags;
+ reason_awoken = reason_awoken & mask;
+
+ if (reason_awoken)
+ {
+ int ret = WakeReason(mask);
+ ToggleFlags(ret); // mark the last-known-state of the wake flags
+
+ return ret;
+ }
+
+ thread_loop.Step(milliseconds);
+
+ int ret = WakeReason(mask);
+ ToggleFlags(ret); // mark the last-known-state of the wake flags
+ return ret;
+}
+
+int PlaybackBase::Sleep(unsigned int milliseconds, int mask)
+{
+ int reason_awoken = last_wake_flags ^ wake_flags;
+ reason_awoken = reason_awoken & mask;
+
+ if (reason_awoken)
+ {
+ int ret = WakeReason(mask);
+ assert(ret != 0);
+
+ return ret;
+
+ }
+
+ thread_loop.Step(milliseconds);
+
+ int ret = WakeReason(mask);
+ return ret;
+
+}
+
+void PlaybackBase::OnStopPlaying()
+{
+ // turn off the play flag (also adjust old wake flags so we don't trigger a WAKE_STOP)
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] OnStopPlaying");
+
+ wake_flags &= ~WAKE_PLAY;
+ last_wake_flags &= ~WAKE_PLAY;
+}
+
+void PlaybackBase::OnInterrupted()
+{
+ __android_log_print(ANDROID_LOG_INFO, "libreplicant", "[PlaybackBase] OnInterrupted");
+
+ wake_flags &= ~WAKE_INTERRUPT;
+ last_wake_flags &= ~WAKE_INTERRUPT;
+}
+
+Agave_Seek *PlaybackBase::GetSeek()
+{
+ Agave_Seek *seek = queued_seek;
+ queued_seek=0;
+ return seek;
+}
+
+void PlaybackBase::FreeSeek(Agave_Seek *seek)
+{
+ free(seek);
+}
+
+bool PlaybackBase::PendingSeek()
+{
+ return !!queued_seek;
+}
+
+void PlaybackBase::APC_Play(void *_playback_base, void *param2, double real_value)
+{
+ PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
+ playback_base->wake_flags |= WAKE_PLAY;
+}
+
+void PlaybackBase::APC_Seek(void *_playback_base, void *_seek, double real_value)
+{
+ PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
+ Agave_Seek *seek = (Agave_Seek *)_seek;
+ free(playback_base->queued_seek);
+ playback_base->queued_seek = seek;
+
+}
+
+void PlaybackBase::APC_Pause(void *_playback_base, void *param2, double real_value)
+{
+ PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
+ playback_base->wake_flags |= WAKE_PAUSE;
+}
+
+void PlaybackBase::APC_Unpause(void *_playback_base, void *param2, double real_value)
+{
+ PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
+ playback_base->wake_flags &= ~WAKE_PAUSE;
+}
+
+void PlaybackBase::APC_Stop(void *_playback_base, void *param2, double real_value)
+{
+ PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
+ playback_base->wake_flags |= WAKE_STOP;
+}
+
+void PlaybackBase::APC_Close(void *_playback_base, void *param2, double real_value)
+{
+ PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
+ playback_base->wake_flags |= WAKE_KILL;
+}
+
+void PlaybackBase::APC_Interrupt(void *_playback_base, void *param2, double real_value)
+{
+ PlaybackBase *playback_base = (PlaybackBase *)_playback_base;
+ playback_base->wake_flags |= WAKE_INTERRUPT;
+}
diff --git a/Src/replicant/nswasabi/PlaybackBase.h b/Src/replicant/nswasabi/PlaybackBase.h
new file mode 100644
index 00000000..8a27387c
--- /dev/null
+++ b/Src/replicant/nswasabi/PlaybackBase.h
@@ -0,0 +1,91 @@
+#pragma once
+#include "nx/nx.h"
+#include "nu/LockFreeItem.h"
+#include "player/ifc_playback.h"
+#include "player/ifc_player.h"
+#include "player/svc_output.h"
+#include "nu/ThreadLoop.h"
+#include "filelock/api_filelock.h"
+
+/* TODO: we can probably redo this without the mutex, possibly using semaphores */
+class PlaybackBase : public ifc_playback, public cb_filelock
+{
+public:
+ using ifc_playback::Retain;
+ using ifc_playback::Release;
+
+
+ /* ifc_playback implementation */
+ int WASABICALL Playback_Play(svc_output *output, ifc_playback_parameters *secondary_parameters);
+ int WASABICALL Playback_SeekSeconds(double seconds);
+ int WASABICALL Playback_Pause();
+ int WASABICALL Playback_Unpause();
+ int WASABICALL Playback_Stop();
+ int WASABICALL Playback_Close();
+
+ /* cb_filelock implementation */
+ int WASABICALL FileLockCallback_Interrupt();
+protected:
+ nx_thread_t playback_thread;
+ svc_output *output_service;
+ ifc_player *player;
+ ifc_playback_parameters *secondary_parameters;
+ nx_uri_t filename;
+
+ enum
+ {
+ WAKE_KILL=(1<<0),
+ WAKE_PLAY=(1<<1),
+ WAKE_PAUSE=(1<<2),
+ WAKE_STOP=(1<<3),
+ WAKE_INTERRUPT=(1<<4),
+ WAKE_UNPAUSE=(1<<5), // this is actually unused in wake_flags, just used as a return value from Wake/WakeReason
+ WAKE_RESUME=(1<<6), // this is actually unused in wake_flags, just used as a return value from Wake/WakeReason
+ WAKE_START_MASK = WAKE_PLAY|WAKE_STOP,
+ WAKE_KILL_MASK = WAKE_KILL|WAKE_STOP,
+ WAKE_ALL_MASK = WAKE_KILL|WAKE_PLAY|WAKE_PAUSE|WAKE_STOP|WAKE_INTERRUPT,
+ };
+
+
+protected:
+ PlaybackBase();
+ ~PlaybackBase();
+
+ /* === API for derived classes to use === */
+ int Initialize(nx_uri_t filename, ifc_player *player);
+ int Init();
+
+ /* this checks if one of the flags in mask has toggled.
+ if playback is stopped and WAKE_PLAY is in the mask, this will sleep until a flag changes
+ if playback is paused and WAKE_PAUSE is in the mask, this will sleep until a flag changes
+ if both WAKE_PLAY and WAKE_PAUSE are in the mask, this will sleep until either happens
+ returns 0 if no flag changed */
+ int Wake(int mask); /* Sleeps indefinitely until a flag changes */
+ int Check(int mask); /* like Wake() but never actually goes to sleep for any reason */
+ int Wait(unsigned int milliseconds, int mask); /* Sleeps for a limited amount of time for a flag to change */
+ int Sleep(unsigned int milliseconds, int mask); /* unlike Wait, this one does *not* update last flags */
+ int WakeReason(int mask) const;
+
+ void OnInterrupted(); /* turn off the WAKE_INTERRUPT flag */
+ void OnStopPlaying(); /* turn off the WAKE_PLAY flag */
+ bool PendingSeek();
+ Agave_Seek *GetSeek();
+ void FreeSeek(Agave_Seek *seek);
+ /* === End of API for derived classes to use === */
+private:
+ void ToggleFlags(int wake_reason);
+
+ int wake_flags; /* marked volatile so the compiler doesn't cache */
+ int last_wake_flags;
+ Agave_Seek *queued_seek;
+
+ ThreadLoop thread_loop;
+ static void APC_Play(void *_playback_base, void *param2, double real_value);
+ static void APC_Seek(void *_playback_base, void *param2, double real_value);
+ static void APC_Pause(void *_playback_base, void *param2, double real_value);
+ static void APC_Unpause(void *_playback_base, void *param2, double real_value);
+ static void APC_Stop(void *_playback_base, void *param2, double real_value);
+ static void APC_Close(void *_playback_base, void *param2, double real_value);
+ static void APC_Interrupt(void *_playback_base, void *param2, double real_value);
+
+};
diff --git a/Src/replicant/nswasabi/PlaybackBase2.cpp b/Src/replicant/nswasabi/PlaybackBase2.cpp
new file mode 100644
index 00000000..4a924e23
--- /dev/null
+++ b/Src/replicant/nswasabi/PlaybackBase2.cpp
@@ -0,0 +1,490 @@
+#include "PlaybackBase2.h"
+
+PlaybackImpl::PlaybackImpl()
+{
+ playback=0;
+ playback_parameters=0;
+}
+
+PlaybackImpl::~PlaybackImpl()
+{
+ // TODO: decide if we need playback->Release() or not
+ if (playback_parameters)
+ playback_parameters->Release();
+}
+
+void PlaybackImpl::Connect(PlaybackBase2 *playback, ifc_playback_parameters *playback_parameters)
+{
+ this->playback = playback;
+ // TODO: decide if we need playback->Retain() or not
+
+ this->playback_parameters = playback_parameters;
+ if (playback_parameters)
+ playback_parameters->Retain();
+
+}
+
+/* ---------- */
+PlaybackBase2::PlaybackBase2()
+{
+ out=0;
+ implementation=0;
+ filelocker=0;
+ paused=false;
+ last_position=0;
+ output_pointers=0;
+ exact_length=false;
+ memset(&parameters, 0, sizeof(parameters));
+}
+
+PlaybackBase2::~PlaybackBase2()
+{
+ /* out should have hopefully been close already. just in case */
+ if (out)
+ out->Release();
+ out=0;
+
+ if (filelocker)
+ filelocker->Release();
+ delete implementation;
+ free(output_pointers);
+
+}
+
+ns_error_t PlaybackBase2::Initialize(api_service *service_manager, PlaybackImpl *implementation, nx_uri_t filename, ifc_player *player)
+{
+ service_manager->GetService(&filelocker);
+
+ this->implementation = implementation;
+ ns_error_t ret = PlaybackBase::Initialize(filename, player);
+ if (ret != NErr_Success)
+ return ret;
+
+ implementation->Connect(this, secondary_parameters);
+
+ this->ifc_playback::Retain(); /* the thread needs to hold a reference to this object so that it doesn't disappear out from under us */
+ NXThreadCreate(&playback_thread, PlayerThreadFunction, this);
+ return NErr_Success;
+}
+
+nx_thread_return_t PlaybackBase2::PlayerThreadFunction(nx_thread_parameter_t param)
+{
+ PlaybackBase2 *playback = (PlaybackBase2 *)param;
+ NXThreadCurrentSetPriority(NX_THREAD_PRIORITY_PLAYBACK);
+ nx_thread_return_t ret = playback->DecodeLoop();
+ playback->ifc_playback::Release(); /* give up the reference that was acquired before spawning the thread */
+ return ret;
+}
+
+int PlaybackBase2::Init()
+{
+ if (filelocker)
+ filelocker->WaitForReadInterruptable(filename, this);
+
+ ns_error_t ret = implementation->Open(filename);
+ if (ret != NErr_Success)
+ return ret;
+
+ ifc_metadata *metadata;
+ if (implementation->GetMetadata(&metadata) == NErr_Success)
+ {
+ player->SetMetadata(metadata);
+ metadata->Release();
+ }
+ else
+ player->SetMetadata(0);
+
+ player->SetSeekable(implementation->IsSeekable()?1:0);
+
+ double length;
+ ret = implementation->GetLength(&length, &exact_length);
+ if (ret == NErr_Success)
+ player->SetLength(length);
+
+ return NErr_Success;
+}
+
+nx_thread_return_t PlaybackBase2::DecodeLoop()
+{
+ player->OnLoaded(filename);
+
+ int ret = Init();
+ if (ret != NErr_Success)
+ {
+ implementation->Close();
+ if (filelocker)
+ filelocker->UnlockFile(filename);
+ player->OnError(ret);
+ return 0;
+ }
+
+ player->OnReady();
+
+ /* wait for Play (or Stop to abort) */
+ for (;;)
+ {
+ ns_error_t ret = Wake(WAKE_PLAY|WAKE_STOP|WAKE_INTERRUPT);
+ if (ret == WAKE_PLAY)
+ {
+ break;
+ }
+ else if (ret == WAKE_STOP)
+ {
+ player->OnStopped();
+ goto cleanup;
+ }
+ else if (ret == WAKE_INTERRUPT)
+ {
+ ns_error_t ret = Internal_Interrupt();
+ if (ret != NErr_Success)
+ {
+ implementation->Close();
+ player->OnError(ret);
+ goto cleanup;
+ }
+ }
+ }
+
+ /* at this point, we know that PLAY is on */
+ for (;;)
+ {
+ int ret = Check(WAKE_STOP|WAKE_PAUSE|WAKE_INTERRUPT);
+ if (ret == WAKE_PAUSE)
+ {
+ if (out)
+ out->Pause(1);
+ paused=true;
+ continue; /* continue in case there's another wake reason */
+ }
+ else if (ret== WAKE_UNPAUSE)
+ {
+ if (out)
+ out->Pause(0);
+ paused=false;
+ continue; /* continue in case there's another wake reason */
+ }
+ else if (ret == WAKE_STOP)
+ {
+ if (out)
+ {
+ out->Stop();
+ out->Release();
+ out=0;
+ }
+ player->OnStopped();
+ goto cleanup;
+ }
+ else if (ret == WAKE_INTERRUPT)
+ {
+ ns_error_t ret = Internal_Interrupt();
+ if (ret != NErr_Success)
+ {
+ implementation->Close();
+ player->OnError(ret);
+ goto cleanup;
+ }
+ continue;
+ }
+
+ Agave_Seek *seek = PlaybackBase::GetSeek();
+ if (seek)
+ {
+ ns_error_t seek_error;
+ double new_position;
+ ns_error_t ret = implementation->Seek(seek, &seek_error, &new_position);
+ if (ret != NErr_Success)
+ {
+ player->OnError(ret);
+ goto cleanup;
+ }
+ if (out)
+ out->Flush(new_position);
+ player->OnSeekComplete(seek_error, new_position);
+ PlaybackBase::FreeSeek(seek);
+ }
+
+ ret = implementation->DecodeStep();
+ if (ret == NErr_EndOfFile)
+ {
+ if (out)
+ out->Done();
+
+ PlaybackBase::OnStopPlaying();
+ player->OnEndOfFile();
+
+ ret = WaitForClose();
+ if (out)
+ out->Release();
+ out=0;
+
+ if (ret != NErr_True)
+ goto cleanup;
+ }
+ else if (ret != NErr_Success)
+ {
+ if (out)
+ {
+ out->Done();
+ out->Release();
+ out=0;
+ }
+ if (ret != NErr_False)
+ player->OnError(NErr_Error); // TODO: find better error code
+ goto cleanup;
+ }
+ else
+ {
+ if (!exact_length)
+ {
+ double length;
+ ret = implementation->GetLength(&length, &exact_length);
+ if (ret == NErr_Success)
+ player->SetLength(length);
+ }
+ }
+ }
+
+cleanup:
+ implementation->Close();
+
+ if (filelocker)
+ filelocker->UnlockFile(filename);
+ return 0;
+}
+
+ns_error_t PlaybackBase2::WaitForClose()
+{
+ if (!out)
+ {
+ player->OnClosed();
+ return NErr_False;
+ }
+ else for (;;)
+ {
+ int ret = Wait(10, WAKE_PLAY|WAKE_KILL|WAKE_STOP);
+ if (ret == WAKE_KILL)
+ {
+ player->OnClosed();
+ return NErr_False;
+ }
+ else if (ret == WAKE_PLAY)
+ {
+ return NErr_True;
+ }
+ else if (ret == WAKE_STOP)
+ {
+ player->OnStopped();
+ return NErr_False;
+ }
+ else
+ {
+ if (out->Playing() == NErr_True)
+ player->SetPosition(last_position - out->Latency());
+ else
+ {
+ player->SetPosition(last_position);
+ player->OnClosed();
+ return NErr_False;
+ }
+ }
+ }
+}
+
+ns_error_t PlaybackBase2::OpenOutput(const ifc_audioout::Parameters *_parameters)
+{
+ // if out is already set, it means that there was a change in parameters, so we'll start a new stream
+ if (out)
+ {
+ // check to see that the parameters actually changed
+ if (!memcmp(&parameters, _parameters, sizeof(parameters)))
+ return NErr_Success;
+
+ out->Done();
+ out=0;
+ }
+
+ parameters = *_parameters;
+
+ free(output_pointers);
+ output_pointers = (const uint8_t **)malloc(parameters.audio.number_of_channels * sizeof(const uint8_t *));
+ if (!output_pointers)
+ return NErr_OutOfMemory;
+
+ ns_error_t ret = output_service->AudioOpen(&parameters, player, secondary_parameters, &out);
+ if (ret != NErr_Success)
+ {
+ player->OnError(ret);
+ return ret;
+ }
+
+ if (paused)
+ out->Pause(1);
+ else
+ out->Pause(0);
+
+
+ return NErr_True;
+}
+
+int PlaybackBase2::OutputNonInterleaved(const void *decode_buffer, size_t decoded, double start_position)
+{
+ int ret;
+ size_t frames_written=0;
+ const uint8_t **buffer = (const uint8_t **)decode_buffer;
+
+ for (size_t c=0;c<parameters.audio.number_of_channels;c++)
+ {
+ output_pointers[c] = buffer[c];
+ }
+
+ while (decoded)
+ {
+ size_t to_write = out->CanWrite();
+ if (to_write)
+ {
+ if (decoded < to_write)
+ to_write = decoded;
+
+ ret = out->Output(output_pointers, to_write);
+ if (ret != NErr_Success)
+ {
+ out->Release();
+ out=0;
+ return ret;
+ }
+
+ decoded -= to_write;
+ for (size_t c=0;c<parameters.audio.number_of_channels;c++)
+ {
+ output_pointers[c] += to_write/parameters.audio.number_of_channels;
+ }
+ frames_written += to_write/parameters.audio.number_of_channels;
+ player->SetPosition(start_position + (double)frames_written/parameters.audio.sample_rate - out->Latency());
+ }
+ else
+ {
+ ns_error_t ret = OutputWait();
+ if (ret != NErr_Success)
+ return ret;
+ }
+ }
+ return NErr_True;
+}
+
+int PlaybackBase2::Output(const void *decode_buffer, size_t decoded, double start_position)
+{
+ int ret;
+ size_t frames_written=0;
+ const uint8_t *decode8 = (const uint8_t *)decode_buffer;
+ size_t buffer_position=0;
+ while (decoded)
+ {
+ size_t to_write = out->CanWrite();
+ if (to_write)
+ {
+ if (decoded < to_write)
+ to_write = decoded;
+
+ ret = out->Output(&decode8[buffer_position], to_write);
+ if (ret != NErr_Success)
+ {
+ out->Release();
+ out=0;
+ return ret;
+ }
+
+ decoded -= to_write;
+ buffer_position += to_write;
+ frames_written += to_write/parameters.audio.number_of_channels;
+ player->SetPosition(start_position + (double)frames_written/parameters.audio.sample_rate - out->Latency());
+ }
+ else
+ {
+ ns_error_t ret = OutputWait();
+ if (ret != NErr_Success)
+ return ret;
+ }
+ }
+ return NErr_True;
+}
+
+ns_error_t PlaybackBase2::OutputWait()
+{
+ if (paused)
+ {
+ /* if we're paused, we need to sit and wait until we're eiter unpaused or stopped */
+ for (;;)
+ {
+ int ret = Wake(WAKE_STOP|WAKE_PAUSE|WAKE_INTERRUPT);
+ if (ret == WAKE_STOP)
+ {
+ out->Stop();
+ out->Release();
+ out=0;
+ player->OnStopped();
+ return NErr_False;
+ }
+ else if (ret == WAKE_UNPAUSE)
+ {
+ out->Pause(0);
+ paused=false;
+ break;
+ }
+ else if (ret == WAKE_PAUSE)
+ {
+ out->Pause(1);
+ paused=true;
+ }
+ else if (PlaybackBase::PendingSeek())
+ {
+ return NErr_True;
+ }
+ else if (ret == WAKE_INTERRUPT)
+ {
+ ns_error_t ret = Internal_Interrupt();
+ if (ret != NErr_Success)
+ return ret;
+ }
+ }
+ }
+ else
+ {
+ int ret = Wait(10, WAKE_STOP);
+ if (ret == WAKE_STOP)
+ {
+ out->Stop();
+ out->Release();
+ out=0;
+ player->OnStopped();
+ return NErr_False;
+ }
+ }
+ return NErr_Success;
+}
+
+ns_error_t PlaybackBase2::Internal_Interrupt()
+{
+ Agave_Seek resume_information;
+ implementation->Interrupt(&resume_information);
+ ns_error_t ret = filelocker->UnlockFile(filename);
+ if (ret != NErr_Success)
+ {
+ implementation->Close();
+ return ret;
+ }
+ PlaybackBase::OnInterrupted();
+ if (filelocker)
+ filelocker->WaitForReadInterruptable(filename, this);
+ ret = implementation->Resume(&resume_information);
+
+
+ if (ret != NErr_Success)
+ return ret;
+ ifc_metadata *metadata;
+ if (implementation->GetMetadata(&metadata) == NErr_Success)
+ {
+ player->SetMetadata(metadata);
+ metadata->Release();
+ }
+ return NErr_Success;
+} \ No newline at end of file
diff --git a/Src/replicant/nswasabi/PlaybackBase2.h b/Src/replicant/nswasabi/PlaybackBase2.h
new file mode 100644
index 00000000..5743aa53
--- /dev/null
+++ b/Src/replicant/nswasabi/PlaybackBase2.h
@@ -0,0 +1,78 @@
+#pragma once
+#include "PlaybackBase.h"
+#include "foundation/error.h"
+#include "service/api_service.h"
+/* this one does most of the work for you.
+It assumes blocking I/O and, generally, a simple implementation
+You provide an implementation of PlaybackImpl */
+
+class PlaybackBase2;
+
+class PlaybackImpl
+{
+public:
+ void Connect(PlaybackBase2 *playback, ifc_playback_parameters *playback_parameters);
+
+ /* stuff you need to implement */
+
+ /* destructor. be diligent about closing stuff, can't guarantee that Close() got called beforehand */
+ virtual ~PlaybackImpl();
+
+ virtual ns_error_t Open(nx_uri_t filename)=0;
+ /* you need to handle the possibility that Close gets called more than one time */
+ virtual void Close()=0;
+ virtual bool IsSeekable()=0;
+ /* implementation note: add a reference (Retain) before assigning the value */
+ virtual ns_error_t GetMetadata(ifc_metadata **metadata)=0;
+ /* if you set *exact=false, GetLength will get called after the next DecodeStep */
+ virtual ns_error_t GetLength(double *length, bool *exact)=0;
+ /* only return an error if you're in a state you can't recover from.
+ if you can't seek, then just don't seek and return NErr_Success */
+ virtual ns_error_t Seek(const Agave_Seek *seek, ns_error_t *seek_error, double *new_position)=0;
+ /* return NErr_Success to continue
+ NErr_EndOfFile to indicate a natural end of file
+ otherwise return an error */
+ virtual ns_error_t DecodeStep()=0;
+ /* Save information and close the OS file handle.
+ fill resume_information with whatever information you'll need to resume */
+ virtual ns_error_t Interrupt(Agave_Seek *resume_information)=0;
+ /* During resume, be sure to call player->SetMetadata again */
+ virtual ns_error_t Resume(Agave_Seek *resume_information)=0;
+protected:
+
+ PlaybackBase2 *playback;
+ ifc_playback_parameters *playback_parameters;
+ PlaybackImpl();
+
+};
+
+class PlaybackBase2 : public PlaybackBase
+{
+public:
+ PlaybackBase2();
+ ~PlaybackBase2();
+
+ ns_error_t Initialize(api_service *service_manager, PlaybackImpl *implementation, nx_uri_t filename, ifc_player *player);
+
+ /* this two should ONLY be called from within your DecodeStep function! */
+ ns_error_t OpenOutput(const ifc_audioout::Parameters *parameters); // if this returns an error, return it from DecodeStep()
+ ns_error_t Output(const void *audio_data, size_t audio_data_length, double begin_position_seconds); // if this returns an error, return it from DecodeStep()
+ ns_error_t OutputNonInterleaved(const void *audio_data, size_t audio_data_length, double begin_position_seconds); // if this returns an error, return it from DecodeStep()
+
+private:
+ static nx_thread_return_t NXTHREADCALL PlayerThreadFunction(nx_thread_parameter_t param);
+ nx_thread_return_t NXTHREADCALL DecodeLoop();
+ ns_error_t Init();
+ ns_error_t WaitForClose();
+ ns_error_t OutputWait();
+ ns_error_t Internal_Interrupt();
+
+ PlaybackImpl *implementation;
+ api_filelock *filelocker;
+ ifc_audioout *out;
+ bool paused;
+ double last_position;
+ bool exact_length;
+ ifc_audioout::Parameters parameters;
+ const uint8_t **output_pointers;
+}; \ No newline at end of file
diff --git a/Src/replicant/nswasabi/ReferenceCounted.h b/Src/replicant/nswasabi/ReferenceCounted.h
new file mode 100644
index 00000000..c16b85dc
--- /dev/null
+++ b/Src/replicant/nswasabi/ReferenceCounted.h
@@ -0,0 +1,232 @@
+#pragma once
+#include "../foundation/dispatch.h"
+#include "../foundation/atomics.h"
+#include <new>
+#include "../nx/nxstring.h"
+#include "../nx/nxuri.h"
+
+#define REFERENCE_COUNT_AS(x) size_t Retain() { return x::Retain(); } size_t Release() { return x::Release(); }
+template <class t>
+class ReferenceCounted : public t
+{
+public:
+ ReferenceCounted() { reference_count = 1; }
+protected:
+ /* Dispatchable implementation */
+
+ size_t WASABICALL Dispatchable_Retain()
+ {
+ return nx_atomic_inc(&reference_count);
+ }
+
+ size_t WASABICALL Dispatchable_Release()
+ {
+ if (!reference_count)
+ return reference_count;
+ size_t r = nx_atomic_dec(&reference_count);
+ if (!r)
+ {
+#if defined(__ARM_ARCH_7A__)
+ __asm__ __volatile__ ("dmb" : : : "memory");
+#endif
+ delete(this);
+ }
+ return r;
+ }
+ size_t reference_count;
+};
+
+template <class t>
+class ReferenceCountedBase
+{
+public:
+ ReferenceCountedBase() { reference_count = 1; }
+
+ size_t Retain()
+ {
+ return nx_atomic_inc(&reference_count);
+ }
+
+ size_t Release()
+ {
+ if (!reference_count)
+ return reference_count;
+ size_t r = nx_atomic_dec(&reference_count);
+ if (!r)
+ {
+#if defined(__ARM_ARCH_7A__)
+ __asm__ __volatile__ ("dmb" : : : "memory");
+#endif
+ delete static_cast<t*>(this);
+ }
+ return r;
+ }
+ size_t reference_count;
+};
+
+template <class t>
+class ReferenceCountedObject
+{
+public:
+ ReferenceCountedObject()
+ {
+ ptr = new (std::nothrow) ReferenceCounted<t>;
+ };
+
+ ~ReferenceCountedObject()
+ {
+ if (ptr)
+ ptr->Release();
+ }
+
+ operator t *()
+ {
+ return ptr;
+ }
+
+ t *operator ->()
+ {
+ return ptr;
+ }
+
+ t *ptr;
+};
+
+template <class t>
+class ReferenceCountedPointer
+{
+public:
+ ReferenceCountedPointer()
+ {
+ ptr = 0;
+ };
+
+ ReferenceCountedPointer(t *new_ptr)
+ {
+ ptr = new_ptr;
+ };
+
+ ~ReferenceCountedPointer()
+ {
+ if (ptr)
+ ptr->Release();
+ }
+
+ operator t *()
+ {
+ return ptr;
+ }
+
+ t *operator ->()
+ {
+ return ptr;
+ }
+
+ t **operator &()
+ {
+ // if there's something already in here, we need to release it first
+ if (ptr)
+ ptr->Release();
+ ptr=0;
+ return &ptr;
+ }
+
+ t *operator =(t *new_ptr)
+ {
+ if (ptr)
+ ptr->Release();
+ ptr=0;
+ ptr = new_ptr;
+ return ptr;
+ }
+
+ t *ptr;
+};
+
+class ReferenceCountedNXString
+{
+public:
+ ReferenceCountedNXString()
+ {
+ ptr = 0;
+ }
+
+ ReferenceCountedNXString(const ReferenceCountedNXString &copy)
+ {
+ ptr = NXStringRetain(copy.ptr);
+ }
+
+ ~ReferenceCountedNXString()
+ {
+ NXStringRelease(ptr);
+ }
+
+ operator nx_string_t()
+ {
+ return ptr;
+ }
+
+ nx_string_t *operator &()
+ {
+ // if there's something already in here, we need to release it first
+ if (ptr)
+ NXStringRelease(ptr);
+ ptr=0;
+ return &ptr;
+ }
+
+ nx_string_t operator =(nx_string_t new_ptr)
+ {
+ if (ptr)
+ NXStringRelease(ptr);
+
+ ptr = new_ptr;
+ return ptr;
+ }
+
+ nx_string_t operator ->()
+ {
+ return ptr;
+ }
+
+ nx_string_t ptr;
+};
+
+class ReferenceCountedNXURI
+{
+public:
+ ReferenceCountedNXURI()
+ {
+ ptr = 0;
+ }
+
+ ReferenceCountedNXURI(const ReferenceCountedNXURI &copy)
+ {
+ ptr = NXURIRetain(copy.ptr);
+ }
+
+ ~ReferenceCountedNXURI()
+ {
+ NXURIRelease(ptr);
+ }
+
+ operator nx_uri_t()
+ {
+ return ptr;
+ }
+
+ nx_uri_t *operator &()
+ {
+ // if there's something already in here, we need to release it first
+ if (ptr)
+ NXURIRelease(ptr);
+ ptr=0;
+ return &ptr;
+ }
+
+ nx_uri_t operator ->()
+ {
+ return ptr;
+ }
+ nx_uri_t ptr;
+};
diff --git a/Src/replicant/nswasabi/ServiceName.h b/Src/replicant/nswasabi/ServiceName.h
new file mode 100644
index 00000000..27b1cf2a
--- /dev/null
+++ b/Src/replicant/nswasabi/ServiceName.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "../nx/nxstring.h"
+
+/*
+this is a helper class to implement GetServiceName
+just put WASABI_SERVICE_NAME("Your Service Name"); in the public section of your class declaration.
+this implementation does leak memory, but I don't think it's that big of a deal (services can't be unloaded at run-time anyway)
+if we ever implement the NXSTR() macro, we can eliminate this leak
+
+e.g.
+class MyAPI : public api_whatever
+{
+public:
+ WASABI_SERVICE_NAME("My API");
+};
+ */
+
+#define WASABI_SERVICE_NAME(x) static nx_string_t GetServiceName(){ static nx_string_t service_name=0; if (!service_name) NXStringCreateWithUTF8(&service_name, x); return NXStringRetain(service_name); }
+#define WASABI_SERVICE_GUID(x) static GUID GetServiceGUID() { return x; }
+
+
diff --git a/Src/replicant/nswasabi/VERSION b/Src/replicant/nswasabi/VERSION
new file mode 100644
index 00000000..ea710abb
--- /dev/null
+++ b/Src/replicant/nswasabi/VERSION
@@ -0,0 +1 @@
+1.2 \ No newline at end of file
diff --git a/Src/replicant/nswasabi/XMLString.cpp b/Src/replicant/nswasabi/XMLString.cpp
new file mode 100644
index 00000000..4b22afd5
--- /dev/null
+++ b/Src/replicant/nswasabi/XMLString.cpp
@@ -0,0 +1,44 @@
+#include "XMLString.h"
+
+/* TODO: make and use some sort of nx_mutable_string_t object */
+XMLString::XMLString()
+{
+ data=0;
+}
+
+XMLString::~XMLString()
+{
+ NXMutableStringDestroy(data);
+}
+
+
+void XMLString::Reset()
+{
+ if (data)
+ data->nx_string_data->len = 0; // TODO: make mutable string function for this
+}
+
+nx_string_t XMLString::GetString()
+{
+ // TODO: make mutable string function for this
+ nx_string_t str = data->nx_string_data;
+ data->nx_string_data = 0;
+ NXMutableStringDestroy(data);
+ data=0;
+ return str;
+}
+
+void XMLString::XMLCallback_OnStartElement(const nsxml_char_t *xmlpath, const nsxml_char_t *xmltag, ifc_xmlattributes *attributes)
+{
+ if (data)
+ data->nx_string_data->len = 0; // TODO: make mutable string function for this
+}
+
+void XMLString::XMLCallback_OnCharacterData(const nsxml_char_t *xmlpath, const nsxml_char_t *xmltag, const nsxml_char_t *characters, size_t num_characters)
+{
+ if (!data)
+ data = NXMutableStringCreateFromXML(characters, num_characters);
+ else
+ NXMutableStringGrowFromXML(data, characters, num_characters);
+}
+
diff --git a/Src/replicant/nswasabi/XMLString.h b/Src/replicant/nswasabi/XMLString.h
new file mode 100644
index 00000000..36d1ef9a
--- /dev/null
+++ b/Src/replicant/nswasabi/XMLString.h
@@ -0,0 +1,21 @@
+#include "xml/ifc_xmlcallback.h"
+#include "nx/nxmutablestring.h"
+
+/* this one is an xml callback that just saves the last encountered string */
+
+class XMLString : public ifc_xmlcallback
+{
+public:
+ XMLString();
+ ~XMLString();
+ void Reset();
+ nx_string_t GetString();
+
+private:
+ /* XML callbacks */
+ void WASABICALL XMLCallback_OnStartElement(const nsxml_char_t *xmlpath, const nsxml_char_t *xmltag, ifc_xmlattributes *attributes);
+ void WASABICALL XMLCallback_OnCharacterData(const nsxml_char_t *xmlpath, const nsxml_char_t *xmltag, const nsxml_char_t *characters, size_t num_characters);
+
+ nx_mutable_string_t data;
+};
+
diff --git a/Src/replicant/nswasabi/nswasabi.sln b/Src/replicant/nswasabi/nswasabi.sln
new file mode 100644
index 00000000..d38f0821
--- /dev/null
+++ b/Src/replicant/nswasabi/nswasabi.sln
@@ -0,0 +1,82 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29509.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nswasabi", "nswasabi.vcxproj", "{E4A6C250-B426-4328-945A-303865086F4C}"
+ ProjectSection(ProjectDependencies) = postProject
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27}
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}
+ {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D}
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E4A6C250-B426-4328-945A-303865086F4C}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E4A6C250-B426-4328-945A-303865086F4C}.Debug|Win32.Build.0 = Debug|Win32
+ {E4A6C250-B426-4328-945A-303865086F4C}.Debug|x64.ActiveCfg = Debug|x64
+ {E4A6C250-B426-4328-945A-303865086F4C}.Debug|x64.Build.0 = Debug|x64
+ {E4A6C250-B426-4328-945A-303865086F4C}.Release|Win32.ActiveCfg = Release|Win32
+ {E4A6C250-B426-4328-945A-303865086F4C}.Release|x64.ActiveCfg = Release|x64
+ {E4A6C250-B426-4328-945A-303865086F4C}.Release|x64.Build.0 = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64
+ {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64
+ {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64
+ {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64
+ {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/replicant/nswasabi/nswasabi.vcxproj b/Src/replicant/nswasabi/nswasabi.vcxproj
new file mode 100644
index 00000000..d9d33094
--- /dev/null
+++ b/Src/replicant/nswasabi/nswasabi.vcxproj
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{E4A6C250-B426-4328-945A-303865086F4C}</ProjectGuid>
+ <RootNamespace>nswasabi</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>x86_Debug\</OutDir>
+ <IntDir>x86_Debug\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>x64_Debug\</OutDir>
+ <IntDir>x64_Debug\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>x86_Release\</OutDir>
+ <IntDir>x86_Release\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>x64_Release\</OutDir>
+ <IntDir>x64_Release\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(ProjectDir)x86_Debug\$(ProjectName).lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>EditAndContinue</DebugInformationFormat>
+ <DisableSpecificWarnings>4996;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(ProjectDir)x64_Debug\$(ProjectName).lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(ProjectDir)x86_Release\$(ProjectName).lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>WIN64;NDEBUG;_LIB;_CRT_SECURE_NO_WARNINGS;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <BufferSecurityCheck>false</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ </ClCompile>
+ <Lib>
+ <OutputFile>$(ProjectDir)x64_Release\$(ProjectName).lib</OutputFile>
+ </Lib>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="ApplicationBase.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="ApplicationBase.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/replicant/nswasabi/nswasabi.xcodeproj/project.pbxproj b/Src/replicant/nswasabi/nswasabi.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..bb8b0ca6
--- /dev/null
+++ b/Src/replicant/nswasabi/nswasabi.xcodeproj/project.pbxproj
@@ -0,0 +1,499 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXAggregateTarget section */
+ 00479F18151C35C300F99D12 /* nswasabi-prepare */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = 00479F19151C35C300F99D12 /* Build configuration list for PBXAggregateTarget "nswasabi-prepare" */;
+ buildPhases = (
+ 00479F20151C3CAE00F99D12 /* Generate Version Info */,
+ );
+ dependencies = (
+ 0039B375152A1F7100D96D3E /* PBXTargetDependency */,
+ );
+ name = "nswasabi-prepare";
+ productName = "nswasabi-version";
+ };
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+ 00479F10151C33FE00F99D12 /* XMLString.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B73470151C308A00A8251C /* XMLString.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 00479F22151C3D1D00F99D12 /* version.h in Headers */ = {isa = PBXBuildFile; fileRef = 00479F21151C3D1D00F99D12 /* version.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 00B73471151C308A00A8251C /* AutoCharNX.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346A151C308A00A8251C /* AutoCharNX.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 00B73472151C308A00A8251C /* ObjectFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346B151C308A00A8251C /* ObjectFactory.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 00B73473151C308A00A8251C /* ReferenceCounted.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346C151C308A00A8251C /* ReferenceCounted.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 00B73474151C308A00A8251C /* ServiceName.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346D151C308A00A8251C /* ServiceName.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 00B73475151C308A00A8251C /* singleton.h in Headers */ = {isa = PBXBuildFile; fileRef = 00B7346E151C308A00A8251C /* singleton.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 00C27E9A15372DEA008D95CD /* precomp.h in Headers */ = {isa = PBXBuildFile; fileRef = 00C27E9915372DEA008D95CD /* precomp.h */; };
+ 0CC9987C14C5DC6900484291 /* PlaybackBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0CC9987A14C5DC6900484291 /* PlaybackBase.cpp */; };
+ 0CC9987D14C5DC6900484291 /* PlaybackBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 0CC9987B14C5DC6900484291 /* PlaybackBase.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ B104AEA614C7CC5100211271 /* ApplicationBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B104AEA414C7CC5100211271 /* ApplicationBase.cpp */; };
+ B104AEA714C7CC5100211271 /* ApplicationBase.h in Headers */ = {isa = PBXBuildFile; fileRef = B104AEA514C7CC5100211271 /* ApplicationBase.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ B15A069D14F499F9004F70E7 /* ID3v1Metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B15A069914F499F9004F70E7 /* ID3v1Metadata.cpp */; };
+ B15A069E14F499F9004F70E7 /* ID3v1Metadata.h in Headers */ = {isa = PBXBuildFile; fileRef = B15A069A14F499F9004F70E7 /* ID3v1Metadata.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ B15A069F14F499F9004F70E7 /* ID3v2Metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B15A069B14F499F9004F70E7 /* ID3v2Metadata.cpp */; };
+ B15A06A014F499F9004F70E7 /* ID3v2Metadata.h in Headers */ = {isa = PBXBuildFile; fileRef = B15A069C14F499F9004F70E7 /* ID3v2Metadata.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ B193DFDA14F49B6F005D99FB /* APEv2Metadata.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B193DFD814F49B6F005D99FB /* APEv2Metadata.cpp */; };
+ B193DFDB14F49B6F005D99FB /* APEv2Metadata.h in Headers */ = {isa = PBXBuildFile; fileRef = B193DFD914F49B6F005D99FB /* APEv2Metadata.h */; settings = {ATTRIBUTES = (Public, ); }; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 0039B374152A1F7100D96D3E /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0CC9986414C5DC3C00484291 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 00479F14151C35B700F99D12;
+ remoteInfo = "nswasabi-cleanup";
+ };
+ 00479F1C151C3C4000F99D12 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0CC9986414C5DC3C00484291 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 00479F14151C35B700F99D12;
+ remoteInfo = "nswasabi-cleanup";
+ };
+ 00479F1E151C3C4300F99D12 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 0CC9986414C5DC3C00484291 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 00479F18151C35C300F99D12;
+ remoteInfo = "nswasabi-version";
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 00479F21151C3D1D00F99D12 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = version.h; path = $PROJECT_DERIVED_FILE_DIR/version.h; sourceTree = "<absolute>"; };
+ 00B7346A151C308A00A8251C /* AutoCharNX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoCharNX.h; sourceTree = "<group>"; };
+ 00B7346B151C308A00A8251C /* ObjectFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectFactory.h; sourceTree = "<group>"; };
+ 00B7346C151C308A00A8251C /* ReferenceCounted.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReferenceCounted.h; sourceTree = "<group>"; };
+ 00B7346D151C308A00A8251C /* ServiceName.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ServiceName.h; sourceTree = "<group>"; };
+ 00B7346E151C308A00A8251C /* singleton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = singleton.h; sourceTree = "<group>"; };
+ 00B7346F151C308A00A8251C /* XMLString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = XMLString.cpp; sourceTree = "<group>"; };
+ 00B73470151C308A00A8251C /* XMLString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLString.h; sourceTree = "<group>"; };
+ 00B7347A151C309D00A8251C /* VERSION */ = {isa = PBXFileReference; lastKnownFileType = text; path = VERSION; sourceTree = "<group>"; };
+ 00C27E9915372DEA008D95CD /* precomp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = precomp.h; sourceTree = "<group>"; };
+ 0CC9986D14C5DC3C00484291 /* libnswasabi.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libnswasabi.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ 0CC9987A14C5DC6900484291 /* PlaybackBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlaybackBase.cpp; sourceTree = "<group>"; };
+ 0CC9987B14C5DC6900484291 /* PlaybackBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaybackBase.h; sourceTree = "<group>"; };
+ B104AEA414C7CC5100211271 /* ApplicationBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ApplicationBase.cpp; sourceTree = "<group>"; };
+ B104AEA514C7CC5100211271 /* ApplicationBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ApplicationBase.h; sourceTree = "<group>"; };
+ B15A069914F499F9004F70E7 /* ID3v1Metadata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ID3v1Metadata.cpp; sourceTree = "<group>"; };
+ B15A069A14F499F9004F70E7 /* ID3v1Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ID3v1Metadata.h; sourceTree = "<group>"; };
+ B15A069B14F499F9004F70E7 /* ID3v2Metadata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ID3v2Metadata.cpp; sourceTree = "<group>"; };
+ B15A069C14F499F9004F70E7 /* ID3v2Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ID3v2Metadata.h; sourceTree = "<group>"; };
+ B193DFD814F49B6F005D99FB /* APEv2Metadata.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = APEv2Metadata.cpp; sourceTree = "<group>"; };
+ B193DFD914F49B6F005D99FB /* APEv2Metadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = APEv2Metadata.h; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 0CC9986A14C5DC3C00484291 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 00B73479151C308E00A8251C /* Version */ = {
+ isa = PBXGroup;
+ children = (
+ 00B7347A151C309D00A8251C /* VERSION */,
+ 00479F21151C3D1D00F99D12 /* version.h */,
+ );
+ name = Version;
+ sourceTree = "<group>";
+ };
+ 0CC9986214C5DC3C00484291 = {
+ isa = PBXGroup;
+ children = (
+ 00C27E9915372DEA008D95CD /* precomp.h */,
+ B193DFD814F49B6F005D99FB /* APEv2Metadata.cpp */,
+ B193DFD914F49B6F005D99FB /* APEv2Metadata.h */,
+ B104AEA414C7CC5100211271 /* ApplicationBase.cpp */,
+ B104AEA514C7CC5100211271 /* ApplicationBase.h */,
+ 00B7346A151C308A00A8251C /* AutoCharNX.h */,
+ B15A069914F499F9004F70E7 /* ID3v1Metadata.cpp */,
+ B15A069A14F499F9004F70E7 /* ID3v1Metadata.h */,
+ B15A069B14F499F9004F70E7 /* ID3v2Metadata.cpp */,
+ B15A069C14F499F9004F70E7 /* ID3v2Metadata.h */,
+ 00B7346B151C308A00A8251C /* ObjectFactory.h */,
+ 0CC9987A14C5DC6900484291 /* PlaybackBase.cpp */,
+ 0CC9987B14C5DC6900484291 /* PlaybackBase.h */,
+ 00B7346C151C308A00A8251C /* ReferenceCounted.h */,
+ 00B7346D151C308A00A8251C /* ServiceName.h */,
+ 00B7346E151C308A00A8251C /* singleton.h */,
+ 00B7346F151C308A00A8251C /* XMLString.cpp */,
+ 00B73470151C308A00A8251C /* XMLString.h */,
+ 00B73479151C308E00A8251C /* Version */,
+ 0CC9986E14C5DC3C00484291 /* Products */,
+ );
+ sourceTree = "<group>";
+ };
+ 0CC9986E14C5DC3C00484291 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 0CC9986D14C5DC3C00484291 /* libnswasabi.a */,
+ );
+ name = Products;
+ sourceTree = "<group>";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 0CC9986B14C5DC3C00484291 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0CC9987D14C5DC6900484291 /* PlaybackBase.h in Headers */,
+ B104AEA714C7CC5100211271 /* ApplicationBase.h in Headers */,
+ B15A069E14F499F9004F70E7 /* ID3v1Metadata.h in Headers */,
+ B15A06A014F499F9004F70E7 /* ID3v2Metadata.h in Headers */,
+ B193DFDB14F49B6F005D99FB /* APEv2Metadata.h in Headers */,
+ 00B73471151C308A00A8251C /* AutoCharNX.h in Headers */,
+ 00B73472151C308A00A8251C /* ObjectFactory.h in Headers */,
+ 00B73473151C308A00A8251C /* ReferenceCounted.h in Headers */,
+ 00B73474151C308A00A8251C /* ServiceName.h in Headers */,
+ 00B73475151C308A00A8251C /* singleton.h in Headers */,
+ 00479F10151C33FE00F99D12 /* XMLString.h in Headers */,
+ 00479F22151C3D1D00F99D12 /* version.h in Headers */,
+ 00C27E9A15372DEA008D95CD /* precomp.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXLegacyTarget section */
+ 00479F14151C35B700F99D12 /* nswasabi-cleanup */ = {
+ isa = PBXLegacyTarget;
+ buildArgumentsString = "$(NSBUILD_TOOLS_BIN_DIR)/cleanbuild --xcode-mode --libraries \"$(LIBRARY_PATH)\" \"$(PUBLIC_HEADERS_DIR)\" \"$(DWARF_DSYM_PATH)\" \"$(PROJECT_DERIVED_FILE_DIR)/version.*\"";
+ buildConfigurationList = 00479F15151C35B700F99D12 /* Build configuration list for PBXLegacyTarget "nswasabi-cleanup" */;
+ buildPhases = (
+ );
+ buildToolPath = /bin/sh;
+ buildWorkingDirectory = "";
+ dependencies = (
+ );
+ name = "nswasabi-cleanup";
+ passBuildSettingsInEnvironment = 1;
+ productName = "nswasabi-cleanup";
+ };
+/* End PBXLegacyTarget section */
+
+/* Begin PBXNativeTarget section */
+ 0CC9986C14C5DC3C00484291 /* nswasabi */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 0CC9987114C5DC3C00484291 /* Build configuration list for PBXNativeTarget "nswasabi" */;
+ buildPhases = (
+ 0CC9986914C5DC3C00484291 /* Sources */,
+ 0CC9986A14C5DC3C00484291 /* Frameworks */,
+ 0CC9986B14C5DC3C00484291 /* Headers */,
+ 00479F13151C354F00F99D12 /* Install Public Headers */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 00479F1D151C3C4000F99D12 /* PBXTargetDependency */,
+ 00479F1F151C3C4300F99D12 /* PBXTargetDependency */,
+ );
+ name = nswasabi;
+ productName = nswasabi;
+ productReference = 0CC9986D14C5DC3C00484291 /* libnswasabi.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 0CC9986414C5DC3C00484291 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0500;
+ ORGANIZATIONNAME = "Nullsoft, Inc.";
+ };
+ buildConfigurationList = 0CC9986714C5DC3C00484291 /* Build configuration list for PBXProject "nswasabi" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 0CC9986214C5DC3C00484291;
+ productRefGroup = 0CC9986E14C5DC3C00484291 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 0CC9986C14C5DC3C00484291 /* nswasabi */,
+ 00479F14151C35B700F99D12 /* nswasabi-cleanup */,
+ 00479F18151C35C300F99D12 /* nswasabi-prepare */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 00479F13151C354F00F99D12 /* Install Public Headers */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 8;
+ files = (
+ );
+ inputPaths = (
+ "$(BUILT_PRODUCTS_DIR)$(PUBLIC_HEADERS_FOLDER_PATH)",
+ );
+ name = "Install Public Headers";
+ outputPaths = (
+ "$(DSTROOT)$(PUBLIC_HEADERS_FOLDER_PATH)",
+ );
+ runOnlyForDeploymentPostprocessing = 1;
+ shellPath = /bin/sh;
+ shellScript = "INSTALLTOOL=\"$NSBUILD_TOOLS_BIN_DIR/installtool\"\n$INSTALLTOOL --headers-only \\\n \"$SCRIPT_INPUT_FILE_0/\" \\\n \"$SCRIPT_OUTPUT_FILE_0\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 00479F20151C3CAE00F99D12 /* Generate Version Info */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "$(SRCROOT)/VERSION",
+ "$(NSBUILD_TOOLS_SHARE_DIR)/nvgtool/lib-version.template.h",
+ );
+ name = "Generate Version Info";
+ outputPaths = (
+ "$(PROJECT_DERIVED_FILE_DIR)/version.h",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "PRODUCT_VERSION=$(cat \"$SCRIPT_INPUT_FILE_0\")\n\nif [ ! -d \"$PROJECT_DERIVED_FILE_DIR\" ]; then\n mkdir -p \"$PROJECT_DERIVED_FILE_DIR\"\nfi\n\nNVGTOOL=\"$NSBUILD_TOOLS_BIN_DIR/nvgtool\"\n$NVGTOOL --product-name \"$PRODUCT_NAME\" \\\n --product-version \"$PRODUCT_VERSION\" \\\n --input-file \"$SCRIPT_INPUT_FILE_1\" \\\n --output-file \"$SCRIPT_OUTPUT_FILE_0\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 0CC9986914C5DC3C00484291 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 0CC9987C14C5DC6900484291 /* PlaybackBase.cpp in Sources */,
+ B104AEA614C7CC5100211271 /* ApplicationBase.cpp in Sources */,
+ B15A069D14F499F9004F70E7 /* ID3v1Metadata.cpp in Sources */,
+ B15A069F14F499F9004F70E7 /* ID3v2Metadata.cpp in Sources */,
+ B193DFDA14F49B6F005D99FB /* APEv2Metadata.cpp in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 0039B375152A1F7100D96D3E /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 00479F14151C35B700F99D12 /* nswasabi-cleanup */;
+ targetProxy = 0039B374152A1F7100D96D3E /* PBXContainerItemProxy */;
+ };
+ 00479F1D151C3C4000F99D12 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 00479F14151C35B700F99D12 /* nswasabi-cleanup */;
+ targetProxy = 00479F1C151C3C4000F99D12 /* PBXContainerItemProxy */;
+ };
+ 00479F1F151C3C4300F99D12 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 00479F18151C35C300F99D12 /* nswasabi-prepare */;
+ targetProxy = 00479F1E151C3C4300F99D12 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 00479F16151C35B700F99D12 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ DWARF_DSYM_PATH = "$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_NAME).dSYM";
+ LIBRARY_PATH = "$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_NAME)";
+ PUBLIC_HEADERS_DIR = "$(BUILT_PRODUCTS_DIR)$(PUBLIC_HEADERS_FOLDER_PATH)";
+ };
+ name = Debug;
+ };
+ 00479F17151C35B700F99D12 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ DWARF_DSYM_PATH = "$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_NAME).dSYM";
+ LIBRARY_PATH = "$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_NAME)";
+ PUBLIC_HEADERS_DIR = "$(BUILT_PRODUCTS_DIR)$(PUBLIC_HEADERS_FOLDER_PATH)";
+ };
+ name = Release;
+ };
+ 00479F1A151C35C300F99D12 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ };
+ name = Debug;
+ };
+ 00479F1B151C35C300F99D12 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ };
+ name = Release;
+ };
+ 0CC9986F14C5DC3C00484291 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DYLIB_COMPATIBILITY_VERSION = "$(CURRENT_PROJECT_VERSION)";
+ DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)";
+ EXECUTABLE_EXTENSION = a;
+ EXECUTABLE_NAME = "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME).$(EXECUTABLE_EXTENSION)";
+ EXECUTABLE_PREFIX = lib;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_INCREASE_PRECOMPILED_HEADER_SHARING = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = precomp.h;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INSTALL_PATH = "$(INSTALL_PATH_PREFIX)/lib";
+ INSTALL_PATH_PREFIX = /usr/local;
+ MACH_O_TYPE = staticlib;
+ MACOSX_DEPLOYMENT_TARGET = 10.6;
+ NSBUILD_TOOLS_BIN_DIR = "$(NSBUILD_TOOLS_DIR)/bin";
+ NSBUILD_TOOLS_DIR = "$(SRCROOT)/../../build-tools";
+ NSBUILD_TOOLS_SHARE_DIR = "$(NSBUILD_TOOLS_DIR)/share";
+ ONLY_ACTIVE_ARCH = YES;
+ PRIVATE_HEADERS_FOLDER_PATH = "$(INSTALL_PATH_PREFIX)/include/$(PRODUCT_NAME)";
+ PRODUCT_NAME = "$(PROJECT_NAME)";
+ PUBLIC_HEADERS_FOLDER_PATH = "$(INSTALL_PATH_PREFIX)/include/$(PRODUCT_NAME)";
+ SDKROOT = macosx;
+ USER_HEADER_SEARCH_PATHS = ".. $(BUILT_PRODUCTS_DIR)$(INSTALL_PATH_PREFIX)/include";
+ };
+ name = Debug;
+ };
+ 0CC9987014C5DC3C00484291 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = YES;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DYLIB_COMPATIBILITY_VERSION = "$(CURRENT_PROJECT_VERSION)";
+ DYLIB_CURRENT_VERSION = "$(CURRENT_PROJECT_VERSION)";
+ EXECUTABLE_EXTENSION = a;
+ EXECUTABLE_NAME = "$(EXECUTABLE_PREFIX)$(PRODUCT_NAME).$(EXECUTABLE_EXTENSION)";
+ EXECUTABLE_PREFIX = lib;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_INCREASE_PRECOMPILED_HEADER_SHARING = YES;
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREFIX_HEADER = precomp.h;
+ GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ INSTALL_PATH = "$(INSTALL_PATH_PREFIX)/lib";
+ INSTALL_PATH_PREFIX = /usr/local;
+ MACH_O_TYPE = staticlib;
+ MACOSX_DEPLOYMENT_TARGET = 10.6;
+ NSBUILD_TOOLS_BIN_DIR = "$(NSBUILD_TOOLS_DIR)/bin";
+ NSBUILD_TOOLS_DIR = "$(SRCROOT)/../../build-tools";
+ NSBUILD_TOOLS_SHARE_DIR = "$(NSBUILD_TOOLS_DIR)/share";
+ PRIVATE_HEADERS_FOLDER_PATH = "$(INSTALL_PATH_PREFIX)/include/$(PRODUCT_NAME)";
+ PRODUCT_NAME = "$(PROJECT_NAME)";
+ PUBLIC_HEADERS_FOLDER_PATH = "$(INSTALL_PATH_PREFIX)/include/$(PRODUCT_NAME)";
+ SDKROOT = macosx;
+ USER_HEADER_SEARCH_PATHS = ".. $(BUILT_PRODUCTS_DIR)$(INSTALL_PATH_PREFIX)/include";
+ };
+ name = Release;
+ };
+ 0CC9987214C5DC3C00484291 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ };
+ name = Debug;
+ };
+ 0CC9987314C5DC3C00484291 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ COMBINE_HIDPI_IMAGES = YES;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 00479F15151C35B700F99D12 /* Build configuration list for PBXLegacyTarget "nswasabi-cleanup" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 00479F16151C35B700F99D12 /* Debug */,
+ 00479F17151C35B700F99D12 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 00479F19151C35C300F99D12 /* Build configuration list for PBXAggregateTarget "nswasabi-prepare" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 00479F1A151C35C300F99D12 /* Debug */,
+ 00479F1B151C35C300F99D12 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 0CC9986714C5DC3C00484291 /* Build configuration list for PBXProject "nswasabi" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 0CC9986F14C5DC3C00484291 /* Debug */,
+ 0CC9987014C5DC3C00484291 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 0CC9987114C5DC3C00484291 /* Build configuration list for PBXNativeTarget "nswasabi" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 0CC9987214C5DC3C00484291 /* Debug */,
+ 0CC9987314C5DC3C00484291 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 0CC9986414C5DC3C00484291 /* Project object */;
+}
diff --git a/Src/replicant/nswasabi/nswasabi.xcodeproj/xcshareddata/xcschemes/nswasabi.xcscheme b/Src/replicant/nswasabi/nswasabi.xcodeproj/xcshareddata/xcschemes/nswasabi.xcscheme
new file mode 100644
index 00000000..84c4002b
--- /dev/null
+++ b/Src/replicant/nswasabi/nswasabi.xcodeproj/xcshareddata/xcschemes/nswasabi.xcscheme
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "0500"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ <BuildActionEntries>
+ <BuildActionEntry
+ buildForTesting = "YES"
+ buildForRunning = "YES"
+ buildForProfiling = "YES"
+ buildForArchiving = "YES"
+ buildForAnalyzing = "YES">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "0CC9986C14C5DC3C00484291"
+ BuildableName = "libnswasabi.a"
+ BlueprintName = "nswasabi"
+ ReferencedContainer = "container:nswasabi.xcodeproj">
+ </BuildableReference>
+ </BuildActionEntry>
+ </BuildActionEntries>
+ </BuildAction>
+ <TestAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ buildConfiguration = "Debug">
+ <Testables>
+ </Testables>
+ </TestAction>
+ <LaunchAction
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Debug"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ allowLocationSimulation = "YES">
+ <AdditionalOptions>
+ </AdditionalOptions>
+ </LaunchAction>
+ <ProfileAction
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ buildConfiguration = "Release"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/Src/replicant/nswasabi/precomp.h b/Src/replicant/nswasabi/precomp.h
new file mode 100644
index 00000000..955fa471
--- /dev/null
+++ b/Src/replicant/nswasabi/precomp.h
@@ -0,0 +1,32 @@
+//
+// precomp.h
+// nswasabi
+//
+
+#include <assert.h>
+#include <limits.h>
+#include <new>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "foundation/error.h"
+#include "foundation/types.h"
+
+#include "nu/ByteReader.h"
+#include "nu/ByteWriter.h"
+#include "nu/LockFreeItem.h"
+#include "nu/ThreadLoop.h"
+
+#include "nx/nxcondition.h"
+#include "nx/nxstring.h"
+#include "nx/nxuri.h"
+#include "nx/nxmutablestring.h"
+
+#include "metadata/metadata.h"
+#include "metadata/MetadataKeys.h"
+
+#include "nsid3v1/nsid3v1.h"
+#include "nsid3v2/nsid3v2.h"
+
+#include "service/ifc_servicefactory.h"
diff --git a/Src/replicant/nswasabi/singleton.h b/Src/replicant/nswasabi/singleton.h
new file mode 100644
index 00000000..b31a68bc
--- /dev/null
+++ b/Src/replicant/nswasabi/singleton.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#include "service/ifc_servicefactory.h"
+
+/*
+====== Usage ======
+disp_t: your Dispatchable base class
+implt_t: your implementation class
+
+SingletonServiceFactory<disp_t, impl_t> myFactory;
+impl_t myImplementation;
+
+//....
+
+//during service registration
+myFactory.Register(WASABI2_API_SVC, &myImplementation);
+
+//during service deregistration
+myFactory.Deregister(WASABI2_API_SVC, &myImplementation);
+
+==== Class requirements ====
+your base or implementation class requires the following three static methods
+static FOURCC getServiceType(); // return your type (e.g. WaSvc::UNIQUE)... might already be defined in the dispatchable base class
+static const char *getServiceName(); // return your service name
+static GUID getServiceGuid(); // return your service GUID
+*/
+
+class WasabiServiceFactory
+{
+public:
+ virtual ~WasabiServiceFactory() {}
+ virtual void Register(api_service *serviceManager)=0;
+ virtual void Deregister(api_service *serviceManager)=0;
+};
+
+
+template <class impl_t, class disp_t>
+class SingletonServiceFactory : public ifc_serviceFactory
+{
+public:
+ SingletonServiceFactory() : impl(0)
+ {
+ }
+
+ ~SingletonServiceFactory()
+ {
+ }
+
+ void Register(api_service *serviceManager, impl_t *new_impl)
+ {
+ impl=new_impl;
+ serviceManager->Register(this);
+ }
+
+ void Deregister(api_service *serviceManager)
+ {
+ if (impl)
+ {
+ serviceManager->Unregister(this);
+ impl=0;
+ }
+ }
+
+private:
+ GUID WASABICALL ServiceFactory_GetServiceType() { return impl_t::GetServiceType(); }
+ nx_string_t WASABICALL ServiceFactory_GetServiceName() { return impl_t::GetServiceName(); }
+ GUID WASABICALL ServiceFactory_GetGUID() { return impl_t::GetServiceGUID(); } // GUID per service factory, can be INVALID_GUID
+ void *WASABICALL ServiceFactory_GetInterface() { return static_cast<disp_t *>(impl); }
+ int WASABICALL ServiceFactory_ServiceNotify(int msg, intptr_t param1 = 0, intptr_t param2 = 0) { return 0; }
+
+private:
+ impl_t *impl;
+
+};
+
+/* same as above, but this one also constructs the implementation object itself
+ useful when:
+ 1) you don't need to ever access the implementation object yourself
+ 2) the implementation class requires no constructor parameters
+
+ TODO: might want to change this to "new" the object during Register and "delete" during Deregister,
+ in case destruction during program termination isn't safe.
+*/
+template <class impl_t, class disp_t>
+class SingletonService : public ifc_serviceFactory, public WasabiServiceFactory
+{
+public:
+ SingletonService()
+ {
+ }
+
+ ~SingletonService()
+ {
+ }
+
+ void Register(api_service *serviceManager)
+ {
+ serviceManager->Register(this);
+ }
+
+ void Deregister(api_service *serviceManager)
+ {
+ serviceManager->Unregister(this);
+ }
+
+private:
+ GUID WASABICALL ServiceFactory_GetServiceType() { return impl_t::GetServiceType(); }
+ nx_string_t WASABICALL ServiceFactory_GetServiceName() { return impl_t::GetServiceName(); }
+ GUID WASABICALL ServiceFactory_GetGUID() { return impl_t::GetServiceGUID(); } // GUID per service factory, can be INVALID_GUID
+ void *WASABICALL ServiceFactory_GetInterface() { return static_cast<disp_t *>(&impl); }
+ int WASABICALL ServiceFactory_ServiceNotify(int msg, intptr_t param1 = 0, intptr_t param2 = 0) { return 0; }
+
+private:
+ impl_t impl;
+
+};
+