# Makefile for AMY , including an example

TARGET = amy-example amy-message amy-piano
LIBS =  -lm  -pthread

UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
	SOURCES = src/macos_midi.m
	CFLAGS += -DMACOS
	LIBS = -framework AudioUnit -framework CoreAudio -framework CoreFoundation -framework CoreMIDI -framework Cocoa -lstdc++ 
	# Needed for brew's python3.12+ on MacOS
	EXTRA_PIP_ENV = PIP_BREAK_SYSTEM_PACKAGES=1 
	CC = clang
else
	CC = gcc
endif

# Linux MIDI is ALSA raw MIDI (src/linux_midi.c), the counterpart of
# src/macos_midi.m -- and it is OPTIONAL, detected rather than assumed.
# A synth library may not make itself unbuildable over a device layer:
# plenty of Linux boxes (CI runners, containers, a headless Pi) have no
# libasound2-dev, and before this they built amy perfectly well. Without
# it AMY_ALSA is unset, linux_midi.c compiles to nothing, and amy_midi.c
# keeps the no-op run_midi/stop_midi it always had there.
#   apt install libasound2-dev
ifeq ($(UNAME_S),Linux)
HAVE_ALSA := $(shell echo 'int main(){return 0;}' | $(CC) -x c - -include alsa/asoundlib.h -lasound -o /dev/null >/dev/null 2>&1 && echo 1)
ifeq ($(HAVE_ALSA),1)
	SOURCES += src/linux_midi.c
	CFLAGS += -DAMY_ALSA
	LIBS += -lasound
else
$(info amy: no libasound2-dev, building without linux MIDI)
endif
endif


# on Raspberry Pi, at least under 32-bit mode, libatomic and libdl are needed.
ifeq ($(shell uname -m), armv7l)
LIBS += -ldl  -latomic
endif

ifeq ($(shell uname -m), armv6l)
LIBS += -ldl  -latomic
endif

CFLAGS += -O3 -Wall -Wno-strict-aliasing -Wextra -Wno-unused-parameter -Wpointer-arith -Wno-float-conversion -Wno-missing-declarations -DAMY_WAVETABLE
#CFLAGS += -DAMY_DEBUG
# -Wdouble-promotion
# Table-driven C API exports (AMY_C_API_EXPORTED_FUNCTIONS), generated by
# scripts/gen_amy_c_api.py so a table entry can never miss the export list.
include src/amy_c_api_exports.mk
EMSCRIPTEN_OPTIONS = -s WASM=1 --bind \
-DMA_ENABLE_AUDIO_WORKLETS -sAUDIO_WORKLET=1 -sWASM_WORKERS=1  \
-sSTACK_SIZE=128000\
-sMODULARIZE -s 'EXPORT_NAME="amyModule"' \
-s EXPORTED_RUNTIME_METHODS="['cwrap','ccall', 'HEAPU8', 'wasmMemory']" \
-s EXPORTED_FUNCTIONS="['_amy_add_event', \
	 '_amy_live_stop', '_amy_set_external_input_buffer', '_amy_live_start_web', '_amy_live_start_web_audioin',\
	  '_amy_start_web', '_amy_start_web_no_synths', '_malloc', '_free', '_size_of_amy_event', '_cos_lut', \
	  '_amy_set_external_hook_context', '_amy_get_external_hook_context', $(AMY_C_API_EXPORTED_FUNCTIONS)]" \
-s SUPPORT_LONGJMP=emscripten \
-s INITIAL_MEMORY=128mb -s TOTAL_STACK=64mb -s ALLOW_MEMORY_GROWTH=1  \
-s ASYNCIFY -s ASYNCIFY_STACK_SIZE=128000
PYTHON = python3

.PHONY: default all clean amy-module test ctest web deploy-web godot-api c-api check-c-api

default: $(TARGET)
all: default

# Regenerate the table-driven cross-platform C API bindings (pyamy/micropython/
# web/godot .inc + .js + exports fragment + the amy/__init__.py block).
c-api:
	$(PYTHON) scripts/gen_amy_c_api.py
	$(PYTHON) scripts/gen_amy_js_api.py
	$(PYTHON) scripts/gen_patches_js.py
	$(PYTHON) scripts/gen_pcm_presets_js.py

