From 1c09694f1b79df812d8eff968a5b906fee84d675 Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Sun, 4 Dec 2022 02:58:52 -0800 Subject: [PATCH] Decoders: Implemented Organya decoder Based on the C++11 code by Joel Yliluoma / bisqwit, which in turn is based on information from NX-Engine. I have also taken the liberty of bundling the required wavetable bank and PixTone drums. Contrary to the documentation provided with the code, my version of dou_1006.zip, as downloaded over a decade ago, had the wavetable bank at offset 635,816 bytes into the file, not 1,115,748 bytes. Possibly a difference of having applied the translation patch? My copy is the original version, so I had to use a real resource parser to locate the waveforms. The player will obey the configured sample rate, loop count, and fade time for synthesizers, and also obey Repeat One to play indefinitely. The code should be quite robust to minor abuses, though I can't imagine how well it would hold up to random bad files, other than playing outright garbage. Signed-off-by: Christopher Snowhill --- Cog.xcodeproj/project.pbxproj | 43 ++ Info.plist.template | 18 + .../Organya/Organya.xcodeproj/project.pbxproj | 368 +++++++++++++ Plugins/Organya/OrganyaDecoder.h | 37 ++ Plugins/Organya/OrganyaDecoder.mm | 511 ++++++++++++++++++ Plugins/Organya/fx96.pxt | 92 ++++ Plugins/Organya/fx97.pxt | 92 ++++ Plugins/Organya/fx98.pxt | 92 ++++ Plugins/Organya/fx99.pxt | 92 ++++ Plugins/Organya/fx9a.pxt | 92 ++++ Plugins/Organya/fx9b.pxt | 92 ++++ Plugins/Organya/wavetable.dat | Bin 0 -> 25600 bytes 12 files changed, 1529 insertions(+) create mode 100644 Plugins/Organya/Organya.xcodeproj/project.pbxproj create mode 100644 Plugins/Organya/OrganyaDecoder.h create mode 100644 Plugins/Organya/OrganyaDecoder.mm create mode 100755 Plugins/Organya/fx96.pxt create mode 100755 Plugins/Organya/fx97.pxt create mode 100755 Plugins/Organya/fx98.pxt create mode 100755 Plugins/Organya/fx99.pxt create mode 100755 Plugins/Organya/fx9a.pxt create mode 100755 Plugins/Organya/fx9b.pxt create mode 100755 Plugins/Organya/wavetable.dat diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 093cd5429..e7dff3f5c 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -105,6 +105,7 @@ 83229C9F283B0095004626A8 /* SpectrumWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83229C9D283B0095004626A8 /* SpectrumWindowController.m */; }; 83256B68286661FC0036D9C0 /* libmpg123.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 83256B672866617F0036D9C0 /* libmpg123.0.dylib */; }; 83256B69286661FC0036D9C0 /* libmpg123.0.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83256B672866617F0036D9C0 /* libmpg123.0.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 8327DBA9293CAD2400CD0580 /* Organya.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8327DB94293C923500CD0580 /* Organya.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 832923AF279FAC400048201E /* Cog.q1.json in Resources */ = {isa = PBXBuildFile; fileRef = 832923AE279FAC400048201E /* Cog.q1.json */; }; 832CFC4F2851AA1A002AC26F /* NSView+Visibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 832CFC4E2851AA1A002AC26F /* NSView+Visibility.m */; }; 832CFC562851AA8B002AC26F /* SpectrumViewCG.m in Sources */ = {isa = PBXBuildFile; fileRef = 832CFC552851AA8B002AC26F /* SpectrumViewCG.m */; }; @@ -404,6 +405,20 @@ remoteGlobalIDString = 8314D6311A354DFE00EEE8E6; remoteInfo = sidplay; }; + 8327DB93293C923500CD0580 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8327DB8F293C923500CD0580 /* Organya.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8327DB7D293C90E300CD0580; + remoteInfo = Organya; + }; + 8327DBA7293CAD0A00CD0580 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8327DB8F293C923500CD0580 /* Organya.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 8327DB7C293C90E300CD0580; + remoteInfo = Organya; + }; 834068BC20E4E40200A01561 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 17C808C00C3BD1DD005707C4 /* WavPack.xcodeproj */; @@ -689,6 +704,7 @@ dstPath = ""; dstSubfolderSpec = 13; files = ( + 8327DBA9293CAD2400CD0580 /* Organya.bundle in CopyFiles */, 8372C93D27C7895300E250C9 /* MAD.bundle in CopyFiles */, 83489C6B2782F78700BDCEA2 /* libvgmPlayer.bundle in CopyFiles */, 834D794020E4EFEF00C4A5CC /* VorbisPlugin.bundle in CopyFiles */, @@ -927,6 +943,7 @@ 83229C9C283B0095004626A8 /* SpectrumWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SpectrumWindowController.h; sourceTree = ""; }; 83229C9D283B0095004626A8 /* SpectrumWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SpectrumWindowController.m; sourceTree = ""; }; 83256B672866617F0036D9C0 /* libmpg123.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpg123.0.dylib; path = ThirdParty/mpg123/lib/libmpg123.0.dylib; sourceTree = ""; }; + 8327DB8F293C923500CD0580 /* Organya.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Organya.xcodeproj; path = Plugins/Organya/Organya.xcodeproj; sourceTree = ""; }; 832923AE279FAC400048201E /* Cog.q1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Cog.q1.json; sourceTree = ""; }; 832CFC4E2851AA1A002AC26F /* NSView+Visibility.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSView+Visibility.m"; sourceTree = ""; }; 832CFC532851AA37002AC26F /* NSView+Visibility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSView+Visibility.h"; sourceTree = ""; }; @@ -1363,6 +1380,7 @@ 17C8089E0C3BD1AB005707C4 /* Musepack.xcodeproj */, 83E5EFAC1FFEF78100659F0F /* OpenMPT.xcodeproj */, 8375B05117FFEA400092A79F /* OpusPlugin.xcodeproj */, + 8327DB8F293C923500CD0580 /* Organya.xcodeproj */, 8E8D41C20CBB0DA000135C1B /* Pls.xcodeproj */, 17C808A70C3BD1BA005707C4 /* Shorten.xcodeproj */, 8314D63B1A354DFE00EEE8E6 /* sidplay.xcodeproj */, @@ -1737,6 +1755,14 @@ name = Products; sourceTree = ""; }; + 8327DB90293C923500CD0580 /* Products */ = { + isa = PBXGroup; + children = ( + 8327DB94293C923500CD0580 /* Organya.bundle */, + ); + name = Products; + sourceTree = ""; + }; 834068A720E4E40200A01561 /* Products */ = { isa = PBXGroup; children = ( @@ -2040,6 +2066,7 @@ buildRules = ( ); dependencies = ( + 8327DBA8293CAD0A00CD0580 /* PBXTargetDependency */, 8372C93C27C7893100E250C9 /* PBXTargetDependency */, 83489C6A2782F76900BDCEA2 /* PBXTargetDependency */, ED69CBC625BE32B40090B90D /* PBXTargetDependency */, @@ -2208,6 +2235,10 @@ ProductGroup = 830B62AF20E4EF89004A74B2 /* Products */; ProjectRef = 8375B05117FFEA400092A79F /* OpusPlugin.xcodeproj */; }, + { + ProductGroup = 8327DB90293C923500CD0580 /* Products */; + ProjectRef = 8327DB8F293C923500CD0580 /* Organya.xcodeproj */; + }, { ProductGroup = 8E8D41C30CBB0DA000135C1B /* Products */; ProjectRef = 8E8D41C20CBB0DA000135C1B /* Pls.xcodeproj */; @@ -2365,6 +2396,13 @@ remoteRef = 8314D6401A354DFF00EEE8E6 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 8327DB94293C923500CD0580 /* Organya.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = Organya.bundle; + remoteRef = 8327DB93293C923500CD0580 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 834068BD20E4E40200A01561 /* WavPack.bundle */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; @@ -2753,6 +2791,11 @@ name = Preferences; targetProxy = 17F5623A0C3BD9280019975C /* PBXContainerItemProxy */; }; + 8327DBA8293CAD0A00CD0580 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = Organya; + targetProxy = 8327DBA7293CAD0A00CD0580 /* PBXContainerItemProxy */; + }; 83489C6A2782F76900BDCEA2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "libvgmPlayer Plugin"; diff --git a/Info.plist.template b/Info.plist.template index f1d46330e..fdb760618 100644 --- a/Info.plist.template +++ b/Info.plist.template @@ -1884,6 +1884,24 @@ LSTypeIsPackage + + CFBundleTypeExtensions + + org + + CFBundleTypeIconFile + vg.icns + CFBundleTypeIconSystemGenerated + 1 + CFBundleTypeName + Organya File + CFBundleTypeRole + Viewer + LSHandlerRank + Default + LSTypeIsPackage + + CFBundleTypeExtensions diff --git a/Plugins/Organya/Organya.xcodeproj/project.pbxproj b/Plugins/Organya/Organya.xcodeproj/project.pbxproj new file mode 100644 index 000000000..8bd013f62 --- /dev/null +++ b/Plugins/Organya/Organya.xcodeproj/project.pbxproj @@ -0,0 +1,368 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 8327DB96293C926F00CD0580 /* OrganyaDecoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8327DB95293C926F00CD0580 /* OrganyaDecoder.mm */; }; + 8327DBA0293CACCC00CD0580 /* fx98.pxt in Resources */ = {isa = PBXBuildFile; fileRef = 8327DB99293CACCC00CD0580 /* fx98.pxt */; }; + 8327DBA1293CACCC00CD0580 /* fx9a.pxt in Resources */ = {isa = PBXBuildFile; fileRef = 8327DB9A293CACCC00CD0580 /* fx9a.pxt */; }; + 8327DBA2293CACCC00CD0580 /* fx9b.pxt in Resources */ = {isa = PBXBuildFile; fileRef = 8327DB9B293CACCC00CD0580 /* fx9b.pxt */; }; + 8327DBA3293CACCC00CD0580 /* fx96.pxt in Resources */ = {isa = PBXBuildFile; fileRef = 8327DB9C293CACCC00CD0580 /* fx96.pxt */; }; + 8327DBA4293CACCC00CD0580 /* fx99.pxt in Resources */ = {isa = PBXBuildFile; fileRef = 8327DB9D293CACCC00CD0580 /* fx99.pxt */; }; + 8327DBA5293CACCC00CD0580 /* wavetable.dat in Resources */ = {isa = PBXBuildFile; fileRef = 8327DB9E293CACCC00CD0580 /* wavetable.dat */; }; + 8327DBA6293CACCC00CD0580 /* fx97.pxt in Resources */ = {isa = PBXBuildFile; fileRef = 8327DB9F293CACCC00CD0580 /* fx97.pxt */; }; + 8327DBAD293CAE3F00CD0580 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8327DBAC293CAE3F00CD0580 /* Cocoa.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 8327DB7D293C90E300CD0580 /* Organya.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Organya.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; + 8327DB8B293C913100CD0580 /* AudioChunk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AudioChunk.h; path = ../../Audio/Chain/AudioChunk.h; sourceTree = ""; }; + 8327DB8C293C915500CD0580 /* Plugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Plugin.h; path = ../../Audio/Plugin.h; sourceTree = ""; }; + 8327DB8D293C916400CD0580 /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = ""; }; + 8327DB95293C926F00CD0580 /* OrganyaDecoder.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = OrganyaDecoder.mm; sourceTree = ""; }; + 8327DB97293C92A300CD0580 /* OrganyaDecoder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OrganyaDecoder.h; sourceTree = ""; }; + 8327DB99293CACCC00CD0580 /* fx98.pxt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fx98.pxt; sourceTree = ""; }; + 8327DB9A293CACCC00CD0580 /* fx9a.pxt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fx9a.pxt; sourceTree = ""; }; + 8327DB9B293CACCC00CD0580 /* fx9b.pxt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fx9b.pxt; sourceTree = ""; }; + 8327DB9C293CACCC00CD0580 /* fx96.pxt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fx96.pxt; sourceTree = ""; }; + 8327DB9D293CACCC00CD0580 /* fx99.pxt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fx99.pxt; sourceTree = ""; }; + 8327DB9E293CACCC00CD0580 /* wavetable.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = wavetable.dat; sourceTree = ""; }; + 8327DB9F293CACCC00CD0580 /* fx97.pxt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fx97.pxt; sourceTree = ""; }; + 8327DBAA293CADCE00CD0580 /* PlaylistController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PlaylistController.h; path = ../../Playlist/PlaylistController.h; sourceTree = ""; }; + 8327DBAC293CAE3F00CD0580 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8327DB7A293C90E300CD0580 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8327DBAD293CAE3F00CD0580 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8327DB74293C90E300CD0580 = { + isa = PBXGroup; + children = ( + 8327DB98293CA88700CD0580 /* Resources */, + 8327DBAB293CAE3100CD0580 /* Frameworks */, + 8327DB8E293C917000CD0580 /* Source */, + 8327DB8A293C90F700CD0580 /* Headers */, + 8327DB7E293C90E300CD0580 /* Products */, + ); + sourceTree = ""; + }; + 8327DB7E293C90E300CD0580 /* Products */ = { + isa = PBXGroup; + children = ( + 8327DB7D293C90E300CD0580 /* Organya.bundle */, + ); + name = Products; + sourceTree = ""; + }; + 8327DB8A293C90F700CD0580 /* Headers */ = { + isa = PBXGroup; + children = ( + 8327DB8B293C913100CD0580 /* AudioChunk.h */, + 8327DB8D293C916400CD0580 /* Logging.h */, + 8327DB97293C92A300CD0580 /* OrganyaDecoder.h */, + 8327DBAA293CADCE00CD0580 /* PlaylistController.h */, + 8327DB8C293C915500CD0580 /* Plugin.h */, + ); + name = Headers; + sourceTree = ""; + }; + 8327DB8E293C917000CD0580 /* Source */ = { + isa = PBXGroup; + children = ( + 8327DB95293C926F00CD0580 /* OrganyaDecoder.mm */, + ); + name = Source; + sourceTree = ""; + }; + 8327DB98293CA88700CD0580 /* Resources */ = { + isa = PBXGroup; + children = ( + 8327DB9A293CACCC00CD0580 /* fx9a.pxt */, + 8327DB9B293CACCC00CD0580 /* fx9b.pxt */, + 8327DB9C293CACCC00CD0580 /* fx96.pxt */, + 8327DB9F293CACCC00CD0580 /* fx97.pxt */, + 8327DB99293CACCC00CD0580 /* fx98.pxt */, + 8327DB9D293CACCC00CD0580 /* fx99.pxt */, + 8327DB9E293CACCC00CD0580 /* wavetable.dat */, + ); + name = Resources; + sourceTree = ""; + }; + 8327DBAB293CAE3100CD0580 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8327DBAC293CAE3F00CD0580 /* Cocoa.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8327DB7C293C90E300CD0580 /* Organya */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8327DB81293C90E300CD0580 /* Build configuration list for PBXNativeTarget "Organya" */; + buildPhases = ( + 8327DB79293C90E300CD0580 /* Sources */, + 8327DB7A293C90E300CD0580 /* Frameworks */, + 8327DB7B293C90E300CD0580 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Organya; + productName = Organya; + productReference = 8327DB7D293C90E300CD0580 /* Organya.bundle */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8327DB75293C90E300CD0580 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1410; + TargetAttributes = { + 8327DB7C293C90E300CD0580 = { + CreatedOnToolsVersion = 14.1; + }; + }; + }; + buildConfigurationList = 8327DB78293C90E300CD0580 /* Build configuration list for PBXProject "Organya" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 8327DB74293C90E300CD0580; + productRefGroup = 8327DB7E293C90E300CD0580 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8327DB7C293C90E300CD0580 /* Organya */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8327DB7B293C90E300CD0580 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8327DBA5293CACCC00CD0580 /* wavetable.dat in Resources */, + 8327DBA3293CACCC00CD0580 /* fx96.pxt in Resources */, + 8327DBA6293CACCC00CD0580 /* fx97.pxt in Resources */, + 8327DBA0293CACCC00CD0580 /* fx98.pxt in Resources */, + 8327DBA4293CACCC00CD0580 /* fx99.pxt in Resources */, + 8327DBA1293CACCC00CD0580 /* fx9a.pxt in Resources */, + 8327DBA2293CACCC00CD0580 /* fx9b.pxt in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8327DB79293C90E300CD0580 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8327DB96293C926F00CD0580 /* OrganyaDecoder.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 8327DB7F293C90E300CD0580 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 8327DB80293C90E300CD0580 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + 8327DB82293C90E300CD0580 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSPrincipalClass = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.losno.Organya; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + WRAPPER_EXTENSION = bundle; + }; + name = Debug; + }; + 8327DB83293C90E300CD0580 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INFOPLIST_KEY_NSPrincipalClass = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = co.losno.Organya; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + WRAPPER_EXTENSION = bundle; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 8327DB78293C90E300CD0580 /* Build configuration list for PBXProject "Organya" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8327DB7F293C90E300CD0580 /* Debug */, + 8327DB80293C90E300CD0580 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8327DB81293C90E300CD0580 /* Build configuration list for PBXNativeTarget "Organya" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8327DB82293C90E300CD0580 /* Debug */, + 8327DB83293C90E300CD0580 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 8327DB75293C90E300CD0580 /* Project object */; +} diff --git a/Plugins/Organya/OrganyaDecoder.h b/Plugins/Organya/OrganyaDecoder.h new file mode 100644 index 000000000..f28bd248b --- /dev/null +++ b/Plugins/Organya/OrganyaDecoder.h @@ -0,0 +1,37 @@ +// +// OrganyaDecoder.h +// Organya +// +// Created by Christopher Snowhill on 12/4/22. +// + +#ifndef OrganyaDecoder_h +#define OrganyaDecoder_h + +#import "Plugin.h" + +namespace Organya { +struct Song; +} + +@interface OrganyaDecoder : NSObject { + Organya::Song *m_song; + id source; + long length, lengthWithFade; + long samplesDiscard; + + double sampleRate; + + long renderedTotal; + long loopedTotal; + long loopsRemain; + long fadeTotal; + long fadeRemain; +} + +- (void)setSource:(id)s; +- (id)source; +- (void)cleanUp; +@end + +#endif /* OrganyaDecoder_h */ diff --git a/Plugins/Organya/OrganyaDecoder.mm b/Plugins/Organya/OrganyaDecoder.mm new file mode 100644 index 000000000..775fff698 --- /dev/null +++ b/Plugins/Organya/OrganyaDecoder.mm @@ -0,0 +1,511 @@ +// +// OrganyaDecoder.m +// Organya +// +// Created by Christopher Snowhill on 12/4/22. +// + +#import + +#import "OrganyaDecoder.h" + +#import "AudioChunk.h" +#import "PlaylistController.h" + +#import +#import +#import +#import +#import +#import + +/* SIMPLE CAVE STORY MUSIC PLAYER (Organya) */ +/* Written by Joel Yliluoma -- http://iki.fi/bisqwit/ */ +/* NX-Engine source code was used as reference. */ +/* Cave Story and its music were written by Pixel ( 天谷 大輔 ) */ + +namespace Organya { + + //========= PART 0 : INPUT/OUTPUT AND UTILITY ========// + using std::fgetc; + int fgetw(FILE* fp) { int a = fgetc(fp), b = fgetc(fp); return (b<<8) + a; } + int fgetd(FILE* fp) { int a = fgetw(fp), b = fgetw(fp); return (b<<16) + a; } + double fgetv(FILE* fp) // Load a numeric value from text file; one per line. + { + char Buf[4096], *p=Buf; Buf[4095]='\0'; + if(!std::fgets(Buf, sizeof(Buf)-1, fp)) return 0.0; + // Ignore empty lines. If the line was empty, try next line. + if(!Buf[0] || Buf[0]=='\r' || Buf[0]=='\n') return fgetv(fp); + while(*p && *p++ != ':') {} // Skip until a colon character. + return std::strtod(p, 0); // Parse the value and return it. + } + + int coggetc(id fp) { + uint8_t value; + if([fp read:&value amount:sizeof(value)] != sizeof(value)) { + return -1; + } + return value; + } + int coggetw(id fp) { int a = coggetc(fp); int b = coggetc(fp); return (b<<8) + a; } + int coggetd(id fp) { int a = coggetw(fp); int b = coggetw(fp); return (b<<16) + a; } + + //========= PART 1 : SOUND EFFECT PLAYER (PXT) ========// + + static signed char Waveforms[6][256]; + static void GenerateWaveforms(void) { + /* Six simple waveforms are used as basis for the signal generators in PXT: */ + for(unsigned seed=0, i=0; i<256; ++i) { + /* These waveforms are bit-exact with PixTone v1.0.3. */ + seed = (seed * 214013) + 2531011; // Linear congruential generator + Waveforms[0][i] = 0x40 * std::sin(i * 3.1416 / 0x80); // Sine + Waveforms[1][i] = ((0x40+i) & 0x80) ? 0x80-i : i; // Triangle + Waveforms[2][i] = -0x40 + i/2; // Sawtooth up + Waveforms[3][i] = 0x40 - i/2; // Sawtooth down + Waveforms[4][i] = 0x40 - (i & 0x80); // Square + Waveforms[5][i] = (signed char)(seed >> 16) / 2; // Pseudorandom + } + } + + struct Pxt { + struct Channel { + bool enabled; + int nsamples; + + // Waveform generator + struct Wave { + const signed char* wave; + double pitch; + int level, offset; + }; + Wave carrier; // The main signal to be generated. + Wave frequency; // Modulator to the main signal. + Wave amplitude; // Modulator to the main signal. + + // Envelope generator (controls the overall amplitude) + struct Env { + int initial; // initial value (0-63) + struct { int time, val; } p[3]; // time offset & value, three of them + int Evaluate(int i) const { // Linearly interpolate between the key points: + int prevval = initial, prevtime=0; + int nextval = 0, nexttime=256; + for(int j=2; j>=0; --j) if(i < p[j].time) { nexttime=p[j].time; nextval=p[j].val; } + for(int j=0; j<=2; ++j) if(i >=p[j].time) { prevtime=p[j].time; prevval=p[j].val; } + if(nexttime <= prevtime) return prevval; + return (i-prevtime) * (nextval-prevval) / (nexttime-prevtime) + prevval; + } + } envelope; + + // Synthesize the sound effect. + std::vector Synth() { + if(!enabled) return {}; + std::vector result(nsamples); + + auto& c = carrier, &f = frequency, &a = amplitude; + double mainpos = c.offset, maindelta = 256*c.pitch/nsamples; + for(size_t i=0; i DrumSamples[12]; + + void LoadWaveTable(void) { + NSURL *url = [[NSBundle bundleWithIdentifier:@"co.losno.Organya"] URLForResource:@"wavetable" withExtension:@"dat"]; + if(!url) return; + NSString *path = [url path]; + FILE* fp = std::fopen([path UTF8String], "rb"); + if(!fp) return; + for(size_t a=0; a<100*256; ++a) + WaveTable[a] = (signed char) fgetc(fp); + std::fclose(fp); + } + + void LoadDrums(void) { + GenerateWaveforms(); + /* List of PXT files containing these percussion instruments: */ + static const int patch[] = {0x96,0,0x97,0, 0x9a,0x98,0x99,0, 0x9b,0,0,0}; + for(unsigned drumno=0; drumno<12; ++drumno) + { + if(!patch[drumno]) continue; // Leave that non-existed drum file unloaded + // Load the drum parameters + char Buf[64] = {}; + std::snprintf(Buf, sizeof(Buf)-1, "fx%02x", patch[drumno]); + NSURL *url = [[NSBundle bundleWithIdentifier:@"co.losno.Organya"] URLForResource:[NSString stringWithUTF8String:Buf] withExtension:@"pxt"]; + if(!url) continue; + NSString *path = [url path]; + FILE* fp = std::fopen([path UTF8String], "rb"); + if(!fp) continue; + Pxt d; + d.Load(fp); + std::fclose(fp); + // Synthesize and mix the drum's component channels + auto& sample = DrumSamples[drumno]; + for(auto& c: d.channels) + { + auto buf = c.Synth(); + if(buf.size() > sample.size()) sample.resize(buf.size()); + for(size_t a=0; a events; + + // Volatile data, used & changed during playback: + double phaseacc, phaseinc, cur_vol; + int cur_pan, cur_length, cur_wavesize; + const short* cur_wave; + } ins[16]; + + BOOL Load(id fp) { + [fp seek:0 whence:SEEK_SET]; + char Signature[6]; + if([fp read:Signature amount:sizeof(Signature)] != sizeof(Signature)) + return NO; + if(memcmp(Signature, "Org-02", 6) != 0) + return NO; + // Load song parameters + ms_per_beat = coggetw(fp); + /*steps_per_bar =*/coggetc(fp); // irrelevant + /*beats_per_step=*/coggetc(fp); // irrelevant + loop_start = coggetd(fp); + loop_end = coggetd(fp); + // Load each instrument parameters (and initialize them) + for(auto& i: ins) + i = { coggetw(fp), coggetc(fp), coggetc(fp)!=0, (unsigned)coggetw(fp), + {}, 0,0,0,0,0,0,0 }; + // Load events for each instrument + for(auto& i: ins) + { + std::vector> events( i.n_events ); + for(auto& n: events) n.first = coggetd(fp); + for(auto& n: events) n.second.note = coggetc(fp); + for(auto& n: events) n.second.length = coggetc(fp); + for(auto& n: events) n.second.volume = coggetc(fp); + for(auto& n: events) n.second.panning = coggetc(fp); + i.events.insert(events.begin(), events.end()); + } + + return YES; + } + + void Reset(void) { + cur_beat = 0; + total_beats = 0; + loop_count = 0; + } + + std::vector Synth(double sampling_rate) + { + // Determine playback settings: + double samples_per_millisecond = sampling_rate * 1e-3, master_volume = 4e-6; + int samples_per_beat = ms_per_beat * samples_per_millisecond; // rounded. + // Begin synthesis + { + if(cur_beat == loop_end) { + cur_beat = loop_start; + loop_count++; + } + // Synthesize this beat in stereo sound (two channels). + std::vector result( samples_per_beat * 2, 0.f ); + for(auto &i: ins) + { + // Check if there is an event for this beat + auto j = i.events.find(cur_beat); + if(j != i.events.end()) + { + auto& event = j->second; + if(event.volume != 255) i.cur_vol = event.volume * master_volume; + if(event.panning != 255) i.cur_pan = event.panning; + if(event.note != 255) + { + // Calculate the note's wave data sampling frequency (equal temperament) + double freq = std::pow(2.0, (event.note + i.tuning/1000.0 + 155.376) / 12); + // Note: 155.376 comes from: + // 12*log(256*440)/log(2) - (4*12-3-1) So that note 4*12-3 plays at 440 Hz. + // Note: Optimizes into + // pow(2, (note+155.376 + tuning/1000.0) / 12.0) + // 2^(155.376/12) * exp( (note + tuning/1000.0)*log(2)/12 ) + // i.e. 7901.988*exp(0.057762265*(note + tuning*1e-3)) + i.phaseinc = freq / sampling_rate; + i.phaseacc = 0; + // And determine the actual wave data to play + i.cur_wave = &WaveTable[256 * (i.wave % 100)]; + i.cur_wavesize = 256; + i.cur_length = i.pi ? 1024/i.phaseinc : (event.length * samples_per_beat); + + if(&i >= &ins[8]) // Percussion is different + { + const auto& d = DrumSamples[i.wave % 12]; + i.phaseinc = event.note * (22050/32.5) / sampling_rate; // Linear frequency + i.cur_wave = &d[0]; + i.cur_wavesize = (int) d.size(); + i.cur_length = d.size() / i.phaseinc; + } + // Ignore missing drum samples + if(i.cur_wavesize <= 0) i.cur_length = 0; + } + } + + // Generate wave data. Calculate left & right volumes... + auto left = (i.cur_pan > 6 ? 12 - i.cur_pan : 6) * i.cur_vol; + auto right = (i.cur_pan < 6 ? i.cur_pan : 6) * i.cur_vol; + int n = samples_per_beat > i.cur_length ? i.cur_length : samples_per_beat; + for(int p=0; p double + { + if(d == 0.) return 1.; + if(std::fabs(d) > radius) return 0.; + double dr = (d *= 3.14159265) / radius; + return std::sin(d) * std::sin(dr) / (d*dr); + }; + double scale = 1/i.phaseinc > 1 ? 1 : 1/i.phaseinc, density = 0, sample = 0; + int min = -radius/scale + pos - 0.5; + int max = radius/scale + pos + 0.5; + for(int m=min; m 0.) sample /= density; // Normalize + // Save audio in float32 format: + result[p*2 + 0] += sample * left; + result[p*2 + 1] += sample * right; + i.phaseacc += i.phaseinc; + } + i.cur_length -= n; + } + + cur_beat++; + + return result; + } + } + }; +} + +@implementation OrganyaDecoder + +// Need this static initializer to create the static global tables that sidplayfp doesn't really lock access to ++ (void)initialize { + Organya::LoadWaveTable(); + Organya::LoadDrums(); +} + +- (BOOL)open:(id)s { + [self setSource:s]; + + sampleRate = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthSampleRate"] doubleValue]; + if(sampleRate < 8000.0) { + sampleRate = 44100.0; + } else if(sampleRate > 192000.0) { + sampleRate = 192000.0; + } + + m_song = new Organya::Song; + if(!m_song->Load(s)) { + return NO; + } + + long loopCount = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthDefaultLoopCount"] intValue]; + double fadeTime = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] valueForKey:@"synthDefaultFadeSeconds"] doubleValue]; + if(fadeTime < 0.0) { + fadeTime = 0.0; + } + + long beatsToEnd = m_song->loop_start + (m_song->loop_end - m_song->loop_start) * loopCount; + double lengthOfSong = ((double)m_song->ms_per_beat * 1e-3) * (double)beatsToEnd; + length = (int)ceil(lengthOfSong * sampleRate); + lengthWithFade = (int)ceil((lengthOfSong + fadeTime) * sampleRate); + + renderedTotal = 0.0; + fadeTotal = fadeRemain = (int)ceil(sampleRate * fadeTime); + + samplesDiscard = 0; + + m_song->Reset(); + + [self willChangeValueForKey:@"properties"]; + [self didChangeValueForKey:@"properties"]; + + return YES; +} + +- (NSDictionary *)properties { + return @{ @"bitrate": @(0), + @"sampleRate": @(sampleRate), + @"totalFrames": @(lengthWithFade), + @"bitsPerSample": @(32), + @"floatingPoint": @(YES), + @"channels": @(2), + @"seekable": @(YES), + @"endian": @"host", + @"encoding": @"synthesized" }; +} + +- (NSDictionary *)metadata { + return @{}; +} + +- (AudioChunk *)readAudio { + int total = 0; + + std::vector samples = m_song->Synth(sampleRate); + + int rendered = (int)(samples.size() / 2); + + renderedTotal += rendered; + + if(!IsRepeatOneSet() && renderedTotal >= length) { + float *sampleBuf = &samples[0]; + long fadeEnd = fadeRemain - rendered; + if(fadeEnd < 0) + fadeEnd = 0; + float fadePosf = (float)fadeRemain / (float)fadeTotal; + const float fadeStep = 1.0f / (float)fadeTotal; + for(long fadePos = fadeRemain; fadePos > fadeEnd; --fadePos, fadePosf -= fadeStep) { + long offset = (fadeRemain - fadePos) * 2; + sampleBuf[offset + 0] *= fadePosf; + sampleBuf[offset + 1] *= fadePosf; + } + rendered = (int)(fadeRemain - fadeEnd); + fadeRemain = fadeEnd; + } + + id audioChunkClass = NSClassFromString(@"AudioChunk"); + AudioChunk *chunk = [[audioChunkClass alloc] initWithProperties:[self properties]]; + + if(samplesDiscard) { + [chunk assignSamples:&samples[samplesDiscard * 2] frameCount:rendered - samplesDiscard]; + samplesDiscard = 0; + } else { + [chunk assignSamples:&samples[0] frameCount:rendered]; + } + + return chunk; +} + +- (long)seek:(long)frame { + long originalFrame = frame; + + if(frame < renderedTotal) { + m_song->Reset(); + renderedTotal = 0; + fadeRemain = fadeTotal; + } + + long msPerLoop = (m_song->loop_end - m_song->loop_start) * m_song->ms_per_beat; + long msIntro = m_song->loop_start * m_song->ms_per_beat; + + long samplesPerBeat = (long)ceil(m_song->ms_per_beat * 1e-3 * sampleRate); + long samplesPerLoop = (long)ceil(msPerLoop * 1e-3 * sampleRate); + long samplesIntro = (long)ceil(msIntro * 1e-3 * sampleRate); + + if(samplesPerLoop) { + while (frame >= (samplesIntro + samplesPerLoop)) { + frame -= samplesPerLoop; + m_song->loop_count++; + } + } + + long beatTarget = frame / samplesPerBeat; + samplesDiscard = frame % samplesPerBeat; + + m_song->cur_beat = (int) beatTarget; + + return originalFrame; +} + +- (void)cleanUp { + if(m_song) { + delete m_song; + m_song = NULL; + } + + source = nil; +} + +- (void)close { + [self cleanUp]; +} + +- (void)dealloc { + [self close]; +} + +- (void)setSource:(id)s { + source = s; +} + +- (id)source { + return source; +} + ++ (NSArray *)fileTypes { + return @[@"org"]; +} + ++ (NSArray *)mimeTypes { + return nil; +} + ++ (float)priority { + return 1.0; +} + ++ (NSArray *)fileTypeAssociations { + return @[ + @[@"Organya File", @"vg.icns", @"org"], + ]; +} + +@end diff --git a/Plugins/Organya/fx96.pxt b/Plugins/Organya/fx96.pxt new file mode 100755 index 000000000..a508185c1 --- /dev/null +++ b/Plugins/Organya/fx96.pxt @@ -0,0 +1,92 @@ +use :1 +size :5000 +main_model :0 +main_freq :16.00 +main_top :63 +main_offset :0 +pitch_model :3 +pitch_freq :1.00 +pitch_top :32 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :64 +ay :63 +bx :166 +by :35 +cx :255 +cy :0 + +use :1 +size :1000 +main_model :5 +main_freq :1.00 +main_top :16 +main_offset :0 +pitch_model :0 +pitch_freq :1.00 +pitch_top :32 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :64 +ay :63 +bx :91 +by :28 +cx :255 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +{1,5000,0,16.00,63,0,3,1.00,32,0,0,0.00,32,0,63,64,63,166,35,255,0}, +{1,1000,5,1.00,16,0,0,1.00,32,0,0,0.00,32,0,63,64,63,91,28,255,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, diff --git a/Plugins/Organya/fx97.pxt b/Plugins/Organya/fx97.pxt new file mode 100755 index 000000000..d7360afd4 --- /dev/null +++ b/Plugins/Organya/fx97.pxt @@ -0,0 +1,92 @@ +use :1 +size :5000 +main_model :0 +main_freq :20.00 +main_top :30 +main_offset :0 +pitch_model :3 +pitch_freq :1.00 +pitch_top :44 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :64 +ay :63 +bx :111 +by :19 +cx :255 +cy :0 + +use :1 +size :10000 +main_model :5 +main_freq :14.00 +main_top :41 +main_offset :0 +pitch_model :5 +pitch_freq :3.00 +pitch_top :32 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :64 +ay :18 +bx :91 +by :12 +cx :255 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +{1,5000,0,20.00,30,0,3,1.00,44,0,0,0.00,32,0,63,64,63,111,19,255,0}, +{1,10000,5,14.00,41,0,5,3.00,32,0,0,0.00,32,0,63,64,18,91,12,255,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, diff --git a/Plugins/Organya/fx98.pxt b/Plugins/Organya/fx98.pxt new file mode 100755 index 000000000..89e43b361 --- /dev/null +++ b/Plugins/Organya/fx98.pxt @@ -0,0 +1,92 @@ +use :1 +size :1000 +main_model :5 +main_freq :48.00 +main_top :30 +main_offset :0 +pitch_model :5 +pitch_freq :1.00 +pitch_top :32 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :64 +ay :63 +bx :166 +by :27 +cx :255 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +{1,1000,5,48.00,30,0,5,1.00,32,0,0,0.00,32,0,63,64,63,166,27,255,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, diff --git a/Plugins/Organya/fx99.pxt b/Plugins/Organya/fx99.pxt new file mode 100755 index 000000000..f9f59b162 --- /dev/null +++ b/Plugins/Organya/fx99.pxt @@ -0,0 +1,92 @@ +use :1 +size :10000 +main_model :5 +main_freq :48.00 +main_top :30 +main_offset :0 +pitch_model :5 +pitch_freq :1.00 +pitch_top :32 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :64 +ay :43 +bx :166 +by :41 +cx :255 +cy :7 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +{1,10000,5,48.00,30,0,5,1.00,32,0,0,0.00,32,0,63,64,43,166,41,255,7}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, diff --git a/Plugins/Organya/fx9a.pxt b/Plugins/Organya/fx9a.pxt new file mode 100755 index 000000000..e163036a6 --- /dev/null +++ b/Plugins/Organya/fx9a.pxt @@ -0,0 +1,92 @@ +use :1 +size :4000 +main_model :5 +main_freq :35.00 +main_top :30 +main_offset :0 +pitch_model :3 +pitch_freq :35.00 +pitch_top :32 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :53 +ay :21 +bx :166 +by :13 +cx :255 +cy :0 + +use :1 +size :10000 +main_model :1 +main_freq :63.00 +main_top :32 +main_offset :0 +pitch_model :3 +pitch_freq :1.00 +pitch_top :32 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :64 +ay :39 +bx :91 +by :20 +cx :255 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +{1,4000,5,35.00,30,0,3,35.00,32,0,0,0.00,32,0,63,53,21,166,13,255,0}, +{1,10000,1,63.00,32,0,3,1.00,32,0,0,0.00,32,0,63,64,39,91,20,255,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, diff --git a/Plugins/Organya/fx9b.pxt b/Plugins/Organya/fx9b.pxt new file mode 100755 index 000000000..fa03db70d --- /dev/null +++ b/Plugins/Organya/fx9b.pxt @@ -0,0 +1,92 @@ +use :1 +size :4000 +main_model :5 +main_freq :6.00 +main_top :32 +main_offset :0 +pitch_model :3 +pitch_freq :2.00 +pitch_top :32 +pitch_offset :0 +volume_model :3 +volume_freq :2.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :26 +ay :30 +bx :66 +by :29 +cx :255 +cy :0 + +use :1 +size :4000 +main_model :0 +main_freq :150.00 +main_top :32 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :32 +pitch_offset :0 +volume_model :3 +volume_freq :2.00 +volume_top :32 +volume_offset:0 +initialY:63 +ax :26 +ay :30 +bx :66 +by :29 +cx :255 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +use :0 +size :0 +main_model :0 +main_freq :0.00 +main_top :0 +main_offset :0 +pitch_model :0 +pitch_freq :0.00 +pitch_top :0 +pitch_offset :0 +volume_model :0 +volume_freq :0.00 +volume_top :0 +volume_offset:0 +initialY:0 +ax :0 +ay :0 +bx :0 +by :0 +cx :0 +cy :0 + +{1,4000,5,6.00,32,0,3,2.00,32,0,3,2.00,32,0,63,26,30,66,29,255,0}, +{1,4000,0,150.00,32,0,0,0.00,32,0,3,2.00,32,0,63,26,30,66,29,255,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, +{0,0,0,0.00,0,0,0,0.00,0,0,0,0.00,0,0,0,0,0,0,0,0,0}, diff --git a/Plugins/Organya/wavetable.dat b/Plugins/Organya/wavetable.dat new file mode 100755 index 0000000000000000000000000000000000000000..fb001d34851a49ba6647a1ddc3af48232daf69df GIT binary patch literal 25600 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 X!!_Y>m75?QB?oH&V`vxUL)ZO()3k}@ literal 0 HcmV?d00001