From b2a9a97fc003a9faaea83c5ddb8cb7804cc8e71b Mon Sep 17 00:00:00 2001 From: Paolo Marrone Date: Mon, 29 Jan 2024 16:01:09 +0100 Subject: [PATCH 1/6] readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f55b254..fe191f6 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ TBA + +iOS app + From 70e3d357f93bfcf3b5309e7fda94a23b3e9b9011 Mon Sep 17 00:00:00 2001 From: paolo mac Date: Fri, 2 Feb 2024 16:58:51 +0100 Subject: [PATCH 2/6] ios support --- .gitignore | 2 + templates/ios-make/Makefile | 35 +++ templates/ios-make/project.yml | 19 ++ templates/ios-make/tibia-index.js | 5 + templates/ios-make/vars.mk | 8 + templates/ios/src/app-Bridging-Header.h | 8 + templates/ios/src/app.swift | 87 ++++++ templates/ios/src/data.h | 66 +++++ templates/ios/src/index.html | 178 ++++++++++++ templates/ios/src/native.mm | 353 ++++++++++++++++++++++++ templates/ios/src/platform.h | 6 + templates/ios/tibia-index.js | 11 + test/ios-make.json | 11 + test/ios.json | 3 + test/run.sh | 4 + 15 files changed, 796 insertions(+) create mode 100644 .gitignore create mode 100644 templates/ios-make/Makefile create mode 100644 templates/ios-make/project.yml create mode 100644 templates/ios-make/tibia-index.js create mode 100644 templates/ios-make/vars.mk create mode 100644 templates/ios/src/app-Bridging-Header.h create mode 100644 templates/ios/src/app.swift create mode 100644 templates/ios/src/data.h create mode 100644 templates/ios/src/index.html create mode 100644 templates/ios/src/native.mm create mode 100644 templates/ios/src/platform.h create mode 100644 templates/ios/tibia-index.js create mode 100644 test/ios-make.json create mode 100644 test/ios.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26c8e1f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.DS_Store +out/ diff --git a/templates/ios-make/Makefile b/templates/ios-make/Makefile new file mode 100644 index 0000000..561bbe0 --- /dev/null +++ b/templates/ios-make/Makefile @@ -0,0 +1,35 @@ +include vars.mk + +ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + + +SOURCES := data.h index.html app.swift native.mm app-Bridging-Header.h platform.h plugin.h +SOURCES_IN := $(addprefix ${ROOT_DIR}/src/, ${SOURCES}) +SOURCES_OUT := $(addprefix ${ROOT_DIR}/build/gen/src/, ${SOURCES}) +EXT_SOURCES_IN := ${3RDPARTYFILES} +EXT_SOURCES_OUT := + + + +all: build/gen/${PROJECT_NAME}.xcodeproj + +${SOURCES_OUT}: ${SOURCES_IN} ${EXT_SOURCES_IN} | build/gen/src + cp $^ ${ROOT_DIR}/build/gen/src + +${EXT_SOURCES_OUT}: ${EXT_SOURCES_IN} + cp ${EXT_SOURCES_IN} ${ROOT_DIR}/build/gen/src/ + +build/gen/${PROJECT_NAME}.xcodeproj: ${SOURCES_OUT} + xcodegen generate --spec project.yml + mv ${PROJECT_NAME}.xcodeproj build/gen/${PROJECT_NAME}.xcodeproj + mv Info.plist build/gen/Info.plist + +build/gen build/gen/src: + mkdir -p $@ + +clean: + rm -fr build + +install: + +.PHONY: all clean install diff --git a/templates/ios-make/project.yml b/templates/ios-make/project.yml new file mode 100644 index 0000000..a41fd14 --- /dev/null +++ b/templates/ios-make/project.yml @@ -0,0 +1,19 @@ +name: {{=it.ios_make.projectName}} +targets: + {{=it.ios_make.targetName}}: + platform: [iOS] + deploymentTarget: + iOS: {{=it.ios_make.deploymentTarget}} + type: application + sources: + - path: src + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.orastron.{{=it.product.bundleName}} + SWIFT_OBJC_BRIDGING_HEADER: src/app-Bridging-Header.h + HEADER_SEARCH_PATHS: {{~it.ios_make.header_search_paths:p}} + - {{=p}}{{~}} + info: + path: Info.plist + properties: + NSMicrophoneUsageDescription: Audio input access needed to run the example diff --git a/templates/ios-make/tibia-index.js b/templates/ios-make/tibia-index.js new file mode 100644 index 0000000..4367ed3 --- /dev/null +++ b/templates/ios-make/tibia-index.js @@ -0,0 +1,5 @@ +module.exports = function (data, api) { + api.copyFile(`Makefile`, `Makefile`); + api.generateFileFromTemplateFile(`vars.mk`, `vars.mk`, data); + api.generateFileFromTemplateFile(`project.yml`, `project.yml`, data); +}; diff --git a/templates/ios-make/vars.mk b/templates/ios-make/vars.mk new file mode 100644 index 0000000..fdc94b9 --- /dev/null +++ b/templates/ios-make/vars.mk @@ -0,0 +1,8 @@ +BUNDLE_NAME := {{=it.product.bundleName}} + +PROJECT_NAME := {{=it.ios_make.projectName}} +TARGET_NAME := {{=it.ios_make.targetName}} + +3RDPARTYFILES := {{=it.ios_make["3rdpartyfiles"].join(' ')}} + +HAS_MIDI_IN := {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0 ? "yes" : "no"}} diff --git a/templates/ios/src/app-Bridging-Header.h b/templates/ios/src/app-Bridging-Header.h new file mode 100644 index 0000000..f89b144 --- /dev/null +++ b/templates/ios/src/app-Bridging-Header.h @@ -0,0 +1,8 @@ +/* + * Copyright (C) 2023, 2024 Orastron Srl unipersonale + */ + +char audioStart(); +void audioStop(); +void setParameter(int i, float v); +float getParameter(int i); diff --git a/templates/ios/src/app.swift b/templates/ios/src/app.swift new file mode 100644 index 0000000..34d5697 --- /dev/null +++ b/templates/ios/src/app.swift @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023, 2024 Orastron Srl unipersonale + */ + + +import SwiftUI +import WebKit +import AVFoundation + +struct WebView: UIViewRepresentable { + class Coordinator: NSObject, WKScriptMessageHandlerWithReply { + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) { + guard let body = message.body as? [String : Any] else { return } + guard let name = body["name"] as? String else { return } + switch (name) { + case "needAudioPermission": + replyHandler(AVCaptureDevice.authorizationStatus(for: .audio) != .authorized, nil) + break; + case "requestAudioPermission": + AVAudioSession.sharedInstance().requestRecordPermission { granted in } + replyHandler(nil, nil) + break; + case "audioStart": + replyHandler(audioStart() != 0, nil) + break; + case "audioStop": + audioStop() + replyHandler(nil, nil) + break; + case "setParameter": + guard let index = body["index"] as? Int32 else { return } + guard let value = body["value"] as? Double else { return } + setParameter(index, Float(value)) + replyHandler(nil, nil) + break; + case "getParameter": + guard let index = body["index"] as? Int32 else { return } + let v = getParameter(index) + replyHandler(v, nil) + break; + default: + break; + } + } + } + + func makeCoordinator() -> Coordinator { + return Coordinator() + } + + let url: URL + + func makeUIView(context: Context) -> WKWebView { + let configuration = WKWebViewConfiguration() + configuration.userContentController.addScriptMessageHandler(Coordinator(), contentWorld: .page, name: "listener") + let webView = WKWebView(frame: .zero, configuration: configuration) + webView.isInspectable = true + return webView + } + + func updateUIView(_ webView: WKWebView, context: Context) { + let request = URLRequest(url: url) + webView.load(request) + } +} + +struct ContentView: View { + var body: some View { + let url = Bundle.main.url(forResource: "index", withExtension: "html") + WebView(url: url!) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} + +@main +struct templateApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/templates/ios/src/data.h b/templates/ios/src/data.h new file mode 100644 index 0000000..f5fcdc6 --- /dev/null +++ b/templates/ios/src/data.h @@ -0,0 +1,66 @@ +#include +#include + +#define NUM_AUDIO_BUSES_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input").length}} +#define NUM_AUDIO_BUSES_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output").length}} + +#define AUDIO_BUS_IN {{=it.product.buses.findIndex(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain)}} +#define AUDIO_BUS_OUT {{=it.product.buses.findIndex(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain)}} + +#define NUM_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}} +#define NUM_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain) ? (it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.cv && !x.sidechain)[0].channels == "mono" ? 1 : 2) : 0}} +#define NUM_NON_OPT_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input" && !x.optional).reduce((a, x) => a + (x.channels == "mono" ? 1 : 2), 0)}} +#define NUM_NON_OPT_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output" && !x.optional).reduce((a, x) => a + (x.channels == "mono" ? 1 : 2), 0)}} +#define NUM_ALL_CHANNELS_IN {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "input").reduce((a, x) => a + (x.channels == "mono" ? 1 : 2), 0)}} +#define NUM_ALL_CHANNELS_OUT {{=it.product.buses.filter(x => x.type == "audio" && x.direction == "output").reduce((a, x) => a + (x.channels == "mono" ? 1 : 2), 0)}} + +#define NUM_MIDI_INPUTS {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length}} + +#define MIDI_BUS_IN {{=it.product.buses.findIndex(x => x.type == "midi" && x.direction == "input")}} + +#if (AUDIO_BUS_IN >= 0) || (AUDIO_BUS_OUT >= 0) +static struct { + size_t index; + char out; + char optional; + char channels; +} audio_bus_data[NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT] = { +{{~it.product.buses :b:i}} +{{?b.type == "audio"}} + { + /* .index = */ {{=i}}, + /* .out = */ {{=b.direction == "output" ? 1 : 0}}, + /* .optional = */ {{=b.optional ? 1 : 0}}, + /* .channels = */ {{=b.channels == "mono" ? 1 : 2}} + }, +{{?}} +{{~}} +}; +#endif + +#define PARAMETERS_N {{=it.product.parameters.length}} + +#if PARAMETERS_N > 0 + +# define PARAM_BYPASS 1 +# define PARAM_TOGGLED (1<<1) +# define PARAM_INTEGER (1<<2) + +static struct { + char out; + float def; + float min; + float max; + uint32_t flags; +} param_data[PARAMETERS_N] = { +{{~it.product.parameters :p}} + { + /* .out = */ {{=p.direction == "output" ? 1 : 0}}, + /* .def = */ {{=p.defaultValue.toExponential()}}, + /* .min = */ {{=p.minimum.toExponential()}}f, + /* .max = */ {{=p.maximum.toExponential()}}f, + /* .flags = */ {{?p.isBypass}}PARAM_BYPASS{{??p.isLatency}}PARAM_INTEGER{{??}}0{{?p.toggled}} | PARAM_TOGGLED{{?}}{{?p.integer}} | PARAM_INTEGER{{?}}{{?}} + }, +{{~}} +}; +#endif diff --git a/templates/ios/src/index.html b/templates/ios/src/index.html new file mode 100644 index 0000000..7504234 --- /dev/null +++ b/templates/ios/src/index.html @@ -0,0 +1,178 @@ + + + + + {{=it.product.name}} + + + + + +
+ + diff --git a/templates/ios/src/native.mm b/templates/ios/src/native.mm new file mode 100644 index 0000000..cae7b77 --- /dev/null +++ b/templates/ios/src/native.mm @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2023, 2024 Orastron Srl unipersonale + */ + +#include "data.h" +#include "plugin.h" +#if PARAMETERS_N > 0 +#include +#endif +#if PARAMETERS_N + NUM_MIDI_INPUTS > 0 +#include +#endif +#include +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 +#define MINIAUDIO_IMPLEMENTATION +#define MA_NO_RUNTIME_LINKING +#include "miniaudio.h" + +#define BLOCK_SIZE 32 +#endif + +#define NUM_BUFS (NUM_CHANNELS_IN > NUM_CHANNELS_OUT ? NUM_CHANNELS_IN : NUM_CHANNELS_OUT) + +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 +static ma_device device; +#endif +static plugin instance; +static void *mem; +#if (NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN) || (NUM_NON_OPT_CHANNELS_OUT > NUM_CHANNELS_OUT) +float zero[BLOCK_SIZE]; +#endif +#if NUM_CHANNELS_IN > 0 +float x_buf[NUM_CHANNELS_IN * BLOCK_SIZE]; +#endif +#if NUM_ALL_CHANNELS_IN > 0 +const float *x[NUM_ALL_CHANNELS_IN]; +#else +const float **x; +#endif +#if NUM_CHANNELS_OUT > 0 +float y_buf[NUM_CHANNELS_OUT * BLOCK_SIZE]; +#endif +#if NUM_ALL_CHANNELS_OUT > 0 +float *y[NUM_ALL_CHANNELS_IN]; +#else +float **y; +#endif +#if PARAMETERS_N > 0 +std::mutex mutex; +float param_values[PARAMETERS_N]; +float param_values_prev[PARAMETERS_N]; +#endif +#if NUM_MIDI_INPUTS > 0 +CFStringRef midiClientName = NULL; +MIDIClientRef midiClient = NULL; +CFStringRef midiInputName = NULL; +MIDIPortRef midiPort = NULL; +#define MIDIBUFFERLEN 1026 +uint8_t midiBuffer[MIDIBUFFERLEN]; +int midiBuffer_i = 0; +#endif + +static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) { + (void)pDevice; + +#if PARAMETERS_N + NUM_MIDI_INPUTS > 0 + if (mutex.try_lock()) { +# if PARAMETERS_N > 0 + for (size_t i = 0; i < PARAMETERS_N; i++) { + if (param_data[i].out) + param_values_prev[i] = param_values[i] = plugin_get_parameter(&instance, i); + else if (param_values_prev[i] != param_values[i]) { + plugin_set_parameter(&instance, i, param_values[i]); + param_values_prev[i] = param_values[i]; + } + } +# endif + +# if NUM_MIDI_INPUTS > 0 + if (midiBuffer_i > 0) { + for (int i = 0; i < midiBuffer_i; i+=3) { + plugin_midi_msg_in(&instance, MIDI_BUS_IN, &(midiBuffer[i])); + } + } + midiBuffer_i = 0; +# endif + mutex.unlock(); + } +#endif + +#if NUM_CHANNELS_IN == 0 + (void)pInput; +#else + const float * in_buf = reinterpret_cast(pInput); +#endif + float * out_buf = reinterpret_cast(pOutput); + + ma_uint32 i = 0; + #if NUM_CHANNELS_IN > 0 + size_t ix = 0; + #endif + #if NUM_CHANNELS_OUT > 0 + size_t iy = 0; + #endif + + while (i < frameCount) { + ma_uint32 n = std::min(frameCount - i, static_cast(BLOCK_SIZE)); + + #if NUM_CHANNELS_IN > 0 + for (ma_uint32 j = 0; j < n; j++) + for (size_t k = 0; k < NUM_CHANNELS_IN; k++, ix++) + x_buf[BLOCK_SIZE * k + j] = in_buf[ix]; + #endif + + #if NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN + memset(zero, 0, BLOCK_SIZE * sizeof(float)); + #endif + plugin_process(&instance, x, y, n); + + #if NUM_CHANNELS_OUT > 0 + for (ma_uint32 j = 0; j < n; j++) + for (size_t k = 0; k < NUM_CHANNELS_OUT; k++, iy++) + out_buf[iy] = y_buf[BLOCK_SIZE * k + j]; + #endif + i += n; + } +} + +#if (NUM_MIDI_INPUTS > 0) +void (^midiNotifyBlock)(const MIDINotification *message) = ^(const MIDINotification *message) { + if (message->messageID != kMIDIMsgObjectAdded) + return; + const MIDIObjectAddRemoveNotification *n = reinterpret_cast(message); + MIDIEndpointRef endPoint = n->child; + MIDIPortConnectSource(midiPort, endPoint, NULL); +}; + +void (^midiReceiveBlock)(const MIDIEventList *evtlist, void *srcConnRefCon) = ^(const MIDIEventList *evtlist, void *srcConnRefCon) { + const MIDIEventPacket *p = evtlist->packet; + for (UInt32 i = 0; i < evtlist->numPackets; i++) { + for (UInt32 j = 0; j < p->wordCount; j++) { + const UInt32 w = p->words[j]; + const uint8_t* t = (uint8_t*) &(w); + + if ((t[3] & 0xf0) != 32) + continue; // We only support MIDI 1.0 + if ((t[2] & 0xF0) == 0xF0) + continue; + mutex.lock(); + if (midiBuffer_i < MIDIBUFFERLEN - 3) { + midiBuffer[midiBuffer_i ] = t[2]; + midiBuffer[midiBuffer_i + 1] = t[1]; + midiBuffer[midiBuffer_i + 2] = t[0]; + midiBuffer_i += 3; + } + mutex.unlock(); + } + p = MIDIEventPacketNext(p); + } +}; +#endif + +extern "C" +char audioStart() { + +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 +# if NUM_CHANNELS_IN == 0 + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); +# elif NUM_CHANNELS_OUT == 0 + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_capture); +# else + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); +# endif + + deviceConfig.periodSizeInFrames = BLOCK_SIZE; + deviceConfig.periods = 1; + deviceConfig.performanceProfile = ma_performance_profile_low_latency; + deviceConfig.noPreSilencedOutputBuffer = 1; + deviceConfig.noClip = 0; + deviceConfig.noDisableDenormals = 0; + deviceConfig.noFixedSizedCallback = 1; + deviceConfig.dataCallback = data_callback; + deviceConfig.capture.pDeviceID = NULL; + deviceConfig.capture.format = ma_format_f32; + deviceConfig.capture.channels = NUM_CHANNELS_IN; + deviceConfig.capture.shareMode = ma_share_mode_shared; + deviceConfig.playback.pDeviceID = NULL; + deviceConfig.playback.format = ma_format_f32; + deviceConfig.playback.channels = NUM_CHANNELS_OUT; + deviceConfig.playback.shareMode = ma_share_mode_shared; + + if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) + return false; + +#endif + +#if (NUM_MIDI_INPUTS > 0) + if (midiClientName == NULL) { + midiClientName = CFSTR("template"); + if (midiClientName == NULL) + return false; // Check unint + } + if (midiClient == (MIDIClientRef)NULL) { + if (MIDIClientCreateWithBlock(midiClientName, &midiClient, midiNotifyBlock) != 0) + return false; + } + if (midiInputName == NULL) { + midiInputName = CFSTR("Input"); + if (midiInputName == NULL) + return false; + } + if (midiPort == (MIDIPortRef)NULL) { + if (MIDIInputPortCreateWithProtocol(midiClient, midiInputName, kMIDIProtocol_1_0, &midiPort, midiReceiveBlock) != 0) + return false; + + ItemCount n = MIDIGetNumberOfSources(); + for (ItemCount i = 0; i < n; i++) { + MIDIEndpointRef endPoint = MIDIGetSource(i); + MIDIPortConnectSource(midiPort, endPoint, NULL); + } + } +#endif + + plugin_init(&instance); + +#if PARAMETERS_N > 0 + for (size_t i = 0; i < PARAMETERS_N; i++) { + if (!param_data[i].out) + plugin_set_parameter(&instance, i, param_data[i].def); + param_values_prev[i] = param_values[i] = param_data[i].def; + } +#endif + + plugin_set_sample_rate(&instance, (float)device.sampleRate); + + size_t req = plugin_mem_req(&instance); + if (req != 0) { + mem = malloc(req); + if (mem == NULL) { + plugin_fini(&instance); + #if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 + ma_device_uninit(&device); + #endif + return false; + } + plugin_mem_set(&instance, mem); + } else + mem = NULL; + + plugin_reset(&instance); + +#if NUM_ALL_CHANNELS_IN > 0 +# if AUDIO_BUS_IN >= 0 + size_t ix = 0; + size_t ixb = 0; + for (size_t j = 0; j < NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT; j++) { + if (audio_bus_data[j].out) + continue; + if (audio_bus_data[j].index == AUDIO_BUS_IN) + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++, ixb++) + x[ix] = x_buf + BLOCK_SIZE * ixb; +# if NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN + else if (!audio_bus_data[j].optional) + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++) + x[ix] = zero; +# endif + else + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++) + x[ix] = NULL; + } +# else + for (size_t i = 0; i < NUM_ALL_CHANNELS_IN; i++) + x[i] = NULL; +# endif +#else + x = NULL; +#endif + +#if NUM_ALL_CHANNELS_OUT > 0 +# if AUDIO_BUS_OUT >= 0 + size_t iy = 0; + size_t iyb = 0; + for (size_t j = 0; j < NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT; j++) { + if (!audio_bus_data[j].out) + continue; + if (audio_bus_data[j].index == AUDIO_BUS_OUT) + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++, iyb++) + y[iy] = y_buf + BLOCK_SIZE * iyb; +# if NUM_NON_OPT_CHANNELS_OUT > NUM_CHANNELS_OUT + else if (!audio_bus_data[j].optional) + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++) + y[iy] = zero; +# endif + else + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++) + y[iy] = NULL; + } +# else + for (size_t i = 0; i < NUM_ALL_CHANNELS_OUT; i++) + y[i] = NULL; +# endif +#else + y = NULL; +#endif + +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 + if (ma_device_start(&device) != MA_SUCCESS) { + if (mem != NULL) + free(mem); + ma_device_uninit(&device); + return false; + } +#endif + return true; +} + +extern "C" +void audioStop() { +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 + ma_device_stop(&device); + ma_device_uninit(&device); +#endif + if (mem != NULL) + free(mem); + plugin_fini(&instance); + // No need to close MIDI connections (e.g. via MIDIClientDispose), the system terminates them when the app terminates. +} + +extern "C" +float getParameter(int i) { +#if PARAMETERS_N > 0 + mutex.lock(); + float v = param_values[i]; + mutex.unlock(); + return v; +#else + return 0.f; +#endif +} + +extern "C" +void setParameter(int i, float v) { + if (param_data[i].flags & (PARAM_BYPASS | PARAM_TOGGLED)) + v = v > 0.5f ? 1.f : 0.f; + else if (param_data[i].flags & PARAM_INTEGER) + v = (int32_t)(v + 0.5f); + v = std::min(std::max(v, param_data[i].min), param_data[i].max); +#if PARAMETERS_N > 0 + mutex.lock(); + param_values[i] = v; + mutex.unlock(); +#endif +} diff --git a/templates/ios/src/platform.h b/templates/ios/src/platform.h new file mode 100644 index 0000000..962b66f --- /dev/null +++ b/templates/ios/src/platform.h @@ -0,0 +1,6 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +#define NDEBUG + +#endif diff --git a/templates/ios/tibia-index.js b/templates/ios/tibia-index.js new file mode 100644 index 0000000..6e6b85f --- /dev/null +++ b/templates/ios/tibia-index.js @@ -0,0 +1,11 @@ +var path = require("path"); +var sep = path.sep; + +module.exports = function (data, api) { + api.generateFileFromTemplateFile(`src${sep}data.h`, `src${sep}data.h`, data); + api.copyFile(`src${sep}native.mm`, `src${sep}native.mm`); + api.copyFile(`src${sep}app-Bridging-Header.h`, `src${sep}app-Bridging-Header.h`); + api.copyFile(`src${sep}platform.h`, `src${sep}platform.h`); + api.generateFileFromTemplateFile(`src${sep}app.swift`, `src${sep}app.swift`, data); + api.generateFileFromTemplateFile(`src${sep}index.html`, `src${sep}index.html`, data); +}; diff --git a/test/ios-make.json b/test/ios-make.json new file mode 100644 index 0000000..e458bb6 --- /dev/null +++ b/test/ios-make.json @@ -0,0 +1,11 @@ +{ + "ios_make": { + "header_search_paths": [], + "3rdpartyfiles": [ + "../../../miniaudio/miniaudio.h" + ], + "projectName": "tibia_test", + "targetName": "tibia_test", + "deploymentTarget": 16.6 + } +} diff --git a/test/ios.json b/test/ios.json new file mode 100644 index 0000000..4eb7c3f --- /dev/null +++ b/test/ios.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/test/run.sh b/test/run.sh index 5a9feff..e8ac9f0 100755 --- a/test/run.sh +++ b/test/run.sh @@ -19,6 +19,10 @@ $dir/../tibia $dir/product.json,$dir/company.json,$dir/android.json,$dir/android cp $dir/keystore.jks $dir/../out/android cp $dir/plugin.h $dir/../out/android/src +$dir/../tibia $dir/product.json,$dir/company.json,$dir/ios.json $dir/../templates/ios $dir/../out/ios +$dir/../tibia $dir/product.json,$dir/company.json,$dir/ios.json,$dir/ios-make.json $dir/../templates/ios-make $dir/../out/ios +cp $dir/plugin.h $dir/../out/ios/src + $dir/../tibia $dir/product.json,$dir/company.json,$dir/cmd.json $dir/../templates/cmd $dir/../out/cmd $dir/../tibia $dir/product.json,$dir/company.json,$dir/cmd.json,$dir/cmd-make.json $dir/../templates/cmd-make $dir/../out/cmd cp $dir/plugin.h $dir/../out/cmd/src From 54e5a539a4841ac15bfc36c0eac318ac06b934b4 Mon Sep 17 00:00:00 2001 From: paolo mac Date: Fri, 2 Feb 2024 17:27:02 +0100 Subject: [PATCH 3/6] minimaudio is always instantiated. If no audio in/out chans, it writes 0s to a fake one --- templates/ios/src/native.mm | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/templates/ios/src/native.mm b/templates/ios/src/native.mm index cae7b77..2c95b76 100644 --- a/templates/ios/src/native.mm +++ b/templates/ios/src/native.mm @@ -11,19 +11,14 @@ #include #endif #include -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 #define MINIAUDIO_IMPLEMENTATION #define MA_NO_RUNTIME_LINKING #include "miniaudio.h" - #define BLOCK_SIZE 32 -#endif #define NUM_BUFS (NUM_CHANNELS_IN > NUM_CHANNELS_OUT ? NUM_CHANNELS_IN : NUM_CHANNELS_OUT) -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 static ma_device device; -#endif static plugin instance; static void *mem; #if (NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN) || (NUM_NON_OPT_CHANNELS_OUT > NUM_CHANNELS_OUT) @@ -94,7 +89,7 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, const float * in_buf = reinterpret_cast(pInput); #endif float * out_buf = reinterpret_cast(pOutput); - +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 ma_uint32 i = 0; #if NUM_CHANNELS_IN > 0 size_t ix = 0; @@ -124,6 +119,10 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, #endif i += n; } +#else + for (ma_uint32 i = 0; i < frameCount; i++) + out_buf[i] = 0.f; // Unique fake channel +#endif } #if (NUM_MIDI_INPUTS > 0) @@ -171,6 +170,9 @@ char audioStart() { # else ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); # endif +#else + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); +#endif deviceConfig.periodSizeInFrames = BLOCK_SIZE; deviceConfig.periods = 1; @@ -186,13 +188,15 @@ char audioStart() { deviceConfig.capture.shareMode = ma_share_mode_shared; deviceConfig.playback.pDeviceID = NULL; deviceConfig.playback.format = ma_format_f32; +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 deviceConfig.playback.channels = NUM_CHANNELS_OUT; +#else + deviceConfig.playback.channels = 1; // Fake & muted +#endif deviceConfig.playback.shareMode = ma_share_mode_shared; if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) return false; - -#endif #if (NUM_MIDI_INPUTS > 0) if (midiClientName == NULL) { @@ -238,9 +242,7 @@ char audioStart() { mem = malloc(req); if (mem == NULL) { plugin_fini(&instance); - #if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 ma_device_uninit(&device); - #endif return false; } plugin_mem_set(&instance, mem); @@ -303,23 +305,19 @@ char audioStart() { y = NULL; #endif -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 if (ma_device_start(&device) != MA_SUCCESS) { if (mem != NULL) free(mem); ma_device_uninit(&device); return false; } -#endif return true; } extern "C" void audioStop() { -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 ma_device_stop(&device); ma_device_uninit(&device); -#endif if (mem != NULL) free(mem); plugin_fini(&instance); From 16ed63107fe6a5284b457d86d7cdab019df29f32 Mon Sep 17 00:00:00 2001 From: paolo mac Date: Fri, 2 Feb 2024 17:32:14 +0100 Subject: [PATCH 4/6] ma_device_stop locks until audio worker ends. It's better called before freeing memory --- templates/android/src/jni.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/templates/android/src/jni.cpp b/templates/android/src/jni.cpp index 169ad63..510d4ad 100644 --- a/templates/android/src/jni.cpp +++ b/templates/android/src/jni.cpp @@ -267,14 +267,13 @@ JNIEXPORT void JNICALL JNI_FUNC(nativeAudioStop)(JNIEnv* env, jobject thiz) { (void)env; (void)thiz; - - if (mem != NULL) - free(mem); - plugin_fini(&instance); #if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 ma_device_stop(&device); ma_device_uninit(&device); #endif + if (mem != NULL) + free(mem); + plugin_fini(&instance); } extern "C" From 2d3f850ca0597b764753f300f50a62e7961dd988 Mon Sep 17 00:00:00 2001 From: paolo mac Date: Fri, 2 Feb 2024 17:47:50 +0100 Subject: [PATCH 5/6] same fix for android: when there are 0 in/out audio channels, use ma anyways with a fake output channel --- templates/android/src/jni.cpp | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/templates/android/src/jni.cpp b/templates/android/src/jni.cpp index 510d4ad..cde4110 100644 --- a/templates/android/src/jni.cpp +++ b/templates/android/src/jni.cpp @@ -16,23 +16,21 @@ #if PARAMETERS_N > 0 # include #endif -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 + # define MINIAUDIO_IMPLEMENTATION # define MA_ENABLE_ONLY_SPECIFIC_BACKENDS # define MA_ENABLE_AAUDIO # include # define BLOCK_SIZE 32 -#endif + #if NUM_MIDI_INPUTS > 0 # include # include #endif -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 static ma_device device; -#endif static plugin instance; static void * mem; #if (NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN) || (NUM_NON_OPT_CHANNELS_OUT > NUM_CHANNELS_OUT) @@ -103,6 +101,7 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, const float * in_buf = reinterpret_cast(pInput); float * out_buf = reinterpret_cast(pOutput); +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 ma_uint32 i = 0; #if NUM_CHANNELS_IN > 0 size_t ix = 0; @@ -133,6 +132,10 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, i += n; } +#else + for (ma_uint32 i = 0; i < frameCount; i++) + out_buf[i] = 0.f; // Unique fake channel +#endif } extern "C" @@ -143,12 +146,16 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) { #if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 # if NUM_CHANNELS_IN == 0 - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); # elif NUM_CHANNELS_OUT == 0 - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_capture); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_capture); # else - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); # endif +#else + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); +#endif + deviceConfig.periodSizeInFrames = BLOCK_SIZE; deviceConfig.periods = 1; deviceConfig.performanceProfile = ma_performance_profile_low_latency; @@ -163,12 +170,15 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) { deviceConfig.capture.shareMode = ma_share_mode_shared; deviceConfig.playback.pDeviceID = NULL; deviceConfig.playback.format = ma_format_f32; - deviceConfig.playback.channels = NUM_CHANNELS_OUT; +#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 + deviceConfig.playback.channels = NUM_CHANNELS_OUT; +#else + deviceConfig.playback.channels = 1; // Fake & muted +#endif deviceConfig.playback.shareMode = ma_share_mode_shared; if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) return false; -#endif plugin_init(&instance); @@ -186,9 +196,7 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) { mem = malloc(req); if (mem == NULL) { plugin_fini(&instance); -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 ma_device_uninit(&device); -#endif return false; } plugin_mem_set(&instance, mem); @@ -250,14 +258,12 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) { y = NULL; #endif -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 if (ma_device_start(&device) != MA_SUCCESS) { if (mem != NULL) free(mem); ma_device_uninit(&device); return false; } -#endif return true; } @@ -267,10 +273,8 @@ JNIEXPORT void JNICALL JNI_FUNC(nativeAudioStop)(JNIEnv* env, jobject thiz) { (void)env; (void)thiz; -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 ma_device_stop(&device); ma_device_uninit(&device); -#endif if (mem != NULL) free(mem); plugin_fini(&instance); From 152a33c65ed04696fd25f998bffa59aa4b75dda4 Mon Sep 17 00:00:00 2001 From: Stefano D'Angelo Date: Mon, 5 Feb 2024 13:55:57 +0100 Subject: [PATCH 6/6] cleanup (tentative) and get ready for merge --- templates/android/src/jni.cpp | 21 +- templates/ios-make/Makefile | 45 ++-- templates/ios-make/project.yml | 14 +- templates/ios-make/vars.mk | 10 +- templates/ios/src/native.mm | 435 ++++++++++++++++----------------- templates/ios/src/platform.h | 6 - templates/ios/tibia-index.js | 5 +- test/ios-make.json | 9 +- test/ios.json | 3 - test/run.sh | 4 +- 10 files changed, 272 insertions(+), 280 deletions(-) delete mode 100644 templates/ios/src/platform.h delete mode 100644 test/ios.json diff --git a/templates/android/src/jni.cpp b/templates/android/src/jni.cpp index cde4110..6f099c6 100644 --- a/templates/android/src/jni.cpp +++ b/templates/android/src/jni.cpp @@ -101,7 +101,6 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, const float * in_buf = reinterpret_cast(pInput); float * out_buf = reinterpret_cast(pOutput); -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 ma_uint32 i = 0; #if NUM_CHANNELS_IN > 0 size_t ix = 0; @@ -128,14 +127,13 @@ static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, for (ma_uint32 j = 0; j < n; j++) for (size_t k = 0; k < NUM_CHANNELS_OUT; k++, iy++) out_buf[iy] = y_buf[BLOCK_SIZE * k + j]; +#elif NUM_CHANNELS_IN == 0 + for (ma_uint32 j = 0; j < n; j++) + out_buf[j] = 0; #endif i += n; } -#else - for (ma_uint32 i = 0; i < frameCount; i++) - out_buf[i] = 0.f; // Unique fake channel -#endif } extern "C" @@ -146,14 +144,14 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) { #if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 # if NUM_CHANNELS_IN == 0 - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); # elif NUM_CHANNELS_OUT == 0 - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_capture); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_capture); # else - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); # endif #else - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); #endif deviceConfig.periodSizeInFrames = BLOCK_SIZE; @@ -171,9 +169,9 @@ JNI_FUNC(nativeAudioStart)(JNIEnv* env, jobject thiz) { deviceConfig.playback.pDeviceID = NULL; deviceConfig.playback.format = ma_format_f32; #if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 - deviceConfig.playback.channels = NUM_CHANNELS_OUT; + deviceConfig.playback.channels = NUM_CHANNELS_OUT; #else - deviceConfig.playback.channels = 1; // Fake & muted + deviceConfig.playback.channels = 1; // Fake & muted #endif deviceConfig.playback.shareMode = ma_share_mode_shared; @@ -273,6 +271,7 @@ JNIEXPORT void JNICALL JNI_FUNC(nativeAudioStop)(JNIEnv* env, jobject thiz) { (void)env; (void)thiz; + ma_device_stop(&device); ma_device_uninit(&device); if (mem != NULL) diff --git a/templates/ios-make/Makefile b/templates/ios-make/Makefile index 561bbe0..84d1ffa 100644 --- a/templates/ios-make/Makefile +++ b/templates/ios-make/Makefile @@ -1,35 +1,38 @@ include vars.mk -ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) +COMMON_DIR := $(or $(COMMON_DIR),.) +DATA_DIR := $(or $(DATA_DIR),.) +PLUGIN_DIR := $(or $(PLUGIN_DIR),src) +SOURCES := \ + ${DATA_DIR}/src/data.h \ + ${DATA_DIR}/src/index.html \ + ${COMMON_DIR}/src/app.swift \ + ${COMMON_DIR}/src/native.mm \ + ${COMMON_DIR}/src/app-Bridging-Header.h \ + ${PLUGIN_DIR}/plugin.h \ + ${C_SRCS_EXTRA} \ + ${CXX_SRCS_EXTRA} +SOURCES_OUT = $(addprefix build/gen/src/, $(notdir $(SOURCES))) -SOURCES := data.h index.html app.swift native.mm app-Bridging-Header.h platform.h plugin.h -SOURCES_IN := $(addprefix ${ROOT_DIR}/src/, ${SOURCES}) -SOURCES_OUT := $(addprefix ${ROOT_DIR}/build/gen/src/, ${SOURCES}) -EXT_SOURCES_IN := ${3RDPARTYFILES} -EXT_SOURCES_OUT := +all: build/gen/${BUNDLE_NAME}.xcodeproj - - -all: build/gen/${PROJECT_NAME}.xcodeproj - -${SOURCES_OUT}: ${SOURCES_IN} ${EXT_SOURCES_IN} | build/gen/src - cp $^ ${ROOT_DIR}/build/gen/src - -${EXT_SOURCES_OUT}: ${EXT_SOURCES_IN} - cp ${EXT_SOURCES_IN} ${ROOT_DIR}/build/gen/src/ - -build/gen/${PROJECT_NAME}.xcodeproj: ${SOURCES_OUT} +build/gen/${BUNDLE_NAME}.xcodeproj: ${SOURCES_OUT} xcodegen generate --spec project.yml - mv ${PROJECT_NAME}.xcodeproj build/gen/${PROJECT_NAME}.xcodeproj + mv ${BUNDLE_NAME}.xcodeproj build/gen/${BUNDLE_NAME}.xcodeproj mv Info.plist build/gen/Info.plist -build/gen build/gen/src: +build/gen/src: mkdir -p $@ clean: rm -fr build -install: +.PHONY: all clean -.PHONY: all clean install +.SECONDEXPANSION: + +PERCENT := % + +$(SOURCES_OUT): build/gen/src/%: $$(filter $$(PERCENT)/%,$$(SOURCES)) | build/gen/src + cp $^ $@ diff --git a/templates/ios-make/project.yml b/templates/ios-make/project.yml index a41fd14..d4975ac 100644 --- a/templates/ios-make/project.yml +++ b/templates/ios-make/project.yml @@ -1,6 +1,6 @@ -name: {{=it.ios_make.projectName}} +name: {{=it.product.bundleName}} targets: - {{=it.ios_make.targetName}}: + {{=it.product.bundleName}}: platform: [iOS] deploymentTarget: iOS: {{=it.ios_make.deploymentTarget}} @@ -9,11 +9,15 @@ targets: - path: src settings: base: - PRODUCT_BUNDLE_IDENTIFIER: com.orastron.{{=it.product.bundleName}} + PRODUCT_BUNDLE_IDENTIFIER: {{=it.ios_make.productBundleIdentifier}} SWIFT_OBJC_BRIDGING_HEADER: src/app-Bridging-Header.h - HEADER_SEARCH_PATHS: {{~it.ios_make.header_search_paths:p}} +{{?it.ios_make.headerSearchPaths}} + HEADER_SEARCH_PATHS: {{~it.ios_make.headerSearchPaths :p}} - {{=p}}{{~}} +{{?}} info: path: Info.plist +{{?it.product.buses.filter(x => x.type == "audio" && x.direction == "input").length > 0}} properties: - NSMicrophoneUsageDescription: Audio input access needed to run the example + NSMicrophoneUsageDescription: Need audio input for processing sound +{{?}} diff --git a/templates/ios-make/vars.mk b/templates/ios-make/vars.mk index fdc94b9..470a7aa 100644 --- a/templates/ios-make/vars.mk +++ b/templates/ios-make/vars.mk @@ -1,8 +1,8 @@ BUNDLE_NAME := {{=it.product.bundleName}} -PROJECT_NAME := {{=it.ios_make.projectName}} -TARGET_NAME := {{=it.ios_make.targetName}} +C_SRCS_EXTRA := {{=it.make && it.make.cSrcs ? it.make.cSrcs : ""}} {{=it.ios_make && it.ios_make.cSrcs ? it.ios_make.cSrcs : ""}} +CXX_SRCS_EXTRA := {{=it.make && it.make.cxxSrcs ? it.make.cxxSrcs : ""}} {{=it.ios_make && it.ios_make.cxxSrcs ? it.ios_make.cxxSrcs : ""}} -3RDPARTYFILES := {{=it.ios_make["3rdpartyfiles"].join(' ')}} - -HAS_MIDI_IN := {{=it.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0 ? "yes" : "no"}} +COMMON_DIR := {{=it.ios_make && it.ios_make.commonDir ? it.ios_make.commonDir : (it.make && it.make.commonDir ? it.make.commonDir : "")}} +DATA_DIR := {{=it.ios_make && it.ios_make.dataDir ? it.ios_make.dataDir : (it.make && it.make.dataDir ? it.make.dataDir : "")}} +PLUGIN_DIR := {{=it.ios_make && it.ios_make.pluginDir ? it.ios_make.pluginDir : (it.make && it.make.pluginDir ? it.make.pluginDir : "")}} diff --git a/templates/ios/src/native.mm b/templates/ios/src/native.mm index 2c95b76..b9646c4 100644 --- a/templates/ios/src/native.mm +++ b/templates/ios/src/native.mm @@ -16,7 +16,7 @@ #include "miniaudio.h" #define BLOCK_SIZE 32 -#define NUM_BUFS (NUM_CHANNELS_IN > NUM_CHANNELS_OUT ? NUM_CHANNELS_IN : NUM_CHANNELS_OUT) +#define NUM_BUFS (NUM_CHANNELS_IN > NUM_CHANNELS_OUT ? NUM_CHANNELS_IN : NUM_CHANNELS_OUT) static ma_device device; static plugin instance; @@ -50,302 +50,301 @@ CFStringRef midiClientName = NULL; MIDIClientRef midiClient = NULL; CFStringRef midiInputName = NULL; MIDIPortRef midiPort = NULL; -#define MIDIBUFFERLEN 1026 +#define MIDIBUFFERLEN 1023 uint8_t midiBuffer[MIDIBUFFERLEN]; int midiBuffer_i = 0; #endif static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) { - (void)pDevice; - + (void)pDevice; + #if PARAMETERS_N + NUM_MIDI_INPUTS > 0 - if (mutex.try_lock()) { + if (mutex.try_lock()) { # if PARAMETERS_N > 0 - for (size_t i = 0; i < PARAMETERS_N; i++) { - if (param_data[i].out) - param_values_prev[i] = param_values[i] = plugin_get_parameter(&instance, i); - else if (param_values_prev[i] != param_values[i]) { - plugin_set_parameter(&instance, i, param_values[i]); - param_values_prev[i] = param_values[i]; - } - } + for (size_t i = 0; i < PARAMETERS_N; i++) { + if (param_data[i].out) + param_values_prev[i] = param_values[i] = plugin_get_parameter(&instance, i); + else if (param_values_prev[i] != param_values[i]) { + plugin_set_parameter(&instance, i, param_values[i]); + param_values_prev[i] = param_values[i]; + } + } # endif # if NUM_MIDI_INPUTS > 0 - if (midiBuffer_i > 0) { - for (int i = 0; i < midiBuffer_i; i+=3) { - plugin_midi_msg_in(&instance, MIDI_BUS_IN, &(midiBuffer[i])); - } - } - midiBuffer_i = 0; + if (midiBuffer_i > 0) { + for (int i = 0; i < midiBuffer_i; i+=3) { + plugin_midi_msg_in(&instance, MIDI_BUS_IN, &(midiBuffer[i])); + } + } + midiBuffer_i = 0; # endif - mutex.unlock(); - } + mutex.unlock(); + } #endif - + #if NUM_CHANNELS_IN == 0 - (void)pInput; + (void)pInput; #else - const float * in_buf = reinterpret_cast(pInput); + const float * in_buf = reinterpret_cast(pInput); #endif - float * out_buf = reinterpret_cast(pOutput); -#if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 - ma_uint32 i = 0; - #if NUM_CHANNELS_IN > 0 - size_t ix = 0; - #endif - #if NUM_CHANNELS_OUT > 0 - size_t iy = 0; - #endif - - while (i < frameCount) { - ma_uint32 n = std::min(frameCount - i, static_cast(BLOCK_SIZE)); - - #if NUM_CHANNELS_IN > 0 - for (ma_uint32 j = 0; j < n; j++) - for (size_t k = 0; k < NUM_CHANNELS_IN; k++, ix++) - x_buf[BLOCK_SIZE * k + j] = in_buf[ix]; - #endif - - #if NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN - memset(zero, 0, BLOCK_SIZE * sizeof(float)); - #endif - plugin_process(&instance, x, y, n); - - #if NUM_CHANNELS_OUT > 0 - for (ma_uint32 j = 0; j < n; j++) - for (size_t k = 0; k < NUM_CHANNELS_OUT; k++, iy++) - out_buf[iy] = y_buf[BLOCK_SIZE * k + j]; - #endif - i += n; - } -#else - for (ma_uint32 i = 0; i < frameCount; i++) - out_buf[i] = 0.f; // Unique fake channel + float * out_buf = reinterpret_cast(pOutput); + ma_uint32 i = 0; +#if NUM_CHANNELS_IN > 0 + size_t ix = 0; #endif +#if NUM_CHANNELS_OUT > 0 + size_t iy = 0; +#endif + + while (i < frameCount) { + ma_uint32 n = std::min(frameCount - i, static_cast(BLOCK_SIZE)); + +#if NUM_CHANNELS_IN > 0 + for (ma_uint32 j = 0; j < n; j++) + for (size_t k = 0; k < NUM_CHANNELS_IN; k++, ix++) + x_buf[BLOCK_SIZE * k + j] = in_buf[ix]; +#endif + +#if NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN + memset(zero, 0, BLOCK_SIZE * sizeof(float)); +#endif + + plugin_process(&instance, x, y, n); + +#if NUM_CHANNELS_OUT > 0 + for (ma_uint32 j = 0; j < n; j++) + for (size_t k = 0; k < NUM_CHANNELS_OUT; k++, iy++) + out_buf[iy] = y_buf[BLOCK_SIZE * k + j]; +#elif NUM_CHANNELS_IN == 0 + for (ma_uint32 j = 0; j < n; j++) + out_buf[j] = 0.f; +#endif + i += n; + } } #if (NUM_MIDI_INPUTS > 0) void (^midiNotifyBlock)(const MIDINotification *message) = ^(const MIDINotification *message) { - if (message->messageID != kMIDIMsgObjectAdded) - return; - const MIDIObjectAddRemoveNotification *n = reinterpret_cast(message); - MIDIEndpointRef endPoint = n->child; - MIDIPortConnectSource(midiPort, endPoint, NULL); + if (message->messageID != kMIDIMsgObjectAdded) + return; + const MIDIObjectAddRemoveNotification *n = reinterpret_cast(message); + MIDIEndpointRef endPoint = n->child; + MIDIPortConnectSource(midiPort, endPoint, NULL); }; void (^midiReceiveBlock)(const MIDIEventList *evtlist, void *srcConnRefCon) = ^(const MIDIEventList *evtlist, void *srcConnRefCon) { - const MIDIEventPacket *p = evtlist->packet; - for (UInt32 i = 0; i < evtlist->numPackets; i++) { - for (UInt32 j = 0; j < p->wordCount; j++) { - const UInt32 w = p->words[j]; - const uint8_t* t = (uint8_t*) &(w); - - if ((t[3] & 0xf0) != 32) - continue; // We only support MIDI 1.0 - if ((t[2] & 0xF0) == 0xF0) - continue; - mutex.lock(); - if (midiBuffer_i < MIDIBUFFERLEN - 3) { - midiBuffer[midiBuffer_i ] = t[2]; - midiBuffer[midiBuffer_i + 1] = t[1]; - midiBuffer[midiBuffer_i + 2] = t[0]; - midiBuffer_i += 3; - } - mutex.unlock(); - } - p = MIDIEventPacketNext(p); - } + const MIDIEventPacket *p = evtlist->packet; + for (UInt32 i = 0; i < evtlist->numPackets; i++) { + for (UInt32 j = 0; j < p->wordCount; j++) { + const UInt32 w = p->words[j]; + const uint8_t* t = (uint8_t*) &(w); + + if ((t[3] & 0xf0) != 32) + continue; // We only support MIDI 1.0 + if ((t[2] & 0xF0) == 0xF0) + continue; + mutex.lock(); + if (midiBuffer_i < MIDIBUFFERLEN - 3) { + midiBuffer[midiBuffer_i ] = t[2]; + midiBuffer[midiBuffer_i + 1] = t[1]; + midiBuffer[midiBuffer_i + 2] = t[0]; + midiBuffer_i += 3; + } + mutex.unlock(); + } + p = MIDIEventPacketNext(p); + } }; #endif extern "C" char audioStart() { - #if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 # if NUM_CHANNELS_IN == 0 - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); # elif NUM_CHANNELS_OUT == 0 - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_capture); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_capture); # else - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_duplex); # endif #else - ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); #endif - deviceConfig.periodSizeInFrames = BLOCK_SIZE; - deviceConfig.periods = 1; - deviceConfig.performanceProfile = ma_performance_profile_low_latency; - deviceConfig.noPreSilencedOutputBuffer = 1; - deviceConfig.noClip = 0; - deviceConfig.noDisableDenormals = 0; - deviceConfig.noFixedSizedCallback = 1; - deviceConfig.dataCallback = data_callback; - deviceConfig.capture.pDeviceID = NULL; - deviceConfig.capture.format = ma_format_f32; - deviceConfig.capture.channels = NUM_CHANNELS_IN; - deviceConfig.capture.shareMode = ma_share_mode_shared; - deviceConfig.playback.pDeviceID = NULL; - deviceConfig.playback.format = ma_format_f32; + deviceConfig.periodSizeInFrames = BLOCK_SIZE; + deviceConfig.periods = 1; + deviceConfig.performanceProfile = ma_performance_profile_low_latency; + deviceConfig.noPreSilencedOutputBuffer = 1; + deviceConfig.noClip = 0; + deviceConfig.noDisableDenormals = 0; + deviceConfig.noFixedSizedCallback = 1; + deviceConfig.dataCallback = data_callback; + deviceConfig.capture.pDeviceID = NULL; + deviceConfig.capture.format = ma_format_f32; + deviceConfig.capture.channels = NUM_CHANNELS_IN; + deviceConfig.capture.shareMode = ma_share_mode_shared; + deviceConfig.playback.pDeviceID = NULL; + deviceConfig.playback.format = ma_format_f32; #if NUM_CHANNELS_IN + NUM_CHANNELS_OUT > 0 - deviceConfig.playback.channels = NUM_CHANNELS_OUT; + deviceConfig.playback.channels = NUM_CHANNELS_OUT; #else - deviceConfig.playback.channels = 1; // Fake & muted + deviceConfig.playback.channels = 1; // Fake & muted #endif - deviceConfig.playback.shareMode = ma_share_mode_shared; + deviceConfig.playback.shareMode = ma_share_mode_shared; - if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) - return false; + if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) + return false; #if (NUM_MIDI_INPUTS > 0) - if (midiClientName == NULL) { - midiClientName = CFSTR("template"); - if (midiClientName == NULL) - return false; // Check unint - } - if (midiClient == (MIDIClientRef)NULL) { - if (MIDIClientCreateWithBlock(midiClientName, &midiClient, midiNotifyBlock) != 0) - return false; - } - if (midiInputName == NULL) { - midiInputName = CFSTR("Input"); - if (midiInputName == NULL) - return false; - } - if (midiPort == (MIDIPortRef)NULL) { - if (MIDIInputPortCreateWithProtocol(midiClient, midiInputName, kMIDIProtocol_1_0, &midiPort, midiReceiveBlock) != 0) - return false; + if (midiClientName == NULL) { + midiClientName = CFSTR("template"); + if (midiClientName == NULL) + return false; // Check unint + } + if (midiClient == (MIDIClientRef)NULL) { + if (MIDIClientCreateWithBlock(midiClientName, &midiClient, midiNotifyBlock) != 0) + return false; + } + if (midiInputName == NULL) { + midiInputName = CFSTR("Input"); + if (midiInputName == NULL) + return false; + } + if (midiPort == (MIDIPortRef)NULL) { + if (MIDIInputPortCreateWithProtocol(midiClient, midiInputName, kMIDIProtocol_1_0, &midiPort, midiReceiveBlock) != 0) + return false; - ItemCount n = MIDIGetNumberOfSources(); - for (ItemCount i = 0; i < n; i++) { - MIDIEndpointRef endPoint = MIDIGetSource(i); - MIDIPortConnectSource(midiPort, endPoint, NULL); - } - } + ItemCount n = MIDIGetNumberOfSources(); + for (ItemCount i = 0; i < n; i++) { + MIDIEndpointRef endPoint = MIDIGetSource(i); + MIDIPortConnectSource(midiPort, endPoint, NULL); + } + } #endif - - plugin_init(&instance); - + + plugin_init(&instance); + #if PARAMETERS_N > 0 - for (size_t i = 0; i < PARAMETERS_N; i++) { - if (!param_data[i].out) - plugin_set_parameter(&instance, i, param_data[i].def); - param_values_prev[i] = param_values[i] = param_data[i].def; - } + for (size_t i = 0; i < PARAMETERS_N; i++) { + if (!param_data[i].out) + plugin_set_parameter(&instance, i, param_data[i].def); + param_values_prev[i] = param_values[i] = param_data[i].def; + } #endif - - plugin_set_sample_rate(&instance, (float)device.sampleRate); + + plugin_set_sample_rate(&instance, (float)device.sampleRate); - size_t req = plugin_mem_req(&instance); - if (req != 0) { - mem = malloc(req); - if (mem == NULL) { - plugin_fini(&instance); - ma_device_uninit(&device); - return false; - } - plugin_mem_set(&instance, mem); - } else - mem = NULL; + size_t req = plugin_mem_req(&instance); + if (req != 0) { + mem = malloc(req); + if (mem == NULL) { + plugin_fini(&instance); + ma_device_uninit(&device); + return false; + } + plugin_mem_set(&instance, mem); + } else + mem = NULL; - plugin_reset(&instance); - + plugin_reset(&instance); + #if NUM_ALL_CHANNELS_IN > 0 # if AUDIO_BUS_IN >= 0 - size_t ix = 0; - size_t ixb = 0; - for (size_t j = 0; j < NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT; j++) { - if (audio_bus_data[j].out) - continue; - if (audio_bus_data[j].index == AUDIO_BUS_IN) - for (char k = 0; k < audio_bus_data[j].channels; k++, ix++, ixb++) - x[ix] = x_buf + BLOCK_SIZE * ixb; + size_t ix = 0; + size_t ixb = 0; + for (size_t j = 0; j < NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT; j++) { + if (audio_bus_data[j].out) + continue; + if (audio_bus_data[j].index == AUDIO_BUS_IN) + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++, ixb++) + x[ix] = x_buf + BLOCK_SIZE * ixb; # if NUM_NON_OPT_CHANNELS_IN > NUM_CHANNELS_IN - else if (!audio_bus_data[j].optional) - for (char k = 0; k < audio_bus_data[j].channels; k++, ix++) - x[ix] = zero; + else if (!audio_bus_data[j].optional) + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++) + x[ix] = zero; # endif - else - for (char k = 0; k < audio_bus_data[j].channels; k++, ix++) - x[ix] = NULL; - } + else + for (char k = 0; k < audio_bus_data[j].channels; k++, ix++) + x[ix] = NULL; + } # else - for (size_t i = 0; i < NUM_ALL_CHANNELS_IN; i++) - x[i] = NULL; + for (size_t i = 0; i < NUM_ALL_CHANNELS_IN; i++) + x[i] = NULL; # endif #else - x = NULL; + x = NULL; #endif - + #if NUM_ALL_CHANNELS_OUT > 0 # if AUDIO_BUS_OUT >= 0 - size_t iy = 0; - size_t iyb = 0; - for (size_t j = 0; j < NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT; j++) { - if (!audio_bus_data[j].out) - continue; - if (audio_bus_data[j].index == AUDIO_BUS_OUT) - for (char k = 0; k < audio_bus_data[j].channels; k++, iy++, iyb++) - y[iy] = y_buf + BLOCK_SIZE * iyb; + size_t iy = 0; + size_t iyb = 0; + for (size_t j = 0; j < NUM_AUDIO_BUSES_IN + NUM_AUDIO_BUSES_OUT; j++) { + if (!audio_bus_data[j].out) + continue; + if (audio_bus_data[j].index == AUDIO_BUS_OUT) + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++, iyb++) + y[iy] = y_buf + BLOCK_SIZE * iyb; # if NUM_NON_OPT_CHANNELS_OUT > NUM_CHANNELS_OUT - else if (!audio_bus_data[j].optional) - for (char k = 0; k < audio_bus_data[j].channels; k++, iy++) - y[iy] = zero; + else if (!audio_bus_data[j].optional) + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++) + y[iy] = zero; # endif - else - for (char k = 0; k < audio_bus_data[j].channels; k++, iy++) - y[iy] = NULL; - } + else + for (char k = 0; k < audio_bus_data[j].channels; k++, iy++) + y[iy] = NULL; + } # else - for (size_t i = 0; i < NUM_ALL_CHANNELS_OUT; i++) - y[i] = NULL; + for (size_t i = 0; i < NUM_ALL_CHANNELS_OUT; i++) + y[i] = NULL; # endif #else - y = NULL; + y = NULL; #endif - - if (ma_device_start(&device) != MA_SUCCESS) { - if (mem != NULL) - free(mem); - ma_device_uninit(&device); - return false; - } - return true; + + if (ma_device_start(&device) != MA_SUCCESS) { + if (mem != NULL) + free(mem); + ma_device_uninit(&device); + return false; + } + return true; } extern "C" void audioStop() { - ma_device_stop(&device); - ma_device_uninit(&device); - if (mem != NULL) - free(mem); - plugin_fini(&instance); - // No need to close MIDI connections (e.g. via MIDIClientDispose), the system terminates them when the app terminates. + ma_device_stop(&device); + ma_device_uninit(&device); + if (mem != NULL) + free(mem); + plugin_fini(&instance); + // No need to close MIDI connections (e.g. via MIDIClientDispose), the system terminates them when the app terminates. } extern "C" float getParameter(int i) { #if PARAMETERS_N > 0 - mutex.lock(); - float v = param_values[i]; - mutex.unlock(); - return v; + mutex.lock(); + float v = param_values[i]; + mutex.unlock(); + return v; #else - return 0.f; + (void)i; + return 0.f; #endif } extern "C" void setParameter(int i, float v) { - if (param_data[i].flags & (PARAM_BYPASS | PARAM_TOGGLED)) - v = v > 0.5f ? 1.f : 0.f; - else if (param_data[i].flags & PARAM_INTEGER) - v = (int32_t)(v + 0.5f); - v = std::min(std::max(v, param_data[i].min), param_data[i].max); + if (param_data[i].flags & (PARAM_BYPASS | PARAM_TOGGLED)) + v = v > 0.5f ? 1.f : 0.f; + else if (param_data[i].flags & PARAM_INTEGER) + v = (int32_t)(v + 0.5f); + v = std::min(std::max(v, param_data[i].min), param_data[i].max); #if PARAMETERS_N > 0 - mutex.lock(); - param_values[i] = v; - mutex.unlock(); + mutex.lock(); + param_values[i] = v; + mutex.unlock(); #endif } diff --git a/templates/ios/src/platform.h b/templates/ios/src/platform.h deleted file mode 100644 index 962b66f..0000000 --- a/templates/ios/src/platform.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef PLATFORM_H -#define PLATFORM_H - -#define NDEBUG - -#endif diff --git a/templates/ios/tibia-index.js b/templates/ios/tibia-index.js index 6e6b85f..c7085d6 100644 --- a/templates/ios/tibia-index.js +++ b/templates/ios/tibia-index.js @@ -3,9 +3,8 @@ var sep = path.sep; module.exports = function (data, api) { api.generateFileFromTemplateFile(`src${sep}data.h`, `src${sep}data.h`, data); + api.generateFileFromTemplateFile(`src${sep}index.html`, `src${sep}index.html`, data); api.copyFile(`src${sep}native.mm`, `src${sep}native.mm`); api.copyFile(`src${sep}app-Bridging-Header.h`, `src${sep}app-Bridging-Header.h`); - api.copyFile(`src${sep}platform.h`, `src${sep}platform.h`); - api.generateFileFromTemplateFile(`src${sep}app.swift`, `src${sep}app.swift`, data); - api.generateFileFromTemplateFile(`src${sep}index.html`, `src${sep}index.html`, data); + api.copyFile(`src${sep}app.swift`, `src${sep}app.swift`, data); }; diff --git a/test/ios-make.json b/test/ios-make.json index e458bb6..9b267b2 100644 --- a/test/ios-make.json +++ b/test/ios-make.json @@ -1,11 +1,8 @@ { "ios_make": { - "header_search_paths": [], - "3rdpartyfiles": [ - "../../../miniaudio/miniaudio.h" - ], - "projectName": "tibia_test", - "targetName": "tibia_test", + "headerSearchPaths": [], + "sourcesExtra": [], + "productBundleIdentifier": "com.example.tibia_test", "deploymentTarget": 16.6 } } diff --git a/test/ios.json b/test/ios.json deleted file mode 100644 index 4eb7c3f..0000000 --- a/test/ios.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} \ No newline at end of file diff --git a/test/run.sh b/test/run.sh index e8ac9f0..389db8f 100755 --- a/test/run.sh +++ b/test/run.sh @@ -19,8 +19,8 @@ $dir/../tibia $dir/product.json,$dir/company.json,$dir/android.json,$dir/android cp $dir/keystore.jks $dir/../out/android cp $dir/plugin.h $dir/../out/android/src -$dir/../tibia $dir/product.json,$dir/company.json,$dir/ios.json $dir/../templates/ios $dir/../out/ios -$dir/../tibia $dir/product.json,$dir/company.json,$dir/ios.json,$dir/ios-make.json $dir/../templates/ios-make $dir/../out/ios +$dir/../tibia $dir/product.json,$dir/company.json $dir/../templates/ios $dir/../out/ios +$dir/../tibia $dir/product.json,$dir/company.json,$dir/ios-make.json $dir/../templates/ios-make $dir/../out/ios cp $dir/plugin.h $dir/../out/ios/src $dir/../tibia $dir/product.json,$dir/company.json,$dir/cmd.json $dir/../templates/cmd $dir/../out/cmd