check-c-api:
	$(PYTHON) scripts/gen_amy_c_api.py --check
	$(PYTHON) scripts/gen_amy_js_api.py --check
	$(PYTHON) scripts/gen_patches_js.py --check
	$(PYTHON) scripts/gen_pcm_presets_js.py --check

SOURCES += src/algorithms.c src/amy.c src/envelope.c src/examples.c src/parse.c \
	src/filters.c src/oscillators.c src/pcm.c src/interp_partials.c src/custom.c \
	src/delay.c src/log2_exp2.c src/patches.c src/transfer.c src/sequencer.c \
	src/libminiaudio-audio.c src/instrument.c src/amy_midi.c src/api.c src/midi_mappings.c \
        src/cv_trigger.c

OBJECTS = $(patsubst %.c, %.o, $(SOURCES)) 

HEADERS = $(wildcard src/*.h)
HEADERS_BUILD := $(filter-out src/patches.h,$(HEADERS))

PYTHONS = $(wildcard *.py)

src/patches.h: $(PYTHONS) $(HEADERS_BUILD)
	cat src/amy.h  | sed -e 's@^//.*@@' | tr '\t' ' ' | egrep 'define +[^ ]+ +[.0-9-]+' | sed -e 's/\([-0-9][0-9]*\.[0-9]*\)f.*/\1/' | awk '{print $$2 "=" $$3}' > amy/constants.py
	${PYTHON} -m amy.headers

%.o: %.c $(HEADERS) src/patches.h
	$(CC) $(CFLAGS) -c $< -o $@

%.o: %.mm $(HEADERS)
	clang $(CFLAGS) -c $< -o $@

%.o: %.m
	clang  -I$(INC) $(CFLAGS) -c -o $@ $<

.PRECIOUS: $(TARGET) $(OBJECTS)

amy-example: $(OBJECTS) src/amy-example.o
	$(CC) $(CFLAGS) $(OBJECTS) src/amy-example.o -Wall $(LIBS) -o $@


amy-piano: $(OBJECTS) src/amy-piano.o
	$(CC) $(CFLAGS) $(OBJECTS) src/amy-piano.o -Wall $(LIBS) -o $@

amy-message: $(OBJECTS) src/amy-message.o
	$(CC) $(CFLAGS) $(OBJECTS) src/amy-message.o -Wall $(LIBS) -o $@

# Plain C tests for things the audio-rendering suite can't reach -- e.g. clock
# rollovers 50 days out, which you can only hit by fast-forwarding the counters.
CTESTS = tests/test_clock_wrap tests/test_sequencer_active tests/test_sequencer_bounds \
         tests/test_bus_config tests/test_patch_slots \
         tests/test_synth_readout tests/test_log2_lut tests/test_clone_on_grow \
         tests/test_timebase_reset tests/test_osc_free_on_release \
         tests/test_voice_osc_range

# Static pattern rules, so these win over the generic %.o: %.c above (which
# would compile without -Isrc and fail to find amy.h).
$(addsuffix .o,$(CTESTS)): %.o: %.c $(HEADERS) src/patches.h
	$(CC) $(CFLAGS) -Isrc -c $< -o $@

$(CTESTS): %: %.o $(OBJECTS)
	$(CC) $(CFLAGS) $(OBJECTS) $< -Wall $(LIBS) -o $@

ctest: $(CTESTS)
	@for t in $(CTESTS); do echo "== $$t"; ./$$t || exit 1; done

amy-module: amy-example
	${EXTRA_PIP_ENV} ${PYTHON} -m pip install -r requirements.txt; touch src/amy.c; ${EXTRA_PIP_ENV} ${PYTHON} -m pip install . --force-reinstall --no-deps; cd ..

test: amy-module
	${PYTHON} -m amy.test

qtest: amy-module
	${PYTHON} -m amy.test quiet

playfailed: qtest
	for x in `cat failed_tests.txt`; do echo $$x "ref"; sleep 0.2; afplay tests/ref/$$x.wav; echo $$x "tst"; sleep 0.2; afplay tests/tst/$$x.wav; done

updateref: qtest
	for x in `cat failed_tests.txt`; do cp tests/tst/$$x.wav tests/ref/; done

# Report the median FILTER_PROCESS timing over 50 runs.
timing: amy-module
	for a in 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40; do ${PYTHON} -m amy.timing 2>&1 ; done > /tmp/timings.txt
	cat /tmp/timings.txt | grep AMY_RENDER: | sed -e 's/us//' | sort -n | awk ' { a[i++]=$$4; } END { print a[int(i/2)]; }'
	cat /tmp/timings.txt | grep FILTER_PROCESS: | sed -e 's/us//' | sort -n | awk ' { a[i++]=$$4; } END { print a[int(i/2)]; }'
	cat /tmp/timings.txt | grep PARAMETRIC_EQ_PROCESS: | sed -e 's/us//' | sort -n | awk ' { a[i++]=$$4; } END { print a[int(i/2)]; }'

#USBSERIAL=/dev/cu.usbserial-0001
USBSERIAL=/dev/cu.usbserial-A5069RR4

speedtest:
	echo "Compiling LoadTestChord.ino..."
	arduino-cli compile --fqbn esp32:esp32:amyboard --build-path ./build --build-property "compiler.c.extra_flags=-DARDUINO_SPEEDTEST" tools/arduino_loadsweep/LoadTestChord
	echo 'Running measure.py.  Press RESET on board after seeing "[flash] ok (attempt 1)"'
	${PYTHON} tools/arduino_loadsweep/measure.py --out ./load --port ${USBSERIAL} ./build

speedtest-piano:
	echo "Compiling LoadTestChord.ino..."
	arduino-cli compile --fqbn esp32:esp32:amyboard --build-path ./build --build-property "compiler.c.extra_flags=-DARDUINO_SPEEDTEST" --build-property "compiler.cpp.extra_flags=-DSPEEDTEST_PATCH=256 -DSPEEDTEST_NUM_NOTES=6" tools/arduino_loadsweep/LoadTestChord
	echo 'Running measure.py.  Press RESET on board after seeing "[flash] ok (attempt 1)"'
	${PYTHON} tools/arduino_loadsweep/measure.py --out ./load --port ${USBSERIAL} ./build

valgrind: amy-example
	valgrind --leak-check=full --show-reachable=yes --suppressions=valgrind.suppressions ./amy-example

# The web build carries the Gamma9001 drum banks: drums.bin is generated from
# sounds/gamma9001/ and linked straight into the wasm as gamma9001_pcm_data.
build/drums_bin.c: sounds/gamma9001/manifest.json amy/headers.py
	$(PYTHON) -m amy.headers gamma9001

build/amy.js: $(TARGET) build/drums_bin.c
	mkdir -p build
	@# The linux MIDI layer is native ALSA and has no business in a wasm
	@# build -- and SOURCES/CFLAGS pick it up on any linux box that has
	@# libasound, so building web THERE would hand emcc alsa/asoundlib.h.
	emcc $(filter-out src/linux_midi.c,$(SOURCES)) build/drums_bin.c $(filter-out -DAMY_ALSA,$(CFLAGS)) -DGAMMA9001 $(EMSCRIPTEN_OPTIONS) -O3 -o $@

# The JS API / patches / pcm-preset files are committed outputs in src/,
# regenerated by `make c-api` and verified fresh in CI by `make check-c-api`
# (they used to be built into build/ here, which let downstream copies drift).
web: build/amy.js

# emscripten (>=4.x) inlines the AudioWorklet/WasmWorker glue into amy.js, so it
# no longer emits separate amy.aw.js / amy.ww.js files (older builds did).
deploy-web: web
	cat build/amy.js src/amy_connector.js src/amy_api.generated.js src/amy_c_api.generated.js > docs/amy.js
	cp build/amy.wasm docs/

# Regenerate the Godot GDScript send() kwarg map from amy/__init__.py (the
# source of truth). Run on release and verified in CI so godot/amy.gd can't drift.
godot-api:
	$(PYTHON) scripts/gen_amy_gd_api.py

js-api:
	$(PYTHON) scripts/gen_amy_js_api.py

clean:
	-rm -f src/*.o
	-rm -r src/patches.h
	-rm -f amy/constants.py
	-rm -f $(TARGET)
	-rm -f tests/*.o $(CTESTS)
