@@ -2,6 +2,11 @@ | |||
In this document, Ctrl means Command (⌘) on Mac. | |||
### 2.6.3 (2025-03-26) | |||
- Fix failure to launch on Windows 7. | |||
- Rack Pro | |||
- Audio Unit: Fix crash when controlling multiple parameters with *VCV MIDI Map*. | |||
### 2.6.2 (2025-03-17) | |||
- Fix incorrect audio driver being loaded from patches made in Rack ≤2.6.0. | |||
- Fix Mac installer creating duplicate app bundle. | |||
@@ -7,12 +7,12 @@ | |||
<key>CFBundleName</key><string>VCV Rack 2 Free</string> | |||
<key>CFBundleDisplayName</key><string>VCV Rack 2 Free</string> | |||
<key>CFBundleIdentifier</key><string>com.vcvrack.rack2</string> | |||
<key>CFBundleVersion</key><string>{VERSION}</string> | |||
<key>CFBundleVersion</key><string>{RACK_VERSION}</string> | |||
<key>CFBundlePackageType</key><string>APPL</string> | |||
<key>CFBundleSignature</key><string>RACK</string> | |||
<key>CFBundleExecutable</key><string>Rack</string> | |||
<key>CFBundleIconFile</key><string>icon</string> | |||
<key>CFBundleShortVersionString</key><string>{VERSION}</string> | |||
<key>CFBundleShortVersionString</key><string>{RACK_VERSION}</string> | |||
<key>NSHumanReadableCopyright</key><string>Copyright © VCV</string> | |||
<key>NSPrincipalClass</key><string>NSApplication</string> | |||
<key>NSHighResolutionCapable</key><true/> | |||
@@ -1,7 +1,7 @@ | |||
RACK_DIR ?= . | |||
EDITION := Free | |||
VERSION_MAJOR := 2 | |||
VERSION ?= $(patsubst v%,%,$(shell git describe --tags --match "v$(VERSION_MAJOR).*")) | |||
RACK_EDITION := Free | |||
RACK_VERSION_MAJOR := 2 | |||
RACK_VERSION ?= $(patsubst v%,%,$(shell git describe --tags --match "v$(RACK_VERSION_MAJOR).*")) | |||
FLAGS += -Iinclude -Idep/include | |||
@@ -17,7 +17,7 @@ SOURCES += dep/tinyexpr/tinyexpr.c | |||
SOURCES += $(wildcard src/*.c src/*/*.c) | |||
SOURCES += $(wildcard src/*.cpp src/*/*.cpp) | |||
build/src/common.cpp.o: FLAGS += -D_APP_VERSION=$(VERSION) | |||
build/src/common.cpp.o: FLAGS += -D_RACK_VERSION=$(RACK_VERSION) | |||
build/dep/tinyexpr/tinyexpr.c.o: FLAGS += -DTE_POW_FROM_RIGHT -DTE_NAT_LOG | |||
FLAGS += -fPIC | |||
@@ -164,17 +164,17 @@ endif | |||
# The following targets are not supported for public use | |||
DIST_NAME = Rack$(EDITION)-$(VERSION)-$(ARCH_NAME) | |||
DIST_NAME = Rack$(RACK_EDITION)-$(RACK_VERSION)-$(ARCH_NAME) | |||
ifdef ARCH_MAC | |||
DIST_BUNDLE := VCV Rack $(VERSION_MAJOR) $(EDITION).app | |||
DIST_BUNDLE := VCV Rack $(RACK_VERSION_MAJOR) $(RACK_EDITION).app | |||
else | |||
DIST_DIR := Rack$(VERSION_MAJOR)$(EDITION) | |||
DIST_DIR := Rack$(RACK_VERSION_MAJOR)$(RACK_EDITION) | |||
endif | |||
DIST_MD := $(wildcard *.md) | |||
DIST_HTML := $(patsubst %.md, build/%.html, $(DIST_MD)) | |||
DIST_RES := res cacert.pem Core.json template.vcv LICENSE-GPLv3.txt $(DIST_HTML) translations | |||
DIST_SDK_DIR := Rack-SDK | |||
DIST_SDK = Rack-SDK-$(VERSION)-$(ARCH_NAME).zip | |||
DIST_SDK = Rack-SDK-$(RACK_VERSION)-$(ARCH_NAME).zip | |||
FUNDAMENTAL_VERSION ?= 2.6.1 | |||
FUNDAMENTAL_FILENAME := Fundamental-$(FUNDAMENTAL_VERSION)-$(ARCH_NAME).vcvplugin | |||
@@ -211,7 +211,7 @@ ifdef ARCH_MAC | |||
otool -L dist/"$(DIST_BUNDLE)"/Contents/MacOS/$(STANDALONE_TARGET) | |||
# Copy resources | |||
cp Info.plist dist/"$(DIST_BUNDLE)"/Contents/ | |||
$(SED) 's/{VERSION}/$(VERSION)/g' dist/"$(DIST_BUNDLE)"/Contents/Info.plist | |||
$(SED) 's/{RACK_VERSION}/$(RACK_VERSION)/g' dist/"$(DIST_BUNDLE)"/Contents/Info.plist | |||
cp -R icon.icns dist/"$(DIST_BUNDLE)"/Contents/Resources/ | |||
cp -R $(DIST_RES) dist/"$(DIST_BUNDLE)"/Contents/Resources/ | |||
cp "$(FUNDAMENTAL_FILENAME)" dist/"$(DIST_BUNDLE)"/Contents/Resources/ | |||
@@ -273,7 +273,7 @@ endif | |||
ifdef ARCH_WIN | |||
# Make NSIS installer | |||
# pacman -S mingw-w64-x86_64-nsis | |||
makensis -DVERSION_MAJOR=$(VERSION_MAJOR) -DVERSION=$(VERSION) "-XOutFile dist/$(DIST_NAME).exe" installer.nsi | |||
makensis -DRACK_VERSION_MAJOR=$(RACK_VERSION_MAJOR) -DRACK_VERSION=$(RACK_VERSION) "-XOutFile dist/$(DIST_NAME).exe" installer.nsi | |||
endif | |||
@@ -104,16 +104,16 @@ $(glfw): | glfw | |||
$(MAKE) -C glfw/build | |||
$(MAKE) -C glfw/build install | |||
jansson-2.14: | |||
$(WGET) "https://github.com/akheron/jansson/releases/download/v2.14/jansson-2.14.tar.gz" || $(WGET) "http://www.digip.org/jansson/releases/jansson-2.14.tar.gz" | |||
$(SHA256) jansson-2.14.tar.gz 5798d010e41cf8d76b66236cfb2f2543c8d082181d16bc3085ab49538d4b9929 | |||
$(UNTAR) jansson-2.14.tar.gz | |||
rm jansson-2.14.tar.gz | |||
jansson-2.12: | |||
$(WGET) "https://github.com/akheron/jansson/releases/download/v2.12/jansson-2.12.tar.gz" || $(WGET) "http://www.digip.org/jansson/releases/jansson-2.12.tar.gz" | |||
$(SHA256) jansson-2.12.tar.gz 5f8dec765048efac5d919aded51b26a32a05397ea207aa769ff6b53c7027d2c9 | |||
$(UNTAR) jansson-2.12.tar.gz | |||
rm jansson-2.12.tar.gz | |||
$(jansson): | jansson-2.14 | |||
cd jansson-2.14 && $(CONFIGURE) | |||
$(MAKE) -C jansson-2.14 | |||
$(MAKE) -C jansson-2.14 install | |||
$(jansson): | jansson-2.12 | |||
cd jansson-2.12 && $(CONFIGURE) | |||
$(MAKE) -C jansson-2.12 | |||
$(MAKE) -C jansson-2.12 install | |||
openssl-3.3.2: | |||
$(WGET) "https://github.com/openssl/openssl/releases/download/openssl-3.3.2/openssl-3.3.2.tar.gz" | |||
@@ -144,32 +144,32 @@ $(libcurl): | $(openssl) curl-8.10.0 | |||
$(MAKE) -C curl-8.10.0 | |||
$(MAKE) -C curl-8.10.0 install | |||
zstd-1.5.7: | |||
$(WGET) "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz" | |||
$(SHA256) zstd-1.5.7.tar.gz eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 | |||
zstd-1.5.6: | |||
$(WGET) "https://github.com/facebook/zstd/releases/download/v1.5.6/zstd-1.5.6.tar.gz" | |||
$(SHA256) zstd-1.5.6.tar.gz 8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 | |||
# HACK This tar archive creates a symlink "untar" to "tar" before tar exists. This is okay on UNIX but not on Windows where symlinks are copies of files. So create a fake "tar" so the unarchiving works. | |||
mkdir -p zstd-1.5.7/tests/cli-tests/bin | |||
touch zstd-1.5.7/tests/cli-tests/bin/zstd | |||
$(UNTAR) zstd-1.5.7.tar.gz | |||
rm zstd-1.5.7.tar.gz | |||
$(zstd): | zstd-1.5.7 | |||
cd zstd-1.5.7/build/cmake && $(CMAKE) -DZSTD_BUILD_PROGRAMS=OFF -DZSTD_BUILD_SHARED=OFF -DZSTD_BUILD_STATIC=ON -DZSTD_BUILD_TESTS=OFF -DZSTD_MULTITHREAD_SUPPORT=OFF . | |||
$(MAKE) -C zstd-1.5.7/build/cmake | |||
$(MAKE) -C zstd-1.5.7/build/cmake install | |||
libarchive-3.7.8: | |||
$(WGET) "https://github.com/libarchive/libarchive/releases/download/v3.7.8/libarchive-3.7.8.tar.gz" || $(WGET) "https://libarchive.org/downloads/libarchive-3.7.8.tar.gz" | |||
$(SHA256) libarchive-3.7.8.tar.gz a123d87b1bd8adb19e8c187da17ae2d957c7f9596e741b929e6b9ceefea5ad0f | |||
$(UNTAR) libarchive-3.7.8.tar.gz | |||
rm libarchive-3.7.8.tar.gz | |||
$(libarchive): | $(zstd) libarchive-3.7.8 | |||
mkdir -p zstd-1.5.6/tests/cli-tests/bin | |||
touch zstd-1.5.6/tests/cli-tests/bin/zstd | |||
$(UNTAR) zstd-1.5.6.tar.gz | |||
rm zstd-1.5.6.tar.gz | |||
$(zstd): | zstd-1.5.6 | |||
cd zstd-1.5.6/build/cmake && $(CMAKE) -DZSTD_BUILD_PROGRAMS=OFF -DZSTD_BUILD_SHARED=OFF -DZSTD_BUILD_STATIC=ON -DZSTD_BUILD_TESTS=OFF -DZSTD_MULTITHREAD_SUPPORT=OFF . | |||
$(MAKE) -C zstd-1.5.6/build/cmake | |||
$(MAKE) -C zstd-1.5.6/build/cmake install | |||
libarchive-3.7.7: | |||
$(WGET) "https://github.com/libarchive/libarchive/releases/download/v3.7.7/libarchive-3.7.7.tar.gz" || $(WGET) "https://libarchive.org/downloads/libarchive-3.7.7.tar.gz" | |||
$(SHA256) libarchive-3.7.7.tar.gz 4cc540a3e9a1eebdefa1045d2e4184831100667e6d7d5b315bb1cbc951f8ddff | |||
$(UNTAR) libarchive-3.7.7.tar.gz | |||
rm libarchive-3.7.7.tar.gz | |||
$(libarchive): | $(zstd) libarchive-3.7.7 | |||
#cd libarchive-3.7.7 && $(CONFIGURE) --enable-shared=no --enable-static=yes --disable-bsdtar --disable-bsdcat --disable-bsdcpio --disable-posix-regex-lib --disable-xattr --disable-acl --without-zlib --without-bz2lib --without-libb2 --without-iconv --without-lz4 --without-lzma --without-cng --without-openssl --without-xml2 --without-expat | |||
cd libarchive-3.7.8 && mkdir -p build | |||
cd libarchive-3.7.8/build && $(CMAKE) -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF -DENABLE_ACL=OFF -DENABLE_BZip2=OFF -DENABLE_CAT=OFF -DENABLE_CNG=OFF -DENABLE_COVERAGE=OFF -DENABLE_CPIO=OFF -DENABLE_EXPAT=OFF -DENABLE_ICONV=OFF -DENABLE_LIBB2=OFF -DENABLE_LIBXML2=OFF -DENABLE_LZ4=OFF -DENABLE_LZMA=OFF -DENABLE_LZO=OFF -DENABLE_MBEDTLS=OFF -DENABLE_NETTLE=OFF -DENABLE_OPENSSL=OFF -DENABLE_TAR=OFF -DENABLE_TEST=OFF -DENABLE_UNZIP=OFF -DENABLE_WERROR=OFF -DENABLE_XATTR=OFF -DENABLE_ZLIB=OFF -DENABLE_ZSTD=ON -DZSTD_INCLUDE_DIR:PATH=$(DEP_PATH)/include -DZSTD_LIBRARY=$(DEP_PATH)/lib/libzstd.a .. | |||
$(MAKE) -C libarchive-3.7.8/build | |||
$(MAKE) -C libarchive-3.7.8/build install | |||
cd libarchive-3.7.7 && mkdir -p build | |||
cd libarchive-3.7.7/build && $(CMAKE) -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF -DENABLE_ACL=OFF -DENABLE_BZip2=OFF -DENABLE_CAT=OFF -DENABLE_CNG=OFF -DENABLE_COVERAGE=OFF -DENABLE_CPIO=OFF -DENABLE_EXPAT=OFF -DENABLE_ICONV=OFF -DENABLE_LIBB2=OFF -DENABLE_LIBXML2=OFF -DENABLE_LZ4=OFF -DENABLE_LZMA=OFF -DENABLE_LZO=OFF -DENABLE_MBEDTLS=OFF -DENABLE_NETTLE=OFF -DENABLE_OPENSSL=OFF -DENABLE_TAR=OFF -DENABLE_TEST=OFF -DENABLE_UNZIP=OFF -DENABLE_WERROR=OFF -DENABLE_XATTR=OFF -DENABLE_ZLIB=OFF -DENABLE_ZSTD=ON -DWINDOWS_VERSION="WIN7" -DZSTD_INCLUDE_DIR:PATH=$(DEP_PATH)/include -DZSTD_LIBRARY=$(DEP_PATH)/lib/libzstd.a .. | |||
$(MAKE) -C libarchive-3.7.7/build | |||
$(MAKE) -C libarchive-3.7.7/build install | |||
$(libspeexdsp): | speexdsp | |||
cd speexdsp && ./autogen.sh | |||
@@ -177,18 +177,16 @@ $(libspeexdsp): | speexdsp | |||
$(MAKE) -C speexdsp | |||
$(MAKE) -C speexdsp install | |||
libsamplerate-0.2.2: | |||
$(WGET) "https://github.com/libsndfile/libsamplerate/releases/download/0.2.2/libsamplerate-0.2.2.tar.xz" | |||
$(SHA256) libsamplerate-0.2.2.tar.xz 3258da280511d24b49d6b08615bbe824d0cacc9842b0e4caf11c52cf2b043893 | |||
$(UNTAR) libsamplerate-0.2.2.tar.xz | |||
rm libsamplerate-0.2.2.tar.xz | |||
libsamplerate-0.1.9: | |||
$(WGET) "https://github.com/libsndfile/libsamplerate/releases/download/0.1.9/libsamplerate-0.1.9.tar.gz" || $(WGET) "http://www.mega-nerd.com/SRC/libsamplerate-0.1.9.tar.gz" | |||
$(SHA256) libsamplerate-0.1.9.tar.gz 0a7eb168e2f21353fb6d84da152e4512126f7dc48ccb0be80578c565413444c1 | |||
$(UNTAR) libsamplerate-0.1.9.tar.gz | |||
rm libsamplerate-0.1.9.tar.gz | |||
$(libsamplerate): | libsamplerate-0.2.2 | |||
cd libsamplerate-0.2.2 && mkdir build | |||
cd libsamplerate-0.2.2/build && $(CMAKE) -DCMAKE_BUILD_TYPE=Release .. | |||
cd libsamplerate-0.2.2/build && $(MAKE) install | |||
cd libsamplerate-0.2.2/build && $(CMAKE) -DBUILD_SHARED_LIBS=ON .. | |||
cd libsamplerate-0.2.2/build && $(MAKE) install | |||
$(libsamplerate): | libsamplerate-0.1.9 | |||
cd libsamplerate-0.1.9 && $(CONFIGURE) --disable-fftw --disable-sndfile | |||
$(MAKE) -C libsamplerate-0.1.9 | |||
$(MAKE) -C libsamplerate-0.1.9 install | |||
RTMIDI_FLAGS += --enable-shared=no | |||
ifdef ARCH_LIN | |||
@@ -215,10 +213,14 @@ ifdef ARCH_MAC | |||
RTAUDIO_FLAGS += -DRTAUDIO_API_CORE=ON -DRTAUDIO_API_PULSE=OFF -DRTAUDIO_API_JACK=OFF | |||
endif | |||
ifdef ARCH_WIN | |||
RTAUDIO_FLAGS += -DRTAUDIO_API_DS=OFF -DRTAUDIO_API_WASAPI=ON | |||
RTAUDIO_FLAGS += -DRTAUDIO_API_DS=ON -DRTAUDIO_API_WASAPI=ON | |||
# ASIO is proprietary software owned by Steinberg, so distributing it with your own VCV Rack build would violate Rack's GPLv3 license. | |||
# However, since VCV owns the copyright of all GPL'd code in the Rack package, we include ASIO in the Windows build for convenience to our users. | |||
ifdef RTAUDIO_ASIO | |||
RTAUDIO_FLAGS += -DRTAUDIO_API_ASIO=ON | |||
else | |||
RTAUDIO_FLAGS += -DRTAUDIO_API_ASIO=OFF | |||
endif | |||
endif | |||
# RtAudio on Windows and Mac uses these macros to enable UTF-8 device names | |||
@@ -267,7 +269,7 @@ $(simde): simde/simde | |||
# Helpers | |||
src: glew-2.1.0 glfw jansson-2.14 libsamplerate-0.2.2 openssl-3.3.2 curl-8.10.0 zstd-1.5.7 libarchive-3.7.8 rtaudio nanovg nanosvg oui-blendish osdialog | |||
src: glew-2.1.0 glfw jansson-2.12 libsamplerate-0.1.9 openssl-3.3.2 curl-8.10.0 zstd-1.5.6 libarchive-3.7.7 rtaudio nanovg nanosvg oui-blendish osdialog | |||
clean: | |||
git clean -fdx | |||
@@ -1,10 +1,10 @@ | |||
!include "MUI2.nsh" | |||
!define NAME_FULL "VCV Rack Free ${VERSION}" | |||
!define NAME "VCV Rack ${VERSION_MAJOR} Free" | |||
!define RACK_DIR "Rack${VERSION_MAJOR}Free" | |||
!define INSTALL_REG "Software\VCV\Rack${VERSION_MAJOR}Free" | |||
!define UNINSTALL_REG "Software\Microsoft\Windows\CurrentVersion\Uninstall\VCVRack${VERSION_MAJOR}Free" | |||
!define NAME_FULL "VCV Rack Free ${RACK_VERSION}" | |||
!define NAME "VCV Rack ${RACK_VERSION_MAJOR} Free" | |||
!define RACK_DIR "Rack${RACK_VERSION_MAJOR}Free" | |||
!define INSTALL_REG "Software\VCV\Rack${RACK_VERSION_MAJOR}Free" | |||
!define UNINSTALL_REG "Software\Microsoft\Windows\CurrentVersion\Uninstall\VCVRack${RACK_VERSION_MAJOR}Free" | |||
Name "${NAME_FULL}" | |||
Unicode True | |||
@@ -94,7 +94,7 @@ Section "${NAME}" INSTALL_SECTION | |||
; Write uninstaller info | |||
WriteRegStr HKLM "${UNINSTALL_REG}" "DisplayName" "${NAME}" | |||
WriteRegStr HKLM "${UNINSTALL_REG}" "DisplayIcon" '"$INSTDIR\Rack.exe"' | |||
WriteRegStr HKLM "${UNINSTALL_REG}" "DisplayVersion" "${VERSION}" | |||
WriteRegStr HKLM "${UNINSTALL_REG}" "DisplayVersion" "${RACK_VERSION}" | |||
WriteRegStr HKLM "${UNINSTALL_REG}" "UninstallString" '"$INSTDIR\Uninstall.exe"' | |||
WriteRegStr HKLM "${UNINSTALL_REG}" "QuietUninstallString" '"$INSTDIR\Uninstall.exe" /S' | |||
WriteRegStr HKLM "${UNINSTALL_REG}" "InstallLocation" '"$INSTDIR"' | |||
@@ -115,10 +115,6 @@ Section "${NAME}" INSTALL_SECTION | |||
; Create shortcuts | |||
CreateShortcut "$DESKTOP\${NAME}.lnk" "$INSTDIR\Rack.exe" | |||
CreateShortcut "$SMPROGRAMS\${NAME}.lnk" "$INSTDIR\Rack.exe" | |||
; Add allowed app to Controlled Folder Access | |||
ExpandEnvStrings $0 "%COMSPEC%" | |||
ExecShellWait "" '"$0"' "/C powershell -ExecutionPolicy Bypass -WindowStyle Hidden $\"Add-MpPreference -ControlledFolderAccessAllowedApplications '$INSTDIR\Rack.exe'$\"" SW_HIDE | |||
SectionEnd | |||
@@ -422,10 +422,19 @@ struct ViewButton : MenuButton { | |||
} | |||
})); | |||
menu->addChild(createIndexPtrSubmenuItem(string::translate("MenuBar.view.pixelRatio"), { | |||
string::translate("MenuBar.view.pixelRatio.auto"), | |||
"100%", "200%", "300%" | |||
}, &settings::pixelRatio)); | |||
static const std::vector<float> pixelRatios = {0, 1, 1.5, 2, 2.5, 3}; | |||
std::vector<std::string> pixelRatioLabels; | |||
for (float pixelRatio : pixelRatios) { | |||
pixelRatioLabels.push_back(pixelRatio == 0.f ? string::translate("MenuBar.view.pixelRatio.auto") : string::f("%0.f%%", pixelRatio * 100.f)); | |||
} | |||
menu->addChild(createIndexSubmenuItem(string::translate("MenuBar.view.pixelRatio"), pixelRatioLabels, [=]() -> size_t { | |||
auto it = std::find(pixelRatios.begin(), pixelRatios.end(), settings::pixelRatio); | |||
if (it == pixelRatios.end()) | |||
return -1; | |||
return it - pixelRatios.begin(); | |||
}, [=](size_t i) { | |||
settings::pixelRatio = pixelRatios[i]; | |||
})); | |||
ZoomSlider* zoomSlider = new ZoomSlider; | |||
zoomSlider->box.size.x = 250.0; | |||
@@ -19,7 +19,7 @@ const std::string APP_NAME = "VCV Rack"; | |||
const std::string APP_EDITION = "Pro"; | |||
const std::string APP_EDITION_NAME = "[SR's Build]"; | |||
const std::string APP_VERSION_MAJOR = "2"; | |||
const std::string APP_VERSION = TOSTRING(_APP_VERSION); | |||
const std::string APP_VERSION = TOSTRING(_RACK_VERSION); | |||
#if defined ARCH_WIN | |||
const std::string APP_OS = "win"; | |||
const std::string APP_OS_NAME = "Windows"; | |||
@@ -115,16 +115,23 @@ struct MIDIMap : Module { | |||
if (values[cc] < 0) | |||
continue; | |||
float value = values[cc] / 127.f; | |||
// Detect behavior from MIDI buttons. | |||
// Detect MIDI CC buttons | |||
if (smooth && std::fabs(valueFilters[id].out - value) < 1.f) { | |||
// Smooth value with filter | |||
valueFilters[id].process(args.sampleTime * divider.getDivision(), value); | |||
value = valueFilters[id].process(args.sampleTime * divider.getDivision(), value); | |||
} | |||
else { | |||
// Jump value | |||
// Jump filter value, don't filter | |||
valueFilters[id].out = value; | |||
} | |||
paramQuantity->setScaledValue(valueFilters[id].out); | |||
// Scale and snap value based on ParamQuantity | |||
value = paramQuantity->fromScaled(value); | |||
if (paramQuantity->snapEnabled) | |||
value = std::round(value); | |||
// Set param value without Engine smoothing, since it is already filtered. | |||
APP->engine->setParamValue(module, paramId, value); | |||
} | |||
} | |||