diff --git a/cpp/README b/cpp/README new file mode 100644 index 0000000..5d29905 --- /dev/null +++ b/cpp/README @@ -0,0 +1,20 @@ +README for C++ codebase + +The C++ codebase will eventually be the primary sound generation module +for this app. It's still experimental, and not yet wired up to the Android +parts, but can be used to make sound. The best way is to use the simple +test app for the Mac. + +To build, edit src/SynthApp/SynthMain.mm to change the path to ROM1A.SYX +to the actual path. These patches can be downloaded from: + +http://www.abdn.ac.uk/~mth192/dx7/dx7patch.zip + +Also change the "KeyRig 49" string to match the actual USB name of your +MIDI controller. Then "open src/SynthApp.xcodeproj", then do "Build and +Run". + +The File Open menu command is hooked up as well and will load SYX format +DX7 patch files (32 patches per file). Send program change midi events +with the first 32 program numbers. + diff --git a/cpp/src/SynthApp.gyp b/cpp/src/SynthApp.gyp new file mode 100644 index 0000000..97585c5 --- /dev/null +++ b/cpp/src/SynthApp.gyp @@ -0,0 +1,38 @@ +{ + 'targets': [ + { + 'target_name': 'SynthApp', + 'type': 'executable', + 'mac_bundle': 1, + 'include_dirs': ['.'], + 'sources': [ + 'SynthApp/main.m', + 'SynthApp/midi_in_mac.cc', + 'SynthApp/SynthAppDelegate.mm', + 'SynthApp/SynthApp_Prefix.pch', + 'SynthApp/SynthMain.mm', + ], + 'dependencies': [ + 'core.gyp:core', + ], + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/AudioToolbox.framework', + '$(SDKROOT)/System/Library/Frameworks/AudioUnit.framework', + '$(SDKROOT)/System/Library/Frameworks/Carbon.framework', + '$(SDKROOT)/System/Library/Frameworks/Cocoa.framework', + '$(SDKROOT)/System/Library/Frameworks/CoreAudio.framework', + '$(SDKROOT)/System/Library/Frameworks/CoreFoundation.framework', + '$(SDKROOT)/System/Library/Frameworks/CoreMIDI.framework', + ], + }, + 'xcode_settings': { + 'INFOPLIST_FILE': 'SynthApp/Synth-Info.plist', + }, + 'mac_bundle_resources': [ + 'SynthApp/English.lproj/InfoPlist.strings', + 'SynthApp/English.lproj/MainMenu.xib', + ], + }, + ], +} diff --git a/cpp/src/SynthApp.xcodeproj/project.pbxproj b/cpp/src/SynthApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..03d547c --- /dev/null +++ b/cpp/src/SynthApp.xcodeproj/project.pbxproj @@ -0,0 +1,306 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 070048C57FCB5F31827D61CD /* midi_in_mac.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9CC658C4805259001BB2BA70 /* midi_in_mac.cc */; }; + 444FC1A8A4F4BFBB71A55578 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 106567A2B2FDE65A2547B9C0 /* InfoPlist.strings */; }; + 4EC6E0A88AFE158936E7DF22 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3EDCE57490308092B70255E /* CoreFoundation.framework */; }; + 6004825C9C1BA9246F032431 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2D3126A377165245C44B6C8A /* CoreAudio.framework */; }; + 6824545AE8F4DAFB95F6CDDB /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = FAB723D6CDE6B962FBA69894 /* main.m */; }; + 752469DD01013D889966B42C /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F664EAAFF41058799553650 /* AudioUnit.framework */; }; + 7E84D499B82B6C02EB8AA96C /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = D651559601BF84BEA261778D /* MainMenu.xib */; }; + 7F9707F48C04E2AE1EB5C866 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4618975BF19F21FC2A429474 /* Cocoa.framework */; }; + 9366D617A2CE978AEA3555B3 /* libcore.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BACEAAFABBABE1D12902220B /* libcore.a */; }; + 982E3B747974F63D8B4482B2 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9DC34125BEDD68ABCAB055D /* AudioToolbox.framework */; }; + 98F8F0314510BD0F6AA320CE /* SynthMain.mm in Sources */ = {isa = PBXBuildFile; fileRef = AFA13CCA8E0B32555D59A9B3 /* SynthMain.mm */; }; + B6BA9FF151AD9A0D309C0D53 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1680278C574FAEFF7657992 /* CoreMIDI.framework */; }; + C5E74E9A85CDC29EB3DCCBF7 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0638CA1C453E326E762DA4BE /* Carbon.framework */; }; + D6CAF0C6797908C34B18E987 /* SynthAppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 22758A226C30B8A0C2E095B2 /* SynthAppDelegate.mm */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0113EA7215BA932B68889274 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B5F1A11C57F7DE82D8F9F9C2 /* core.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 5A9991BB6607533745115226; + remoteInfo = core; + }; + 8733BC1F6527748907794EA9 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B5F1A11C57F7DE82D8F9F9C2 /* core.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8B1FC9FF853D5C32F4771091; + remoteInfo = core; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0638CA1C453E326E762DA4BE /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; + 22758A226C30B8A0C2E095B2 /* SynthAppDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SynthAppDelegate.mm; sourceTree = ""; }; + 2D3126A377165245C44B6C8A /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; + 3C854865960DE58DBB62E200 /* SynthApp_Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = text; path = SynthApp_Prefix.pch; sourceTree = ""; }; + 4618975BF19F21FC2A429474 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 47E3EC1A3593850470FB7E33 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = ""; }; + 645192D86D7D39B0CAB2E0FC /* SynthApp.gyp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SynthApp.gyp; sourceTree = ""; }; + 6F664EAAFF41058799553650 /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; + 9CC658C4805259001BB2BA70 /* midi_in_mac.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = midi_in_mac.cc; sourceTree = ""; }; + AC00D3CE197617EB5BC7110C /* English */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; + AFA13CCA8E0B32555D59A9B3 /* SynthMain.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SynthMain.mm; sourceTree = ""; }; + B1680278C574FAEFF7657992 /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; }; + B5F1A11C57F7DE82D8F9F9C2 /* core.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = core.xcodeproj; sourceTree = SOURCE_ROOT; }; + C3EDCE57490308092B70255E /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + E4B4F7598D96CC2CE29666B4 /* SynthApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SynthApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F9DC34125BEDD68ABCAB055D /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + FAB723D6CDE6B962FBA69894 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E0443A39F1982DD2A0D4C8E6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9366D617A2CE978AEA3555B3 /* libcore.a in Frameworks */, + 982E3B747974F63D8B4482B2 /* AudioToolbox.framework in Frameworks */, + 752469DD01013D889966B42C /* AudioUnit.framework in Frameworks */, + C5E74E9A85CDC29EB3DCCBF7 /* Carbon.framework in Frameworks */, + 7F9707F48C04E2AE1EB5C866 /* Cocoa.framework in Frameworks */, + 6004825C9C1BA9246F032431 /* CoreAudio.framework in Frameworks */, + 4EC6E0A88AFE158936E7DF22 /* CoreFoundation.framework in Frameworks */, + B6BA9FF151AD9A0D309C0D53 /* CoreMIDI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08D7B67857704EB61BBC1112 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F9DC34125BEDD68ABCAB055D /* AudioToolbox.framework */, + 6F664EAAFF41058799553650 /* AudioUnit.framework */, + 0638CA1C453E326E762DA4BE /* Carbon.framework */, + 4618975BF19F21FC2A429474 /* Cocoa.framework */, + 2D3126A377165245C44B6C8A /* CoreAudio.framework */, + C3EDCE57490308092B70255E /* CoreFoundation.framework */, + B1680278C574FAEFF7657992 /* CoreMIDI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 26BC9EF25519FDFECC66EBA5 /* Products */ = { + isa = PBXGroup; + children = ( + E4B4F7598D96CC2CE29666B4 /* SynthApp.app */, + ); + name = Products; + sourceTree = ""; + }; + 56669AEF9C728CE587C3E8A3 = { + isa = PBXGroup; + children = ( + C611344839FF3B4608474E37 /* Source */, + 710624B4F3ADB1F020942DB0 /* Projects */, + 08D7B67857704EB61BBC1112 /* Frameworks */, + 26BC9EF25519FDFECC66EBA5 /* Products */, + FF670D1787E80284877493D1 /* Build */, + ); + sourceTree = ""; + }; + 710624B4F3ADB1F020942DB0 /* Projects */ = { + isa = PBXGroup; + children = ( + B5F1A11C57F7DE82D8F9F9C2 /* core.xcodeproj */, + ); + name = Projects; + sourceTree = ""; + }; + C611344839FF3B4608474E37 /* Source */ = { + isa = PBXGroup; + children = ( + 106567A2B2FDE65A2547B9C0 /* InfoPlist.strings */, + D651559601BF84BEA261778D /* MainMenu.xib */, + 22758A226C30B8A0C2E095B2 /* SynthAppDelegate.mm */, + 3C854865960DE58DBB62E200 /* SynthApp_Prefix.pch */, + AFA13CCA8E0B32555D59A9B3 /* SynthMain.mm */, + FAB723D6CDE6B962FBA69894 /* main.m */, + 9CC658C4805259001BB2BA70 /* midi_in_mac.cc */, + ); + name = Source; + path = SynthApp; + sourceTree = ""; + }; + D9149B7557AF29282FBD3555 /* Products */ = { + isa = PBXGroup; + children = ( + BACEAAFABBABE1D12902220B /* libcore.a */, + ); + name = Products; + sourceTree = ""; + }; + FF670D1787E80284877493D1 /* Build */ = { + isa = PBXGroup; + children = ( + 645192D86D7D39B0CAB2E0FC /* SynthApp.gyp */, + ); + name = Build; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C05003BAF5004F3B18F94002 /* SynthApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = DB5E551BF701B14CD4C2B627 /* Build configuration list for PBXNativeTarget "SynthApp" */; + buildPhases = ( + 11D527F4085F0D823076B687 /* Resources */, + BCACCEFB309FFDB59EE9B9A1 /* Sources */, + E0443A39F1982DD2A0D4C8E6 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 27F0280BCDE369FB81181FD4 /* PBXTargetDependency */, + ); + name = SynthApp; + productName = SynthApp; + productReference = E4B4F7598D96CC2CE29666B4 /* SynthApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F6DFBBC1A435D3962598EE50 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + }; + buildConfigurationList = F2D5F35CD2FF169326ADC8E8 /* Build configuration list for PBXProject "SynthApp" */; + compatibilityVersion = "Xcode 3.2"; + hasScannedForEncodings = 1; + mainGroup = 56669AEF9C728CE587C3E8A3; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = D9149B7557AF29282FBD3555 /* Products */; + ProjectRef = B5F1A11C57F7DE82D8F9F9C2 /* core.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + C05003BAF5004F3B18F94002 /* SynthApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + BACEAAFABBABE1D12902220B /* libcore.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libcore.a; + remoteRef = 8733BC1F6527748907794EA9 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 11D527F4085F0D823076B687 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 444FC1A8A4F4BFBB71A55578 /* InfoPlist.strings in Resources */, + 7E84D499B82B6C02EB8AA96C /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + BCACCEFB309FFDB59EE9B9A1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6824545AE8F4DAFB95F6CDDB /* main.m in Sources */, + 070048C57FCB5F31827D61CD /* midi_in_mac.cc in Sources */, + D6CAF0C6797908C34B18E987 /* SynthAppDelegate.mm in Sources */, + 98F8F0314510BD0F6AA320CE /* SynthMain.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 27F0280BCDE369FB81181FD4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = core; + targetProxy = 0113EA7215BA932B68889274 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 106567A2B2FDE65A2547B9C0 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + AC00D3CE197617EB5BC7110C /* English */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + D651559601BF84BEA261778D /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 47E3EC1A3593850470FB7E33 /* English */, + ); + name = MainMenu.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 200985946FE77FF1BE0AA81C /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + INTERMEDIATE_DIR = "$(PROJECT_DERIVED_FILE_DIR)/$(CONFIGURATION)"; + SHARED_INTERMEDIATE_DIR = "$(SYMROOT)/DerivedSources/$(CONFIGURATION)"; + }; + name = Default; + }; + F637FD3979A20A160561D04A /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = .; + INFOPLIST_FILE = "SynthApp/Synth-Info.plist"; + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks"; + PRODUCT_NAME = SynthApp; + WRAPPER_PREFIX = ""; + }; + name = Default; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + DB5E551BF701B14CD4C2B627 /* Build configuration list for PBXNativeTarget "SynthApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F637FD3979A20A160561D04A /* Default */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Default; + }; + F2D5F35CD2FF169326ADC8E8 /* Build configuration list for PBXProject "SynthApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 200985946FE77FF1BE0AA81C /* Default */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Default; + }; +/* End XCConfigurationList section */ + }; + rootObject = F6DFBBC1A435D3962598EE50 /* Project object */; +} diff --git a/cpp/src/SynthApp/English.lproj/InfoPlist.strings b/cpp/src/SynthApp/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/cpp/src/SynthApp/English.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/cpp/src/SynthApp/English.lproj/MainMenu.xib b/cpp/src/SynthApp/English.lproj/MainMenu.xib new file mode 100644 index 0000000..8a526fb --- /dev/null +++ b/cpp/src/SynthApp/English.lproj/MainMenu.xib @@ -0,0 +1,3617 @@ + + + + 1060 + 10K549 + 823 + 1038.36 + 461.00 + + com.apple.InterfaceBuilder.CocoaPlugin + 823 + + + YES + + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + PluginDependencyRecalculationVersion + + + + YES + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + YES + + + Synth + + 1048576 + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + Synth + + YES + + + About Synth + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Preferences… + , + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Services + + 1048576 + 2147483647 + + + submenuAction: + + Services + + YES + + _NSServicesMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Hide Synth + h + 1048576 + 2147483647 + + + + + + Hide Others + h + 1572864 + 2147483647 + + + + + + Show All + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Quit Synth + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + File + + 1048576 + 2147483647 + + + submenuAction: + + File + + YES + + + New + n + 1048576 + 2147483647 + + + + + + Open… + o + 1048576 + 2147483647 + + + + + + Open Recent + + 1048576 + 2147483647 + + + submenuAction: + + Open Recent + + YES + + + Clear Menu + + 1048576 + 2147483647 + + + + + _NSRecentDocumentsMenu + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Close + w + 1048576 + 2147483647 + + + + + + Save + s + 1048576 + 2147483647 + + + + + + Save As… + S + 1179648 + 2147483647 + + + + + + Revert to Saved + + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Page Setup... + P + 1179648 + 2147483647 + + + + + + + Print… + p + 1048576 + 2147483647 + + + + + + + + + Edit + + 1048576 + 2147483647 + + + submenuAction: + + Edit + + YES + + + Undo + z + 1048576 + 2147483647 + + + + + + Redo + Z + 1179648 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Cut + x + 1048576 + 2147483647 + + + + + + Copy + c + 1048576 + 2147483647 + + + + + + Paste + v + 1048576 + 2147483647 + + + + + + Paste and Match Style + V + 1572864 + 2147483647 + + + + + + Delete + + 1048576 + 2147483647 + + + + + + Select All + a + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Find + + 1048576 + 2147483647 + + + submenuAction: + + Find + + YES + + + Find… + f + 1048576 + 2147483647 + + + 1 + + + + Find Next + g + 1048576 + 2147483647 + + + 2 + + + + Find Previous + G + 1179648 + 2147483647 + + + 3 + + + + Use Selection for Find + e + 1048576 + 2147483647 + + + 7 + + + + Jump to Selection + j + 1048576 + 2147483647 + + + + + + + + + Spelling and Grammar + + 1048576 + 2147483647 + + + submenuAction: + + Spelling and Grammar + + YES + + + Show Spelling and Grammar + : + 1048576 + 2147483647 + + + + + + Check Document Now + ; + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Check Spelling While Typing + + 1048576 + 2147483647 + + + + + + Check Grammar With Spelling + + 1048576 + 2147483647 + + + + + + Correct Spelling Automatically + + 2147483647 + + + + + + + + + Substitutions + + 1048576 + 2147483647 + + + submenuAction: + + Substitutions + + YES + + + Show Substitutions + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Smart Copy/Paste + f + 1048576 + 2147483647 + + + 1 + + + + Smart Quotes + g + 1048576 + 2147483647 + + + 2 + + + + Smart Dashes + + 2147483647 + + + + + + Smart Links + G + 1179648 + 2147483647 + + + 3 + + + + Text Replacement + + 2147483647 + + + + + + + + + Transformations + + 2147483647 + + + submenuAction: + + Transformations + + YES + + + Make Upper Case + + 2147483647 + + + + + + Make Lower Case + + 2147483647 + + + + + + Capitalize + + 2147483647 + + + + + + + + + Speech + + 1048576 + 2147483647 + + + submenuAction: + + Speech + + YES + + + Start Speaking + + 1048576 + 2147483647 + + + + + + Stop Speaking + + 1048576 + 2147483647 + + + + + + + + + + + + Format + + 2147483647 + + + submenuAction: + + Format + + YES + + + Font + + 2147483647 + + + submenuAction: + + Font + + YES + + + Show Fonts + t + 1048576 + 2147483647 + + + + + + Bold + b + 1048576 + 2147483647 + + + 2 + + + + Italic + i + 1048576 + 2147483647 + + + 1 + + + + Underline + u + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Bigger + + + 1048576 + 2147483647 + + + 3 + + + + Smaller + - + 1048576 + 2147483647 + + + 4 + + + + YES + YES + + + 2147483647 + + + + + + Kern + + 2147483647 + + + submenuAction: + + Kern + + YES + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Tighten + + 2147483647 + + + + + + Loosen + + 2147483647 + + + + + + + + + Ligature + + 2147483647 + + + submenuAction: + + Ligature + + YES + + + Use Default + + 2147483647 + + + + + + Use None + + 2147483647 + + + + + + Use All + + 2147483647 + + + + + + + + + Baseline + + 2147483647 + + + submenuAction: + + Baseline + + YES + + + Use Default + + 2147483647 + + + + + + Superscript + + 2147483647 + + + + + + Subscript + + 2147483647 + + + + + + Raise + + 2147483647 + + + + + + Lower + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Colors + C + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Copy Style + c + 1572864 + 2147483647 + + + + + + Paste Style + v + 1572864 + 2147483647 + + + + + _NSFontMenu + + + + + Text + + 2147483647 + + + submenuAction: + + Text + + YES + + + Align Left + { + 1048576 + 2147483647 + + + + + + Center + | + 1048576 + 2147483647 + + + + + + Justify + + 2147483647 + + + + + + Align Right + } + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + Writing Direction + + 2147483647 + + + submenuAction: + + Writing Direction + + YES + + + YES + Paragraph + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + YES + Selection + + 2147483647 + + + + + + CURlZmF1bHQ + + 2147483647 + + + + + + CUxlZnQgdG8gUmlnaHQ + + 2147483647 + + + + + + CVJpZ2h0IHRvIExlZnQ + + 2147483647 + + + + + + + + + YES + YES + + + 2147483647 + + + + + + Show Ruler + + 2147483647 + + + + + + Copy Ruler + c + 1310720 + 2147483647 + + + + + + Paste Ruler + v + 1310720 + 2147483647 + + + + + + + + + + + + View + + 1048576 + 2147483647 + + + submenuAction: + + View + + YES + + + Show Toolbar + t + 1572864 + 2147483647 + + + + + + Customize Toolbar… + + 1048576 + 2147483647 + + + + + + + + + Window + + 1048576 + 2147483647 + + + submenuAction: + + Window + + YES + + + Minimize + m + 1048576 + 2147483647 + + + + + + Zoom + + 1048576 + 2147483647 + + + + + + YES + YES + + + 1048576 + 2147483647 + + + + + + Bring All to Front + + 1048576 + 2147483647 + + + + + _NSWindowsMenu + + + + + Help + + 2147483647 + + + submenuAction: + + Help + + YES + + + Synth Help + ? + 1048576 + 2147483647 + + + + + _NSHelpMenu + + + + _NSMainMenu + + + 15 + 2 + {{335, 390}, {480, 360}} + 1954021376 + Synth + NSWindow + + {1.79769e+308, 1.79769e+308} + + + 256 + {480, 360} + + + + {{0, 0}, {1920, 1178}} + {1.79769e+308, 1.79769e+308} + + + SynthAppDelegate + + + NSFontManager + + + + + YES + + + performMiniaturize: + + + + 37 + + + + arrangeInFront: + + + + 39 + + + + print: + + + + 86 + + + + runPageLayout: + + + + 87 + + + + clearRecentDocuments: + + + + 127 + + + + orderFrontStandardAboutPanel: + + + + 142 + + + + performClose: + + + + 193 + + + + toggleContinuousSpellChecking: + + + + 222 + + + + undo: + + + + 223 + + + + copy: + + + + 224 + + + + checkSpelling: + + + + 225 + + + + paste: + + + + 226 + + + + stopSpeaking: + + + + 227 + + + + cut: + + + + 228 + + + + showGuessPanel: + + + + 230 + + + + redo: + + + + 231 + + + + selectAll: + + + + 232 + + + + startSpeaking: + + + + 233 + + + + delete: + + + + 235 + + + + performZoom: + + + + 240 + + + + performFindPanelAction: + + + + 241 + + + + centerSelectionInVisibleArea: + + + + 245 + + + + toggleGrammarChecking: + + + + 347 + + + + toggleSmartInsertDelete: + + + + 355 + + + + toggleAutomaticQuoteSubstitution: + + + + 356 + + + + toggleAutomaticLinkDetection: + + + + 357 + + + + saveDocument: + + + + 362 + + + + saveDocumentAs: + + + + 363 + + + + revertDocumentToSaved: + + + + 364 + + + + runToolbarCustomizationPalette: + + + + 365 + + + + toggleToolbarShown: + + + + 366 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + unhideAllApplications: + + + + 370 + + + + newDocument: + + + + 373 + + + + openDocument: + + + + 374 + + + + addFontTrait: + + + + 421 + + + + addFontTrait: + + + + 422 + + + + modifyFont: + + + + 423 + + + + orderFrontFontPanel: + + + + 424 + + + + modifyFont: + + + + 425 + + + + raiseBaseline: + + + + 426 + + + + lowerBaseline: + + + + 427 + + + + copyFont: + + + + 428 + + + + subscript: + + + + 429 + + + + superscript: + + + + 430 + + + + tightenKerning: + + + + 431 + + + + underline: + + + + 432 + + + + orderFrontColorPanel: + + + + 433 + + + + useAllLigatures: + + + + 434 + + + + loosenKerning: + + + + 435 + + + + pasteFont: + + + + 436 + + + + unscript: + + + + 437 + + + + useStandardKerning: + + + + 438 + + + + useStandardLigatures: + + + + 439 + + + + turnOffLigatures: + + + + 440 + + + + turnOffKerning: + + + + 441 + + + + terminate: + + + + 449 + + + + toggleAutomaticSpellingCorrection: + + + + 456 + + + + orderFrontSubstitutionsPanel: + + + + 458 + + + + toggleAutomaticDashSubstitution: + + + + 461 + + + + toggleAutomaticTextReplacement: + + + + 463 + + + + uppercaseWord: + + + + 464 + + + + capitalizeWord: + + + + 467 + + + + lowercaseWord: + + + + 468 + + + + pasteAsPlainText: + + + + 486 + + + + performFindPanelAction: + + + + 487 + + + + performFindPanelAction: + + + + 488 + + + + performFindPanelAction: + + + + 489 + + + + showHelp: + + + + 493 + + + + delegate + + + + 495 + + + + alignCenter: + + + + 518 + + + + pasteRuler: + + + + 519 + + + + toggleRuler: + + + + 520 + + + + alignRight: + + + + 521 + + + + copyRuler: + + + + 522 + + + + alignJustified: + + + + 523 + + + + alignLeft: + + + + 524 + + + + makeBaseWritingDirectionNatural: + + + + 525 + + + + makeBaseWritingDirectionLeftToRight: + + + + 526 + + + + makeBaseWritingDirectionRightToLeft: + + + + 527 + + + + makeTextWritingDirectionNatural: + + + + 528 + + + + makeTextWritingDirectionLeftToRight: + + + + 529 + + + + makeTextWritingDirectionRightToLeft: + + + + 530 + + + + window + + + + 532 + + + + + YES + + 0 + + YES + + + + + + -2 + + + File's Owner + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + YES + + + + + + + + + + + + 19 + + + YES + + + + + + 56 + + + YES + + + + + + 217 + + + YES + + + + + + 83 + + + YES + + + + + + 81 + + + YES + + + + + + + + + + + + + + + + 75 + + + + + 80 + + + + + 78 + + + + + 72 + + + + + 82 + + + + + 124 + + + YES + + + + + + 77 + + + + + 73 + + + + + 79 + + + + + 112 + + + + + 74 + + + + + 125 + + + YES + + + + + + 126 + + + + + 205 + + + YES + + + + + + + + + + + + + + + + + + + + 202 + + + + + 198 + + + + + 207 + + + + + 214 + + + + + 199 + + + + + 203 + + + + + 197 + + + + + 206 + + + + + 215 + + + + + 218 + + + YES + + + + + + 216 + + + YES + + + + + + 200 + + + YES + + + + + + + + + + + 219 + + + + + 201 + + + + + 204 + + + + + 220 + + + YES + + + + + + + + + + 213 + + + + + 210 + + + + + 221 + + + + + 208 + + + + + 209 + + + + + 57 + + + YES + + + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + + + 144 + + + + + 129 + + + + + 143 + + + + + 236 + + + + + 131 + + + YES + + + + + + 149 + + + + + 145 + + + + + 130 + + + + + 24 + + + YES + + + + + + + + + 92 + + + + + 5 + + + + + 239 + + + + + 23 + + + + + 295 + + + YES + + + + + + 296 + + + YES + + + + + + + 297 + + + + + 298 + + + + + 211 + + + YES + + + + + + 212 + + + YES + + + + + + + 195 + + + + + 196 + + + + + 346 + + + + + 348 + + + YES + + + + + + 349 + + + YES + + + + + + + + + + + + 350 + + + + + 351 + + + + + 354 + + + + + 371 + + + YES + + + + + + 372 + + + + + 375 + + + YES + + + + + + 376 + + + YES + + + + + + + 377 + + + YES + + + + + + 388 + + + YES + + + + + + + + + + + + + + + + + + + + + 389 + + + + + 390 + + + + + 391 + + + + + 392 + + + + + 393 + + + + + 394 + + + + + 395 + + + + + 396 + + + + + 397 + + + YES + + + + + + 398 + + + YES + + + + + + 399 + + + YES + + + + + + 400 + + + + + 401 + + + + + 402 + + + + + 403 + + + + + 404 + + + + + 405 + + + YES + + + + + + + + + + 406 + + + + + 407 + + + + + 408 + + + + + 409 + + + + + 410 + + + + + 411 + + + YES + + + + + + + + 412 + + + + + 413 + + + + + 414 + + + + + 415 + + + YES + + + + + + + + + 416 + + + + + 417 + + + + + 418 + + + + + 419 + + + + + 420 + + + + + 450 + + + YES + + + + + + 451 + + + YES + + + + + + + + 452 + + + + + 453 + + + + + 454 + + + + + 457 + + + + + 459 + + + + + 460 + + + + + 462 + + + + + 465 + + + + + 466 + + + + + 485 + + + + + 490 + + + YES + + + + + + 491 + + + YES + + + + + + 492 + + + + + 494 + + + + + 496 + + + YES + + + + + + 497 + + + YES + + + + + + + + + + + + + + + 498 + + + + + 499 + + + + + 500 + + + + + 501 + + + + + 502 + + + + + 503 + + + YES + + + + + + 504 + + + + + 505 + + + + + 506 + + + + + 507 + + + + + 508 + + + YES + + + + + + + + + + + + + + 509 + + + + + 510 + + + + + 511 + + + + + 512 + + + + + 513 + + + + + 514 + + + + + 515 + + + + + 516 + + + + + 517 + + + + + + + YES + + YES + -3.IBPluginDependency + 112.IBPluginDependency + 112.ImportedFromIB2 + 124.IBPluginDependency + 124.ImportedFromIB2 + 125.IBPluginDependency + 125.ImportedFromIB2 + 125.editorWindowContentRectSynchronizationRect + 126.IBPluginDependency + 126.ImportedFromIB2 + 129.IBPluginDependency + 129.ImportedFromIB2 + 130.IBPluginDependency + 130.ImportedFromIB2 + 130.editorWindowContentRectSynchronizationRect + 131.IBPluginDependency + 131.ImportedFromIB2 + 134.IBPluginDependency + 134.ImportedFromIB2 + 136.IBPluginDependency + 136.ImportedFromIB2 + 143.IBPluginDependency + 143.ImportedFromIB2 + 144.IBPluginDependency + 144.ImportedFromIB2 + 145.IBPluginDependency + 145.ImportedFromIB2 + 149.IBPluginDependency + 149.ImportedFromIB2 + 150.IBPluginDependency + 150.ImportedFromIB2 + 19.IBPluginDependency + 19.ImportedFromIB2 + 195.IBPluginDependency + 195.ImportedFromIB2 + 196.IBPluginDependency + 196.ImportedFromIB2 + 197.IBPluginDependency + 197.ImportedFromIB2 + 198.IBPluginDependency + 198.ImportedFromIB2 + 199.IBPluginDependency + 199.ImportedFromIB2 + 200.IBEditorWindowLastContentRect + 200.IBPluginDependency + 200.ImportedFromIB2 + 200.editorWindowContentRectSynchronizationRect + 201.IBPluginDependency + 201.ImportedFromIB2 + 202.IBPluginDependency + 202.ImportedFromIB2 + 203.IBPluginDependency + 203.ImportedFromIB2 + 204.IBPluginDependency + 204.ImportedFromIB2 + 205.IBEditorWindowLastContentRect + 205.IBPluginDependency + 205.ImportedFromIB2 + 205.editorWindowContentRectSynchronizationRect + 206.IBPluginDependency + 206.ImportedFromIB2 + 207.IBPluginDependency + 207.ImportedFromIB2 + 208.IBPluginDependency + 208.ImportedFromIB2 + 209.IBPluginDependency + 209.ImportedFromIB2 + 210.IBPluginDependency + 210.ImportedFromIB2 + 211.IBPluginDependency + 211.ImportedFromIB2 + 212.IBPluginDependency + 212.ImportedFromIB2 + 212.editorWindowContentRectSynchronizationRect + 213.IBPluginDependency + 213.ImportedFromIB2 + 214.IBPluginDependency + 214.ImportedFromIB2 + 215.IBPluginDependency + 215.ImportedFromIB2 + 216.IBPluginDependency + 216.ImportedFromIB2 + 217.IBPluginDependency + 217.ImportedFromIB2 + 218.IBPluginDependency + 218.ImportedFromIB2 + 219.IBPluginDependency + 219.ImportedFromIB2 + 220.IBEditorWindowLastContentRect + 220.IBPluginDependency + 220.ImportedFromIB2 + 220.editorWindowContentRectSynchronizationRect + 221.IBPluginDependency + 221.ImportedFromIB2 + 23.IBPluginDependency + 23.ImportedFromIB2 + 236.IBPluginDependency + 236.ImportedFromIB2 + 239.IBPluginDependency + 239.ImportedFromIB2 + 24.IBEditorWindowLastContentRect + 24.IBPluginDependency + 24.ImportedFromIB2 + 24.editorWindowContentRectSynchronizationRect + 29.IBEditorWindowLastContentRect + 29.IBPluginDependency + 29.ImportedFromIB2 + 29.WindowOrigin + 29.editorWindowContentRectSynchronizationRect + 295.IBPluginDependency + 296.IBEditorWindowLastContentRect + 296.IBPluginDependency + 296.editorWindowContentRectSynchronizationRect + 297.IBPluginDependency + 298.IBPluginDependency + 346.IBPluginDependency + 346.ImportedFromIB2 + 348.IBPluginDependency + 348.ImportedFromIB2 + 349.IBEditorWindowLastContentRect + 349.IBPluginDependency + 349.ImportedFromIB2 + 349.editorWindowContentRectSynchronizationRect + 350.IBPluginDependency + 350.ImportedFromIB2 + 351.IBPluginDependency + 351.ImportedFromIB2 + 354.IBPluginDependency + 354.ImportedFromIB2 + 371.IBEditorWindowLastContentRect + 371.IBPluginDependency + 371.IBWindowTemplateEditedContentRect + 371.NSWindowTemplate.visibleAtLaunch + 371.editorWindowContentRectSynchronizationRect + 371.windowTemplate.maxSize + 372.IBPluginDependency + 375.IBPluginDependency + 376.IBEditorWindowLastContentRect + 376.IBPluginDependency + 377.IBPluginDependency + 388.IBEditorWindowLastContentRect + 388.IBPluginDependency + 389.IBPluginDependency + 390.IBPluginDependency + 391.IBPluginDependency + 392.IBPluginDependency + 393.IBPluginDependency + 394.IBPluginDependency + 395.IBPluginDependency + 396.IBPluginDependency + 397.IBPluginDependency + 398.IBPluginDependency + 399.IBPluginDependency + 400.IBPluginDependency + 401.IBPluginDependency + 402.IBPluginDependency + 403.IBPluginDependency + 404.IBPluginDependency + 405.IBPluginDependency + 406.IBPluginDependency + 407.IBPluginDependency + 408.IBPluginDependency + 409.IBPluginDependency + 410.IBPluginDependency + 411.IBPluginDependency + 412.IBPluginDependency + 413.IBPluginDependency + 414.IBPluginDependency + 415.IBPluginDependency + 416.IBPluginDependency + 417.IBPluginDependency + 418.IBPluginDependency + 419.IBPluginDependency + 450.IBPluginDependency + 451.IBEditorWindowLastContentRect + 451.IBPluginDependency + 452.IBPluginDependency + 453.IBPluginDependency + 454.IBPluginDependency + 457.IBPluginDependency + 459.IBPluginDependency + 460.IBPluginDependency + 462.IBPluginDependency + 465.IBPluginDependency + 466.IBPluginDependency + 485.IBPluginDependency + 490.IBPluginDependency + 491.IBEditorWindowLastContentRect + 491.IBPluginDependency + 492.IBPluginDependency + 496.IBPluginDependency + 497.IBEditorWindowLastContentRect + 497.IBPluginDependency + 498.IBPluginDependency + 499.IBPluginDependency + 5.IBPluginDependency + 5.ImportedFromIB2 + 500.IBPluginDependency + 501.IBPluginDependency + 502.IBPluginDependency + 503.IBPluginDependency + 504.IBPluginDependency + 505.IBPluginDependency + 506.IBPluginDependency + 507.IBPluginDependency + 508.IBEditorWindowLastContentRect + 508.IBPluginDependency + 509.IBPluginDependency + 510.IBPluginDependency + 511.IBPluginDependency + 512.IBPluginDependency + 513.IBPluginDependency + 514.IBPluginDependency + 515.IBPluginDependency + 516.IBPluginDependency + 517.IBPluginDependency + 56.IBPluginDependency + 56.ImportedFromIB2 + 57.IBEditorWindowLastContentRect + 57.IBPluginDependency + 57.ImportedFromIB2 + 57.editorWindowContentRectSynchronizationRect + 58.IBPluginDependency + 58.ImportedFromIB2 + 72.IBPluginDependency + 72.ImportedFromIB2 + 73.IBPluginDependency + 73.ImportedFromIB2 + 74.IBPluginDependency + 74.ImportedFromIB2 + 75.IBPluginDependency + 75.ImportedFromIB2 + 77.IBPluginDependency + 77.ImportedFromIB2 + 78.IBPluginDependency + 78.ImportedFromIB2 + 79.IBPluginDependency + 79.ImportedFromIB2 + 80.IBPluginDependency + 80.ImportedFromIB2 + 81.IBEditorWindowLastContentRect + 81.IBPluginDependency + 81.ImportedFromIB2 + 81.editorWindowContentRectSynchronizationRect + 82.IBPluginDependency + 82.ImportedFromIB2 + 83.IBPluginDependency + 83.ImportedFromIB2 + 92.IBPluginDependency + 92.ImportedFromIB2 + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{522, 812}, {146, 23}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{436, 809}, {64, 6}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{753, 187}, {275, 113}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{608, 612}, {275, 83}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{547, 180}, {254, 283}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{187, 434}, {243, 243}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{608, 612}, {167, 43}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{753, 217}, {238, 103}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{608, 612}, {241, 103}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{654, 239}, {194, 73}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{525, 802}, {197, 73}} + {{462, 873}, {405, 20}} + com.apple.InterfaceBuilder.CocoaPlugin + + {74, 862} + {{6, 978}, {478, 20}} + com.apple.InterfaceBuilder.CocoaPlugin + {{604, 269}, {231, 43}} + com.apple.InterfaceBuilder.CocoaPlugin + {{475, 832}, {234, 43}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{746, 287}, {220, 133}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{608, 612}, {215, 63}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{718, 404}, {480, 360}} + com.apple.InterfaceBuilder.CocoaPlugin + {{718, 404}, {480, 360}} + + {{33, 99}, {480, 360}} + {3.40282e+38, 3.40282e+38} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{591, 420}, {83, 43}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{523, 2}, {178, 283}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{753, 197}, {170, 63}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{397, 846}, {144, 23}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{674, 260}, {204, 183}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + {{878, 180}, {164, 173}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + {{474, 690}, {183, 183}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{23, 794}, {245, 183}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{533, 670}, {196, 203}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{145, 474}, {199, 203}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + + + + YES + + + YES + + + + + YES + + + YES + + + + 533 + + + 0 + IBCocoaFramework + + com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 + + + YES + .. + 3 + + YES + + YES + NSMenuCheckmark + NSMenuMixedState + + + YES + {9, 8} + {7, 2} + + + + diff --git a/cpp/src/SynthApp/Synth-Info.plist b/cpp/src/SynthApp/Synth-Info.plist new file mode 100644 index 0000000..f696cb2 --- /dev/null +++ b/cpp/src/SynthApp/Synth-Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.yourcompany.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/cpp/src/SynthApp/SynthAppDelegate.h b/cpp/src/SynthApp/SynthAppDelegate.h new file mode 100644 index 0000000..20c5d25 --- /dev/null +++ b/cpp/src/SynthApp/SynthAppDelegate.h @@ -0,0 +1,28 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +@interface SynthAppDelegate : NSObject { + NSWindow *window; + SynthMain synthMain; +} + +@property (assign) IBOutlet NSWindow *window; + +@end diff --git a/cpp/src/SynthApp/SynthAppDelegate.mm b/cpp/src/SynthApp/SynthAppDelegate.mm new file mode 100644 index 0000000..6e09206 --- /dev/null +++ b/cpp/src/SynthApp/SynthAppDelegate.mm @@ -0,0 +1,49 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "SynthMain.h" + +#import "SynthAppDelegate.h" + +@implementation SynthAppDelegate + +@synthesize window; + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { + synthMain.SynthInit(); +} + +- (void)openDocument:(id)sender { + NSLog(@"openDocument"); + NSOpenPanel *openPanel; + + openPanel = [NSOpenPanel openPanel]; + if (NSOKButton == [openPanel runModal]) { + NSArray *selectedPaths = [openPanel filenames]; + NSEnumerator *enumerator = [selectedPaths objectEnumerator]; + NSString *currentPath; + while (nil != (currentPath = [enumerator nextObject])) { + const char *filename = [currentPath UTF8String]; + synthMain.Load(filename); + } + } +} + +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { + NSLog(@"open"); +} + +@end diff --git a/cpp/src/SynthApp/SynthApp_Prefix.pch b/cpp/src/SynthApp/SynthApp_Prefix.pch new file mode 100644 index 0000000..3a67392 --- /dev/null +++ b/cpp/src/SynthApp/SynthApp_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'Empty' target in the 'Empty' project +// + +#ifdef __OBJC__ + #import +#endif diff --git a/cpp/src/SynthApp/SynthMain.h b/cpp/src/SynthApp/SynthMain.h new file mode 100644 index 0000000..8163ad6 --- /dev/null +++ b/cpp/src/SynthApp/SynthMain.h @@ -0,0 +1,41 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __SYNTH_SYNTH_MAIN_H +#define __SYNTH_SYNTH_MAIN_H + +#include + +#include "synth.h" +#include "midi_in_mac.h" +#include "synth_unit.h" + +class SynthMain { + public: + int SynthInit(); + int SynthDone(); + int Load(const char *filename); + private: + OSStatus setupplayback(SynthUnit *synth_unit); + OSStatus startplayback(); + OSStatus stopplayback(); + AudioUnit audioUnit_; + MidiInMac midi_in_mac_; + RingBuffer ring_buffer_; + SynthUnit *synth_unit_; +}; + +#endif // __SYNTH_SYNTH_MAIN_H diff --git a/cpp/src/SynthApp/SynthMain.mm b/cpp/src/SynthApp/SynthMain.mm new file mode 100644 index 0000000..d41e3d8 --- /dev/null +++ b/cpp/src/SynthApp/SynthMain.mm @@ -0,0 +1,183 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SynthMain.h" + +#include +#include +#include + +#import +#import + +#import "synth.h" +#import "module.h" +#import "freqlut.h" +#import "sin.h" +#import "sawtooth.h" + +using namespace ::std; + + +OSStatus audiocallback(void *data, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) { + //cout << "callback!" << inNumberFrames << endl; + + SynthUnit *synth_unit = (SynthUnit *)data; + + SInt16 *buffer = (SInt16 *)ioData->mBuffers[0].mData; + synth_unit->GetSamples(inNumberFrames, buffer); + return noErr; +} + +// Set up audio playback for Mac +OSStatus SynthMain::setupplayback(SynthUnit *synth_unit) { + Component component; + ComponentDescription description; + OSStatus err = noErr; + AURenderCallbackStruct callback; + + description.componentType = kAudioUnitType_Output; + description.componentSubType = kAudioUnitSubType_HALOutput; + description.componentManufacturer = kAudioUnitManufacturer_Apple; + description.componentFlags = 0; + description.componentFlagsMask = 0; + + if (component = FindNextComponent(NULL, &description)) { + err = OpenAComponent(component, &audioUnit_); + if (err != noErr) return err; + } + + UInt32 param = 0; + err = AudioUnitSetProperty(audioUnit_, kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, 1, ¶m, sizeof(param)); + if (err != noErr) return err; + param = 1; + err = AudioUnitSetProperty(audioUnit_, kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, 0, ¶m, sizeof(param)); + if (err != noErr) return err; + + AudioDeviceID deviceId = kAudioObjectUnknown; + UInt32 deviceIdSize = sizeof(deviceId); + AudioObjectPropertyAddress propertyAddress = { + kAudioHardwarePropertyDefaultOutputDevice, // mSelector + kAudioObjectPropertyScopeGlobal, // mScope + kAudioObjectPropertyElementMaster // mElement + }; + err = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &propertyAddress, + 0, + NULL, + &deviceIdSize, + &deviceId); + if (err != noErr) return err; + err = AudioUnitSetProperty(audioUnit_, kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, 0, &deviceId, sizeof(deviceId)); + if (err != noErr) return err; + callback.inputProc = audiocallback; + callback.inputProcRefCon = synth_unit; + err = AudioUnitSetProperty(audioUnit_, kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, &callback, sizeof(callback)); + if (err != noErr) return err; + + AudioStreamBasicDescription deviceFormat; + deviceFormat.mChannelsPerFrame = 1; + deviceFormat.mSampleRate = 44100.0; + deviceFormat.mFormatID = kAudioFormatLinearPCM; + deviceFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | + kAudioFormatFlagIsPacked; + deviceFormat.mBytesPerFrame = 2; + deviceFormat.mBitsPerChannel = deviceFormat.mBytesPerFrame * 8; + deviceFormat.mFramesPerPacket = 1; + deviceFormat.mBytesPerPacket = deviceFormat.mBytesPerFrame; + err = AudioUnitSetProperty(audioUnit_, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, &deviceFormat, sizeof(deviceFormat)); + if (err != noErr) return err; + + err = AudioUnitInitialize(audioUnit_); + if (err != noErr) return err; +} + +OSStatus SynthMain::startplayback() { + return AudioOutputUnitStart(audioUnit_); +} + +OSStatus SynthMain::stopplayback() { + return AudioOutputUnitStop(audioUnit_); +} + +int SynthMain::Load(const char *filename) { + uint8_t syx_data[4104]; + ifstream fp_in; + fp_in.open(filename, ifstream::in); + if (fp_in.fail()) { + std::cerr << "error opening file" << std::endl; + return 1; + } + fp_in.read((char *)syx_data, 4104); + if (fp_in.fail()) { + std::cerr << "error reading file" << std::endl; + return 1; + } + ring_buffer_.Write(syx_data, 4104); +#if 0 + const uint8_t *data = syx_data + 6 + 128 * patch_num; + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 17; j++) { + if (j == 4 || j == 8) std::cout << " |"; + std::cout << " " << (int)data[i * 17 + j]; + } + std::cout << std::endl; + } + for (int j = 102; j < 118; j++) { + if (j == 106 || j == 110) std::cout << " |"; + std::cout << " " << (int)data[j]; + } + std::cout << std::endl; +#endif +} + +int SynthMain::SynthInit() { + double sample_rate = 44100.0; + Freqlut::init(sample_rate); + Sawtooth::init(sample_rate); + Sin::init(); + synth_unit_ = new SynthUnit(&ring_buffer_); + if (true) { + const char *fn = "/Users/raph/dx7/ROM1A.SYX"; + Load(fn); + } + + OSStatus err = setupplayback(synth_unit_); + if (err != noErr) { + cout << err << endl; + return 1; + } + midi_in_mac_.Init(CFSTR("KeyRig 49"), &ring_buffer_); + startplayback(); + return 0; +} + +int SynthMain::SynthDone() { + midi_in_mac_.Done(); + stopplayback(); + delete synth_unit_; + return 0; +} diff --git a/cpp/src/SynthApp/main.m b/cpp/src/SynthApp/main.m new file mode 100644 index 0000000..938862c --- /dev/null +++ b/cpp/src/SynthApp/main.m @@ -0,0 +1,22 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **) argv); +} diff --git a/cpp/src/SynthApp/midi_in_mac.cc b/cpp/src/SynthApp/midi_in_mac.cc new file mode 100644 index 0000000..ac5abf3 --- /dev/null +++ b/cpp/src/SynthApp/midi_in_mac.cc @@ -0,0 +1,63 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "midi_in_mac.h" + +void MidiInMac::OnRead(const MIDIPacketList *pktlist) { + const MIDIPacket *packet = &(pktlist->packet[0]); + for (int i = 0; i < pktlist->numPackets; ++i) { + ring_buffer_->Write(packet->data, packet->length); + packet = MIDIPacketNext(packet); + } +} + +extern "C" void ReadProc(const MIDIPacketList *pktlist, void *readProcRefCon, + void *srcConnRefCon) { + MidiInMac *self = (MidiInMac *)readProcRefCon; + self->OnRead(pktlist); +} + +bool MidiInMac::Init(CFStringRef name, RingBuffer *ring_buffer) { + ring_buffer_ = ring_buffer; + OSStatus s = MIDIClientCreate(CFSTR("synth"), NULL, NULL, &client_); + if (s != noErr) return false; + s = MIDIInputPortCreate(client_, CFSTR("synthin"), ReadProc, (void *)this, + &port_); + ItemCount n = MIDIGetNumberOfDevices(); + for (int i = 0; i < n; ++i) { + MIDIDeviceRef device_ref = MIDIGetDevice(i); + CFPropertyListRef midi_device_properties; + MIDIObjectGetProperties(device_ref, &midi_device_properties, true); + CFStringRef dev_name = NULL; + s = MIDIObjectGetStringProperty(device_ref, kMIDIPropertyName, &dev_name); + CFComparisonResult comparison = CFStringCompare(dev_name, name, 0); + CFRelease(dev_name); + if (comparison == kCFCompareEqualTo) { + std::cout << "found!" << std::endl; + MIDIEntityRef entity = MIDIDeviceGetEntity(device_ref, 0); + MIDIEndpointRef endpoint_ = MIDIEntityGetSource(entity, 0); + s = MIDIPortConnectSource(port_, endpoint_, NULL); + return true; + } + } + return false; +} + +void MidiInMac::Done() { + MIDIEndpointDispose(endpoint_); +} + diff --git a/cpp/src/SynthApp/midi_in_mac.h b/cpp/src/SynthApp/midi_in_mac.h new file mode 100644 index 0000000..109758a --- /dev/null +++ b/cpp/src/SynthApp/midi_in_mac.h @@ -0,0 +1,46 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Interface for receiving MIDI events + +#include + +#include "ringbuffer.h" + +// At some point, we may want to have a generic MidiIn interface that gets +// implemented differently on different platforms, but for now we keep it +// simple (threading and lifetime might be different on other platforms, +// so it's not obvious what the interface might look like). +class MidiInMac { + public: + //MidiInMac(); + + // Return true on success. While running (ie until Done() is called, + // MIDI bytes from the device are written to the ring buffer. + bool Init(CFStringRef name, RingBuffer *ring_buffer); + + void Done(); + + // Effectively private - only called from ReadProc + void OnRead(const MIDIPacketList *pktlist); + private: + + RingBuffer *ring_buffer_; + MIDIClientRef client_; + MIDIPortRef port_; + MIDIEndpointRef endpoint_; +}; + diff --git a/cpp/src/core.gyp b/cpp/src/core.gyp new file mode 100644 index 0000000..ae9fca8 --- /dev/null +++ b/cpp/src/core.gyp @@ -0,0 +1,23 @@ +{ + 'targets': [ + { + 'target_name': 'core', + 'type': 'static_library', + 'sources': [ + 'dx7note.cc', + 'env.cc', + 'fm_core.cc', + 'fm_op_kernel.cc', + 'freqlut.cc', + 'resofilter.cc', + 'ringbuffer.cc', + 'sawtooth.cc', + 'sin.cc', + 'synth_unit.cc', + 'test_ringbuffer.cc', + ], + 'include_dirs': ['.'], + }, + ], +} + diff --git a/cpp/src/core.xcodeproj/project.pbxproj b/cpp/src/core.xcodeproj/project.pbxproj new file mode 100644 index 0000000..fb2b048 --- /dev/null +++ b/cpp/src/core.xcodeproj/project.pbxproj @@ -0,0 +1,193 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 1DBA85B761236957DF00CF7B /* fm_core.cc in Sources */ = {isa = PBXBuildFile; fileRef = DA7AAEE2AD874001F6B71D52 /* fm_core.cc */; }; + 2FB5AF9855596821669CAE1F /* dx7note.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA00975977E2704F74104728 /* dx7note.cc */; }; + 36D08A0FEA591FC0EF188469 /* env.cc in Sources */ = {isa = PBXBuildFile; fileRef = B73D485E55EBD9CD5950A375 /* env.cc */; }; + 4FB7FFF436D333023EC91E2C /* sin.cc in Sources */ = {isa = PBXBuildFile; fileRef = D1D8B6FB01C9E7E2D99378F0 /* sin.cc */; }; + 5FA224E159A6B07188657BA8 /* freqlut.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9FF02D488CE6FD5017D7D81A /* freqlut.cc */; }; + 71911D7305F0175DC4A3FF17 /* sawtooth.cc in Sources */ = {isa = PBXBuildFile; fileRef = 48B6535400CF3AC8BABB3299 /* sawtooth.cc */; }; + 80D3C6DC6F5236826B6AB404 /* test_ringbuffer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 521793C71CAA078F5598EFC0 /* test_ringbuffer.cc */; }; + 8D41064389CC8FD281956BF7 /* fm_op_kernel.cc in Sources */ = {isa = PBXBuildFile; fileRef = 509D811344DB98984FD6C126 /* fm_op_kernel.cc */; }; + 908EAB1EE59231C41FD88BCD /* ringbuffer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 68FD17910F296961541A67E4 /* ringbuffer.cc */; }; + A16F70FD02394895C6FA7326 /* synth_unit.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2082841A11DF6E62596265CF /* synth_unit.cc */; }; + D5ECD09EBEB1684C00616248 /* resofilter.cc in Sources */ = {isa = PBXBuildFile; fileRef = 97A5CBACD479212282D0BFD6 /* resofilter.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2082841A11DF6E62596265CF /* synth_unit.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = synth_unit.cc; sourceTree = ""; }; + 48B6535400CF3AC8BABB3299 /* sawtooth.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sawtooth.cc; sourceTree = ""; }; + 509D811344DB98984FD6C126 /* fm_op_kernel.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = fm_op_kernel.cc; sourceTree = ""; }; + 521793C71CAA078F5598EFC0 /* test_ringbuffer.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_ringbuffer.cc; sourceTree = ""; }; + 68FD17910F296961541A67E4 /* ringbuffer.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ringbuffer.cc; sourceTree = ""; }; + 6C1B71FBACD041F20E7F828A /* core.gyp */ = {isa = PBXFileReference; lastKnownFileType = text; path = core.gyp; sourceTree = ""; }; + 8B1FC9FF853D5C32F4771091 /* libcore.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libcore.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 97A5CBACD479212282D0BFD6 /* resofilter.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = resofilter.cc; sourceTree = ""; }; + 9FF02D488CE6FD5017D7D81A /* freqlut.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = freqlut.cc; sourceTree = ""; }; + B73D485E55EBD9CD5950A375 /* env.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = env.cc; sourceTree = ""; }; + BA00975977E2704F74104728 /* dx7note.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dx7note.cc; sourceTree = ""; }; + D1D8B6FB01C9E7E2D99378F0 /* sin.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sin.cc; sourceTree = ""; }; + DA7AAEE2AD874001F6B71D52 /* fm_core.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = fm_core.cc; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 59A50EE4C8CF7BE6875289EF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1CE02724939AB46300B24440 = { + isa = PBXGroup; + children = ( + 521E615727EAD5BFC2BFE8C3 /* Source */, + ADABDC1FC75C07B5469F92DE /* Products */, + 72A5A7469C1AF9FDDAB23BD1 /* Build */, + ); + sourceTree = ""; + }; + 521E615727EAD5BFC2BFE8C3 /* Source */ = { + isa = PBXGroup; + children = ( + BA00975977E2704F74104728 /* dx7note.cc */, + B73D485E55EBD9CD5950A375 /* env.cc */, + DA7AAEE2AD874001F6B71D52 /* fm_core.cc */, + 509D811344DB98984FD6C126 /* fm_op_kernel.cc */, + 9FF02D488CE6FD5017D7D81A /* freqlut.cc */, + 97A5CBACD479212282D0BFD6 /* resofilter.cc */, + 68FD17910F296961541A67E4 /* ringbuffer.cc */, + 48B6535400CF3AC8BABB3299 /* sawtooth.cc */, + D1D8B6FB01C9E7E2D99378F0 /* sin.cc */, + 2082841A11DF6E62596265CF /* synth_unit.cc */, + 521793C71CAA078F5598EFC0 /* test_ringbuffer.cc */, + ); + name = Source; + sourceTree = ""; + }; + 72A5A7469C1AF9FDDAB23BD1 /* Build */ = { + isa = PBXGroup; + children = ( + 6C1B71FBACD041F20E7F828A /* core.gyp */, + ); + name = Build; + sourceTree = ""; + }; + ADABDC1FC75C07B5469F92DE /* Products */ = { + isa = PBXGroup; + children = ( + 8B1FC9FF853D5C32F4771091 /* libcore.a */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 5A9991BB6607533745115226 /* core */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6B3D26CA3D8A77B9BBF035FF /* Build configuration list for PBXNativeTarget "core" */; + buildPhases = ( + CE1A34D2C5345E1B084CD2DB /* Sources */, + 59A50EE4C8CF7BE6875289EF /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = core; + productName = core; + productReference = 8B1FC9FF853D5C32F4771091 /* libcore.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 64E0BDEAB5AF6CD7ECF6A5F6 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + }; + buildConfigurationList = 2F684B8427DC8F5001D9A5B0 /* Build configuration list for PBXProject "core" */; + compatibilityVersion = "Xcode 3.2"; + hasScannedForEncodings = 1; + mainGroup = 1CE02724939AB46300B24440; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 5A9991BB6607533745115226 /* core */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + CE1A34D2C5345E1B084CD2DB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2FB5AF9855596821669CAE1F /* dx7note.cc in Sources */, + 36D08A0FEA591FC0EF188469 /* env.cc in Sources */, + 1DBA85B761236957DF00CF7B /* fm_core.cc in Sources */, + 8D41064389CC8FD281956BF7 /* fm_op_kernel.cc in Sources */, + 5FA224E159A6B07188657BA8 /* freqlut.cc in Sources */, + D5ECD09EBEB1684C00616248 /* resofilter.cc in Sources */, + 908EAB1EE59231C41FD88BCD /* ringbuffer.cc in Sources */, + 71911D7305F0175DC4A3FF17 /* sawtooth.cc in Sources */, + 4FB7FFF436D333023EC91E2C /* sin.cc in Sources */, + A16F70FD02394895C6FA7326 /* synth_unit.cc in Sources */, + 80D3C6DC6F5236826B6AB404 /* test_ringbuffer.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 0B978C26C6EBEB8479A59BA7 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + INTERMEDIATE_DIR = "$(PROJECT_DERIVED_FILE_DIR)/$(CONFIGURATION)"; + SHARED_INTERMEDIATE_DIR = "$(SYMROOT)/DerivedSources/$(CONFIGURATION)"; + }; + name = Default; + }; + 5A3F0CBEC18C4B4A90EF08F4 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + EXECUTABLE_PREFIX = lib; + HEADER_SEARCH_PATHS = .; + PRODUCT_NAME = core; + }; + name = Default; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 2F684B8427DC8F5001D9A5B0 /* Build configuration list for PBXProject "core" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0B978C26C6EBEB8479A59BA7 /* Default */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Default; + }; + 6B3D26CA3D8A77B9BBF035FF /* Build configuration list for PBXNativeTarget "core" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5A3F0CBEC18C4B4A90EF08F4 /* Default */, + ); + defaultConfigurationIsVisible = 1; + defaultConfigurationName = Default; + }; +/* End XCConfigurationList section */ + }; + rootObject = 64E0BDEAB5AF6CD7ECF6A5F6 /* Project object */; +} diff --git a/cpp/src/dx7note.cc b/cpp/src/dx7note.cc new file mode 100644 index 0000000..6550640 --- /dev/null +++ b/cpp/src/dx7note.cc @@ -0,0 +1,177 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "synth.h" +#include "freqlut.h" +#include "dx7note.h" + +using namespace std; + +int32_t midinote_to_logfreq(int midinote) { + const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) + const int step = (1 << 24) / 12; + return base + step * midinote; +} + +const int32_t coarsemul[] = { + -16777216, 0, 16777216, 26591258, 33554432, 38955489, 43368474, 47099600, + 50331648, 53182516, 55732705, 58039632, 60145690, 62083076, 63876816, + 65546747, 67108864, 68576247, 69959732, 71268397, 72509921, 73690858, + 74816848, 75892776, 76922906, 77910978, 78860292, 79773775, 80654032, + 81503396, 82323963, 83117622 +}; + +int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune) { + // TODO: pitch randomization + int32_t logfreq; + if (mode == 0) { + logfreq = midinote_to_logfreq(midinote); + logfreq += coarsemul[coarse & 31]; + if (fine) { + // (1 << 24) / log(2) + logfreq += (int32_t)floor(24204406.323123 * log(1 + 0.01 * fine) + 0.5); + } + // TODO: detune + } else { + // ((1 << 24) * log(10) / log(2) * .01) << 3 + logfreq = (4458616 * ((coarse & 3) * 100 + fine)) >> 3; + // TODO: detune + } + int32_t base_freq = Freqlut::lookup(logfreq); + return base_freq; +} + +const uint8_t velocity_data[64] = { + 0, 70, 86, 97, 106, 114, 121, 126, 132, 138, 142, 148, 152, 156, 160, 163, + 166, 170, 173, 174, 178, 181, 184, 186, 189, 190, 194, 196, 198, 200, 202, + 205, 206, 209, 211, 214, 216, 218, 220, 222, 224, 225, 227, 229, 230, 232, + 233, 235, 237, 238, 240, 241, 242, 243, 244, 246, 246, 248, 249, 250, 251, + 252, 253, 254 +}; + +// See "velocity" section of notes. Returns velocity delta in microsteps. +int ScaleVelocity(int velocity, int sensitivity) { + int clamped_vel = std::max(0, std::min(127, velocity)); + int vel_value = velocity_data[clamped_vel >> 1] - 239; + int scaled_vel = ((sensitivity * vel_value + 7) >> 3) << 4; + return scaled_vel; +} + +int ScaleRate(int midinote, int sensitivity) { + int x = std::min(31, std::max(0, midinote / 3 - 7)); + int qratedelta = (sensitivity * x) >> 3; +#ifdef SUPER_PRECISE + int rem = x & 7; + if (sensitivity == 3 && rem == 3) { + qratedelta -= 1; + } else if (sensitivity == 7 && rem > 0 && rem < 4) { + qratedelta += 1; + } +#endif + return qratedelta; +} + +const uint8_t exp_scale_data[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 14, 16, 19, 23, 27, 33, 39, 47, 56, 66, + 80, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 250 +}; + +int ScaleCurve(int group, int depth, int curve) { + int scale; + if (curve == 0 || curve == 3) { + // linear + scale = (group * depth * 329) >> 12; + } else { + // exponential + int n_scale_data = sizeof(exp_scale_data); + int raw_exp = exp_scale_data[std::min(group, n_scale_data - 1)]; + scale = (raw_exp * depth * 329) >> 15; + } + if (curve < 2) { + scale = -scale; + } + return scale; +} + +int ScaleLevel(int midinote, int break_pt, int left_depth, int right_depth, + int left_curve, int right_curve) { + int offset = midinote - break_pt - 17; + if (offset >= 0) { + return ScaleCurve(offset / 3, right_depth, right_curve); + } else { + return ScaleCurve((-offset) / 3, left_depth, left_curve); + } +} + +// Considering making this an init method... +Dx7Note::Dx7Note(const char patch[128], int midinote, int velocity) { + for (int op = 0; op < 6; op++) { + int off = op * 17; + int rates[4]; + int levels[4]; + for (int i = 0; i < 4; i++) { + rates[i] = patch[off + i]; + levels[i] = patch[off + 4 + i]; + } + int outlevel = patch[off + 14]; + for (int j = 8; j < 12; j++) { + cout << (int)patch[off + j] << " "; + } + int level_scaling = ScaleLevel(midinote, patch[off + 8], patch[off + 9], + patch[off + 10], patch[off + 11] & 3, patch[off + 11] >> 2); + outlevel += level_scaling; + outlevel = std::min(99, outlevel); + cout << op << ": " << level_scaling << " " << outlevel << endl; + outlevel = outlevel << 5; + outlevel += ScaleVelocity(velocity, patch[off + 13] >> 2); + outlevel = std::max(0, outlevel); + int rate_scaling = ScaleRate(midinote, patch[off + 12] & 7); + env_[op].init(rates, levels, outlevel, rate_scaling); + + int mode = patch[off + 15] & 1; + int coarse = patch[off + 15] >> 1; + int fine = patch[off + 16]; + int detune = (patch[off + 12] >> 3) - 7; + int32_t freq = osc_freq(midinote, mode, coarse, fine, detune); + params_[op].freq = freq; + // cout << op << " freq: " << freq << endl; + params_[op].phase = 0; + params_[op].gain[1] = 0; + } + algorithm_ = patch[110]; + int feedback = patch[111] & 7; + fb_shift_ = feedback != 0 ? 8 - feedback : 16; +} + +void Dx7Note::compute(int32_t *buf) { + for (int op = 0; op < 6; op++) { + params_[op].gain[0] = params_[op].gain[1]; + int32_t level = env_[op].getsample(); + // TODO: replace pow with faster calculation + int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24))); + params_[op].gain[1] = gain; + } + core_.compute(buf, params_, algorithm_, fb_buf_, fb_shift_); +} + +void Dx7Note::keyup() { + for (int op = 0; op < 6; op++) { + env_[op].keydown(false); + } +} + diff --git a/cpp/src/dx7note.h b/cpp/src/dx7note.h new file mode 100644 index 0000000..34bb00b --- /dev/null +++ b/cpp/src/dx7note.h @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYNTH_DX7NOTE_H_ +#define SYNTH_DX7NOTE_H_ + +// This is the logic to put together a note from the MIDI description +// and run the low-level modules. + +// It will continue to evolve a bit, as note-stealing logic, scaling, +// and real-time control of parameters live here. + +#include "env.h" +#include "fm_core.h" + +class Dx7Note { + public: + // Interesting question: should the setup be in the constructor, or should + // there be an init method? The latter would make it easier to use a fixed + // pool of note objects. + Dx7Note(const char patch[128], int midinote, int velocity); + + // Note: this _adds_ to the buffer. Interesting question whether it's + // worth it... + void compute(int32_t *buf); + + void keyup(); + + // TODO: parameter changes + + // TODO: some way of indicating end-of-note. Maybe should be a return + // value from the compute method? (Having a count return from keyup + // is also tempting, but if there's a dynamic parameter change after + // keyup, that won't work. + + private: + FmCore core_; + Env env_[6]; + FmOpParams params_[6]; + int32_t fb_buf_[2]; + int32_t fb_shift_; + + int algorithm_; +}; + +#endif // SYNTH_DX7NOTE_H_ diff --git a/cpp/src/env.cc b/cpp/src/env.cc new file mode 100644 index 0000000..bb54014 --- /dev/null +++ b/cpp/src/env.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "synth.h" +#include "env.h" + +using namespace std; + +void Env::init(const int r[4], const int l[4], int32_t ol, int rate_scaling) { + for (int i = 0; i < 4; i++) { + rates_[i] = r[i]; + levels_[i] = l[i]; + } + outlevel_ = ol; + rate_scaling_ = rate_scaling; + level_ = 0; + down_ = true; + advance(0); +} + +int32_t Env::getsample() { + if (ix_ < 3 || (ix_ < 4) && !down_) { + if (rising_) { + const int jumptarget = 1716; + if (level_ < (jumptarget << 16)) { + level_ = jumptarget << 16; + } + level_ += (((17 << 24) - level_) >> 24) * inc_; + // TODO: should probably be more accurate when inc is large + if (level_ >= targetlevel_) { + level_ = targetlevel_; + advance(ix_ + 1); + } + } else { // !rising + level_ -= inc_; + if (level_ <= targetlevel_) { + level_ = targetlevel_; + advance(ix_ + 1); + } + } + } + // TODO: this would be a good place to set level to 0 when under threshold + return level_; +} + +void Env::keydown(bool d) { + if (down_ != d) { + down_ = d; + advance(d ? 0 : 3); + } +} + +void Env::setparam(int param, int value) { + if (param < 4) { + rates_[param] = value; + } else if (param < 8) { + levels_[param - 4] = value; + } + // Unknown parameter, ignore for now +} + +const int levellut[] = { + 0, 2, 4, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 +}; + +void Env::advance(int newix) { + ix_ = newix; + if (ix_ < 4) { + int newlevel = levels_[ix_]; + int actuallevel = newlevel >= 19 ? + 14 + (newlevel >> 1) : levellut[newlevel]; + actuallevel = (actuallevel << 6) + outlevel_ - 3360; + actuallevel = actuallevel < 16 ? 16 : actuallevel; + // level here is same as Java impl + targetlevel_ = actuallevel << 16; + rising_ = (targetlevel_ > level_); + + // rate + int qrate = (rates_[ix_] * 41) >> 6; + qrate += rate_scaling_; + qrate = std::min(qrate, 63); + inc_ = (4 + (qrate & 3)) << (2 + LG_N + (qrate >> 2)); + } +} + diff --git a/cpp/src/env.h b/cpp/src/env.h new file mode 100644 index 0000000..20e2d5c --- /dev/null +++ b/cpp/src/env.h @@ -0,0 +1,62 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __ENV_H +#define __ENV_H + +// DX7 envelope generation + +class Env { + public: + + // The rates and levels arrays are calibrated to match the Dx7 parameters + // (ie, value 0..99). The outlevel parameter is calibrated in microsteps + // (ie units of approx .023 dB), with 99 * 32 = nominal full scale. The + // rate_scaling parameter is in qRate units (ie 0..63). + void init(const int rates[4], const int levels[4], int outlevel, + int rate_scaling); + + // Result is in Q24/doubling log format. Also, result is subsampled + // for every N samples. + // A couple more things need to happen for this to be used as a gain + // value. First, the # of outputs scaling needs to be applied. Also, + // modulation. + // Then, of course, log to linear. + int32_t getsample(); + + void keydown(bool down); + void setparam(int param, int value); + private: + int rates_[4]; + int levels_[4]; + int outlevel_; + int rate_scaling_; + // Level is stored so that 2^24 is one doubling, ie 16 more bits than + // the DX7 itself (fraction is stored in level rather than separate + // counter) + int32_t level_; + int targetlevel_; + bool rising_; + int ix_; + int inc_; + + bool down_; + + void advance(int newix); +}; + +#endif // __ENV_H + diff --git a/cpp/src/fm_core.cc b/cpp/src/fm_core.cc new file mode 100644 index 0000000..874164c --- /dev/null +++ b/cpp/src/fm_core.cc @@ -0,0 +1,127 @@ +#include +#include "synth.h" +#include "fm_op_kernel.h" +#include "fm_core.h" + +using namespace std; + +struct FmOperatorInfo { + int in; + int out; +}; + +enum FmOperatorFlags { + OUT_BUS_ONE = 1 << 0, + OUT_BUS_TWO = 1 << 1, + OUT_BUS_ADD = 1 << 2, + IN_BUS_ONE = 1 << 4, + IN_BUS_TWO = 1 << 5, + FB_IN = 1 << 6, + FB_OUT = 1 << 7 +}; + +struct FmAlgorithm { + int ops[6]; +}; + +const FmAlgorithm algorithms[32] = { + { { 0xc1, 0x11, 0x11, 0x14, 0x01, 0x14 } }, // 1 + { { 0x01, 0x11, 0x11, 0x14, 0xc1, 0x14 } }, // 2 + { { 0xc1, 0x11, 0x14, 0x01, 0x11, 0x14 } }, // 3 + { { 0x41, 0x11, 0x94, 0x01, 0x11, 0x14 } }, // 4 + { { 0xc1, 0x14, 0x01, 0x14, 0x01, 0x14 } }, // 5 + { { 0x41, 0x94, 0x01, 0x14, 0x01, 0x14 } }, // 6 + { { 0xc1, 0x11, 0x05, 0x14, 0x01, 0x14 } }, // 7 + { { 0x01, 0x11, 0xc5, 0x14, 0x01, 0x14 } }, // 8 + { { 0x01, 0x11, 0x05, 0x14, 0xc1, 0x14 } }, // 9 + { { 0x01, 0x05, 0x14, 0xc1, 0x11, 0x14 } }, // 10 + { { 0xc1, 0x05, 0x14, 0x01, 0x11, 0x14 } }, // 11 + { { 0x01, 0x05, 0x05, 0x14, 0xc1, 0x14 } }, // 12 + { { 0xc1, 0x05, 0x05, 0x14, 0x01, 0x14 } }, // 13 + { { 0xc1, 0x05, 0x11, 0x14, 0x01, 0x14 } }, // 14 + { { 0x01, 0x05, 0x11, 0x14, 0xc1, 0x14 } }, // 15 + { { 0xc1, 0x11, 0x02, 0x25, 0x05, 0x14 } }, // 16 + { { 0x01, 0x11, 0x02, 0x25, 0xc5, 0x14 } }, // 17 + { { 0x01, 0x11, 0x11, 0xc5, 0x05, 0x14 } }, // 18 + { { 0xc1, 0x14, 0x14, 0x01, 0x11, 0x14 } }, // 19 + { { 0x01, 0x05, 0x14, 0xc1, 0x14, 0x14 } }, // 20 + { { 0x01, 0x14, 0x14, 0xc1, 0x14, 0x14 } }, // 21 + { { 0xc1, 0x14, 0x14, 0x14, 0x01, 0x14 } }, // 22 + { { 0xc1, 0x14, 0x14, 0x01, 0x14, 0x04 } }, // 23 + { { 0xc1, 0x14, 0x14, 0x14, 0x04, 0x04 } }, // 24 + { { 0xc1, 0x14, 0x14, 0x04, 0x04, 0x04 } }, // 25 + { { 0xc1, 0x05, 0x14, 0x01, 0x14, 0x04 } }, // 26 + { { 0x01, 0x05, 0x14, 0xc1, 0x14, 0x04 } }, // 27 + { { 0x04, 0xc1, 0x11, 0x14, 0x01, 0x14 } }, // 28 + { { 0xc1, 0x14, 0x01, 0x14, 0x04, 0x04 } }, // 29 + { { 0x04, 0xc1, 0x11, 0x14, 0x04, 0x04 } }, // 30 + { { 0xc1, 0x14, 0x04, 0x04, 0x04, 0x04 } }, // 31 + { { 0xc4, 0x04, 0x04, 0x04, 0x04, 0x04 } }, // 32 +}; + +int n_out(const FmAlgorithm &alg) { + int count = 0; + for (int i = 0; i < 6; i++) { + if ((alg.ops[i] & 7) == OUT_BUS_ADD) count++; + } + return count; +} + +void FmCore::dump() { + for (int i = 0; i < 32; i++) { + cout << (i + 1) << ":"; + const FmAlgorithm &alg = algorithms[i]; + for (int j = 0; j < 6; j++) { + int flags = alg.ops[j]; + cout << " "; + if (flags & FB_IN) cout << "["; + cout << (flags & IN_BUS_ONE ? "1" : flags & IN_BUS_TWO ? "2" : "0") << "->"; + cout << (flags & OUT_BUS_ONE ? "1" : flags & OUT_BUS_TWO ? "2" : "0"); + if (flags & OUT_BUS_ADD) cout << "+"; + //cout << alg.ops[j].in << "->" << alg.ops[j].out; + if (flags & FB_OUT) cout << "]"; + } + cout << " " << n_out(alg); + cout << endl; + } +} + +void FmCore::compute(int32_t *output, FmOpParams *params, int algorithm, + int32_t *fb_buf, int feedback_shift) { + const FmAlgorithm alg = algorithms[algorithm]; + bool has_contents[3] = { true, false, false }; + for (int op = 0; op < 6; op++) { + int flags = alg.ops[op]; + bool add = (flags & OUT_BUS_ADD) != 0; + FmOpParams ¶m = params[op]; + int inbus = (flags >> 4) & 3; + int outbus = flags & 3; + int32_t *outptr = (outbus == 0) ? output : buf_[outbus - 1]; + int32_t gain1 = param.gain[0]; + int32_t gain2 = param.gain[1]; + if (gain1 != 0 || gain2 != 0) { + if (!has_contents[outbus]) { + add = false; + } + if (inbus == 0 || !has_contents[inbus]) { + // todo: more than one op in a feedback loop + if ((flags & 0xc0) == 0xc0 && feedback_shift < 16) { + // cout << op << " fb " << inbus << outbus << add << endl; + FmOpKernel::compute_fb(outptr, param.phase, param.freq, + gain1, gain2, + fb_buf, feedback_shift, add); + } else { + // cout << op << " pure " << inbus << outbus << add << endl; + FmOpKernel::compute_pure(outptr, param.phase, param.freq, + gain1, gain2, add); + } + } else { + // cout << op << " normal " << inbus << outbus << " " << param.freq << add << endl; + FmOpKernel::compute(outptr, buf_[inbus - 1], param.phase, param.freq, + gain1, gain2, add); + } + has_contents[outbus] = true; + } + param.phase += param.freq << LG_N; + } +} diff --git a/cpp/src/fm_core.h b/cpp/src/fm_core.h new file mode 100644 index 0000000..8aa9f09 --- /dev/null +++ b/cpp/src/fm_core.h @@ -0,0 +1,35 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __FM_CORE_H +#define __FM_CORE_H + +struct FmOpParams { + int32_t gain[2]; + int32_t freq; + int32_t phase; +}; + +class FmCore { + public: + static void dump(); + void compute(int32_t *output, FmOpParams *params, int algorithm, + int32_t *fb_buf, int32_t feedback_gain); + private: + int32_t buf_[2][N]; +}; + +#endif // __FM_CORE_H diff --git a/cpp/src/fm_op_kernel.cc b/cpp/src/fm_op_kernel.cc new file mode 100644 index 0000000..29f42d6 --- /dev/null +++ b/cpp/src/fm_op_kernel.cc @@ -0,0 +1,241 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +using namespace std; + +#include "synth.h" + +#include "sin.h" +#include "fm_op_kernel.h" + +void FmOpKernel::compute(int32_t *output, const int32_t *input, + int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add) { + int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; + int32_t gain = gain1; + int32_t phase = phase0; + if (add) { + for (int i = 0; i < N; i++) { + gain += dgain; + int32_t y = Sin::lookup(phase + input[i]); + output[i] += ((int64_t)y * (int64_t)gain) >> 24; + phase += freq; + } + } else { + for (int i = 0; i < N; i++) { + gain += dgain; + int32_t y = Sin::lookup(phase + input[i]); + output[i] = ((int64_t)y * (int64_t)gain) >> 24; + phase += freq; + } + } +} + +#if 1 +void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add) { + int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; + int32_t gain = gain1; + int32_t phase = phase0; + if (add) { + for (int i = 0; i < N; i++) { + gain += dgain; + int32_t y = Sin::lookup(phase); + output[i] += ((int64_t)y * (int64_t)gain) >> 24; + phase += freq; + } + } else { + for (int i = 0; i < N; i++) { + gain += dgain; + int32_t y = Sin::lookup(phase); + output[i] = ((int64_t)y * (int64_t)gain) >> 24; + phase += freq; + } + } +} +#endif + +#define noDOUBLE_ACCURACY +#define HIGH_ACCURACY + +// Experimental sine wave generators below +#if 0 +// Results: accuracy 64.3 mean, 170 worst case +// high accuracy: 5.0 mean, 49 worst case +void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add) { + int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; + int32_t gain = gain1; + int32_t phase = phase0; +#ifdef HIGH_ACCURACY + int32_t u = Sin::compute10(phase << 6); + u = ((int64_t)u * gain) >> 30; + int32_t v = Sin::compute10((phase << 6) + (1 << 28)); // quarter cycle + v = ((int64_t)v * gain) >> 30; + int32_t s = Sin::compute10(freq << 6); + int32_t c = Sin::compute10((freq << 6) + (1 << 28)); +#else + int32_t u = Sin::compute(phase); + u = ((int64_t)u * gain) >> 24; + int32_t v = Sin::compute(phase + (1 << 22)); // quarter cycle + v = ((int64_t)v * gain) >> 24; + int32_t s = Sin::compute(freq) << 6; + int32_t c = Sin::compute(freq + (1 << 22)) << 6; +#endif + for (int i = 0; i < N; i++) { + output[i] = u; + int32_t t = ((int64_t)v * (int64_t)c - (int64_t)u * (int64_t)s) >> 30; + u = ((int64_t)u * (int64_t)c + (int64_t)v * (int64_t)s) >> 30; + v = t; + } +} +#endif + +#if 0 +// Results: accuracy 392.3 mean, 15190 worst case (near freq = 0.5) +// for freq < 0.25, 275.2 mean, 716 worst +// high accuracy: 57.4 mean, 7559 worst +// freq < 0.25: 17.9 mean, 78 worst +void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add) { + int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; + int32_t gain = gain1; + int32_t phase = phase0; +#ifdef HIGH_ACCURACY + int32_t u = floor(gain * sin(phase * (M_PI / (1 << 23))) + 0.5); + int32_t v = floor(gain * cos((phase - freq * 0.5) * (M_PI / (1 << 23))) + 0.5); + int32_t a = floor((1 << 25) * sin(freq * (M_PI / (1 << 24))) + 0.5); +#else + int32_t u = Sin::compute(phase); + u = ((int64_t)u * gain) >> 24; + int32_t v = Sin::compute(phase + (1 << 22) - (freq >> 1)); + v = ((int64_t)v * gain) >> 24; + int32_t a = Sin::compute(freq >> 1) << 1; +#endif + for (int i = 0; i < N; i++) { + output[i] = u; + v -= ((int64_t)a * (int64_t)u) >> 24; + u += ((int64_t)a * (int64_t)v) >> 24; + } +} +#endif + +#if 0 +// Results: accuracy 370.0 mean, 15480 worst case (near freq = 0.5) +// with double accuracy initialization: mean 1.55, worst 58 (near freq = 0) +// with high accuracy: mean 4.2, worst 292 (near freq = 0.5) +void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add) { + int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; + int32_t gain = gain1; + int32_t phase = phase0; +#ifdef DOUBLE_ACCURACY + int32_t u = floor((1 << 30) * sin(phase * (M_PI / (1 << 23))) + 0.5); + double a_d = sin(freq * (M_PI / (1 << 24))); + int32_t v = floor((1LL << 31) * a_d * cos((phase - freq * 0.5) * + (M_PI / (1 << 23))) + 0.5); + int32_t aa = floor((1LL << 31) * a_d * a_d + 0.5); +#else +#ifdef HIGH_ACCURACY + int32_t u = Sin::compute10(phase << 6); + int32_t v = Sin::compute10((phase << 6) + (1 << 28) - (freq << 5)); + int32_t a = Sin::compute10(freq << 5); + v = ((int64_t)v * (int64_t)a) >> 29; + int32_t aa = ((int64_t)a * (int64_t)a) >> 29; +#else + int32_t u = Sin::compute(phase) << 6; + int32_t v = Sin::compute(phase + (1 << 22) - (freq >> 1)); + int32_t a = Sin::compute(freq >> 1); + v = ((int64_t)v * (int64_t)a) >> 17; + int32_t aa = ((int64_t)a * (int64_t)a) >> 17; +#endif +#endif + + if (aa < 0) aa = (1 << 31) - 1; + for (int i = 0; i < N; i++) { + gain += dgain; + output[i] = ((int64_t)u * (int64_t)gain) >> 30; + v -= ((int64_t)aa * (int64_t)u) >> 29; + u += v; + } +} +#endif + +#if 0 +// Results:: accuracy 112.3 mean, 4262 worst (near freq = 0.5) +// high accuracy 2.9 mean, 143 worst +void FmOpKernel::compute_pure(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add) { + int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; + int32_t gain = gain1; + int32_t phase = phase0; +#ifdef HIGH_ACCURACY + int32_t u = Sin::compute10(phase << 6); + int32_t lastu = Sin::compute10((phase - freq) << 6); + int32_t a = Sin::compute10((freq << 6) + (1 << 28)) << 1; +#else + int32_t u = Sin::compute(phase) << 6; + int32_t lastu = Sin::compute(phase - freq) << 6; + int32_t a = Sin::compute(freq + (1 << 22)) << 7; +#endif + if (a < 0 && freq < 256) a = (1 << 31) - 1; + if (a > 0 && freq > 0x7fff00) a = -(1 << 31); + for (int i = 0; i < N; i++) { + gain += dgain; + output[i] = ((int64_t)u * (int64_t)gain) >> 30; + //output[i] = u; + int32_t newu = (((int64_t)u * (int64_t)a) >> 30) - lastu; + lastu = u; + u = newu; + } +} +#endif + +void FmOpKernel::compute_fb(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, + int32_t *fb_buf, int fb_shift, bool add) { + int32_t dgain = (gain2 - gain1 + (N >> 1)) >> LG_N; + int32_t gain = gain1; + int32_t phase = phase0; + int32_t y0 = fb_buf[0]; + int32_t y = fb_buf[1]; + if (add) { + for (int i = 0; i < N; i++) { + gain += dgain; + int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); + y0 = y; + y = Sin::lookup(phase + scaled_fb); + y = ((int64_t)y * (int64_t)gain) >> 24; + output[i] += y; + phase += freq; + } + } else { + for (int i = 0; i < N; i++) { + gain += dgain; + int32_t scaled_fb = (y0 + y) >> (fb_shift + 1); + y0 = y; + y = Sin::lookup(phase + scaled_fb); + y = ((int64_t)y * (int64_t)gain) >> 24; + output[i] = y; + phase += freq; + } + } + fb_buf[0] = y0; + fb_buf[1] = y; +} diff --git a/cpp/src/fm_op_kernel.h b/cpp/src/fm_op_kernel.h new file mode 100644 index 0000000..2c93aff --- /dev/null +++ b/cpp/src/fm_op_kernel.h @@ -0,0 +1,35 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class FmOpKernel { + public: + // gain1 and gain2 represent linear step: gain for sample i is + // gain1 + (1 + i) / 64 * (gain2 - gain1) + + // This is the basic FM operator. No feedback. + static void compute(int32_t *output, const int32_t *input, + int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add); + + // This is a sine generator, no feedback. + static void compute_pure(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, bool add); + + // One op with feedback, no add. + static void compute_fb(int32_t *output, int32_t phase0, int32_t freq, + int32_t gain1, int32_t gain2, + int32_t *fb_buf, int fb_gain, bool add); +}; diff --git a/cpp/src/freqlut.cc b/cpp/src/freqlut.cc new file mode 100644 index 0000000..cf8afeb --- /dev/null +++ b/cpp/src/freqlut.cc @@ -0,0 +1,58 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Resolve frequency signal (1.0 in Q24 format = 1 octave) to phase delta. + +// The LUT is just a global, and we'll need the init function to be called before +// use. + +#include // for debugging +#include +#include + +using namespace std; + +#include "freqlut.h" + +#define LG_N_SAMPLES 10 +#define N_SAMPLES (1 << LG_N_SAMPLES) +#define SAMPLE_SHIFT (24 - LG_N_SAMPLES) + +#define MAX_LOGFREQ_INT 20 + +int32_t lut[N_SAMPLES + 1]; + +void Freqlut::init(double sample_rate) { + double y = (1LL << (24 + MAX_LOGFREQ_INT)) / sample_rate; + double inc = pow(2, 1.0 / N_SAMPLES); + for (int i = 0; i < N_SAMPLES + 1; i++) { + lut[i] = (int32_t)floor(y + 0.5); + y *= inc; + } +} + +// Note: if logfreq is more than 20.0, the results will be inaccurate. However, +// that will be many times the Nyquist rate. +int32_t Freqlut::lookup(int32_t logfreq) { + int ix = (logfreq & 0xffffff) >> SAMPLE_SHIFT; + + int32_t y0 = lut[ix]; + int32_t y1 = lut[ix + 1]; + int lowbits = logfreq & ((1 << SAMPLE_SHIFT) - 1); + int32_t y = y0 + ((((int64_t)(y1 - y0) * (int64_t)lowbits)) >> SAMPLE_SHIFT); + int hibits = logfreq >> 24; + return y >> (MAX_LOGFREQ_INT - hibits); +} diff --git a/cpp/src/freqlut.h b/cpp/src/freqlut.h new file mode 100644 index 0000000..5cba4af --- /dev/null +++ b/cpp/src/freqlut.h @@ -0,0 +1,21 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class Freqlut { + public: + static void init(double sample_rate); + static int32_t lookup(int32_t logfreq); +}; diff --git a/cpp/src/main.cc b/cpp/src/main.cc new file mode 100644 index 0000000..b0b44b9 --- /dev/null +++ b/cpp/src/main.cc @@ -0,0 +1,238 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "synth.h" +#include "module.h" +#include "freqlut.h" +#include "wavout.h" +#include "sawtooth.h" +#include "sin.h" +#include "resofilter.h" +#include "fm_core.h" +#include "fm_op_kernel.h" +#include "env.h" +#include "dx7note.h" + +using namespace std; + +void benchmark_sin() { + int32_t x; + for (int j = 0; j < 1000; j++) { + for (int i = 0; i < 1000000; i++) { + x += Sin::lookup(i); + } + } + cout << x << endl; // to make sure it gets used +} + +void benchmark_fm_op() { + int32_t buf[64]; + int32_t fb_buf[2]; + for (int i = 0; i < 15625000; i++) + FmOpKernel::compute_fb(buf, 0, 123456, 1 << 24, 1 << 24, + fb_buf, 1, false); +} + +void test_sin_accuracy() { + double maxerr = 0; + for (int i = 0; i < 1000000; i++) { + int32_t phase = rand() & ((1 << 24) - 1); + int32_t y = Sin::compute(phase); + double yd = (1 << 24) * sin(phase * (M_PI / (1 << 23))); + double err = fabs(y - yd); + if (err > maxerr) maxerr = err; + } + cout << "Max error: " << maxerr; +} + +void test_pure_accuracy() { + int32_t worstfreq; + int32_t worstphase; + int32_t worsterr = 0; + double errsum = 0; + for (int i = 0; i < 1000000; i++) { + int32_t freq = rand() & 0x7fffff; + int32_t phase = rand() & 0xffffff; + int32_t gain = 1 << 24; + int32_t buf[64]; + FmOpKernel::compute_pure(buf, phase, freq, gain, gain, false); + int32_t maxerr = 0; + for (int j = 0; j < 64; j++) { + double y = gain * sin((phase + j * freq) * (2.0 * M_PI / (1 << 24))); + int32_t accurate = (int32_t)floor(y + 0.5); + int32_t err = abs(buf[j] - accurate); + if (err > maxerr) maxerr = err; + } + errsum += maxerr; + if (maxerr > worsterr) { + worsterr = maxerr; + worstfreq = freq; + worstphase = phase; + } + if (i < 10) + cout << phase << " " << freq << " " << maxerr << endl; + } + cout << worstphase << " " << worstfreq << " " << worsterr << endl; + cout << "Mean: " << (errsum * 1e-6) << endl; +} + +void mksaw(double sample_rate) { + const int n_samples = 400 * 1024; + WavOut w("/tmp/foo.wav", sample_rate, n_samples); + + Sawtooth s; + int32_t control_last[1]; + int32_t control[1]; + + ResoFilter rf; + int32_t fc_last[2]; + int32_t fc[2]; + fc[0] = 0; // TODO + fc[1] = 4.2 * (1 << 24); + fc_last[0] = fc[0]; + fc_last[1] = fc[1]; + + double ramp = 1e-7; + double f = ramp * (64 + 1); + control[0] = (1 << 24) * log(f * sample_rate) / log(2); + + int32_t buf[64]; + int32_t buf2[64]; + int32_t *bufs[1]; + int32_t *bufs2[1]; + bufs[0] = buf; + bufs2[0] = buf2; + int32_t phase = 0; + for (int i = 0; i < n_samples; i += 64) { + + double f = ramp * (i + 64 + 1); + // f = 44.0 / sample_rate; + control_last[0] = control[0]; + control[0] = (1 << 24) * log(f * sample_rate) / log(2); + fc_last[1] = fc[1]; + fc[1] = 4.0 * i * (1 << 24) / n_samples; + s.process((const int32_t **)0, control, control_last, bufs); + rf.process((const int32_t **)bufs, fc, fc_last, bufs2); + for (int j = 0; j < 64; j++) { + buf2[j] = buf[j] >> 1; + //phase += 100000; + //buf2[j] = (Sin::compute(phase) - (int32_t)((1<< 24) * sin(phase * 2 * M_PI / (1 << 24)))) << 12; + } + w.write_data(buf2, 64); + } + w.close(); +} + +void mknote(double sample_rate) { + const int n_samples = 400 * 1024; + WavOut w("/tmp/foo.wav", sample_rate, n_samples); + + int32_t freq = 150358; + int32_t phase = 0; + + int rates[4] = {70, 50, 30, 80}; + int levels[4] = {99, 90, 70, 0}; + Env e; + e.init(rates, levels, 99, 0); + + int rates2[4] = {70, 50, 30, 80}; + int levels2[4] = {99, 90, 70, 0}; + Env e2; + e2.init(rates, levels, 90, 0); + + int32_t buf[64]; + + int32_t gain1, gain2; + gain2 = 0; + int32_t gain21, gain22; + gain22 = 0; + for (int i = 0; i < n_samples; i += N) { + gain1 = gain2; + gain21 = gain22; + if (i == n_samples / 2) { + e.keydown(false); + e2.keydown(false); + } + int32_t level = e.getsample(); + gain2 = (1<<8) * pow(2, level * (1.0 / (1 << 24))); + FmOpKernel::compute_pure(buf, phase, freq, gain1, gain2, false); + level = e2.getsample(); + gain22 = (1<<8) * pow(2, level * (1.0 / (1 << 24))); + FmOpKernel::compute(buf, buf, phase, freq, gain21, gain22, false); + phase += freq << LG_N; + w.write_data(buf, N); + } + w.close(); +} + +char epiano[] = { + 95, 29, 20, 50, 99, 95, 0, 0, 41, 0, 19, 0, 115, 24, 79, 2, 0, 95, 20, 20, + 50, 99, 95, 0, 0, 0, 0, 0, 0, 3, 0, 99, 2, 0, 95, 29, 20, 50, 99, 95, 0, 0, + 0, 0, 0, 0, 59, 24, 89, 2, 0, 95, 20, 20, 50, 99, 95, 0, 0, 0, 0, 0, 0, 59, + 8, 99, 2, 0, 95, 50, 35, 78, 99, 75, 0, 0, 0, 0, 0, 0, 59, 28, 58, 28, 0, 96, + 25, 25, 67, 99, 75, 0, 0, 0, 0, 0, 0, 83, 8, 99, 2, 0, 94, 67, 95, 60, 50, + 50, 50, 50, 4, 6, 34, 33, 0, 0, 56, 24, 69, 46, 80, 73, 65, 78, 79, 32, 49, + 32 +}; + +void mkdx7note(double sample_rate) { + const int n_samples = 400 * 1024; + WavOut w("/tmp/foo.wav", sample_rate, n_samples); + + Dx7Note note(epiano, 57, 64); + int32_t buf[N]; + + for (int i = 0; i < n_samples; i += N) { + for (int j = 0; j < N; j++) { + buf[j] = 0; + } + if (i == n_samples / 2) { + note.keyup(); + } + note.compute(buf); + for (int j = 0; j < N; j++) { + buf[j] >>= 2; + } + w.write_data(buf, N); + } + w.close(); +} + +void test_ringbuffer(); + +int main(int argc, char **argv) { + double sample_rate = 44100.0; + Freqlut::init(sample_rate); + Sawtooth::init(sample_rate); + Sin::init(); + + //FmCore::dump(); + //test_sin_accuracy(); + //benchmark_fm_op(); + //test_pure_accuracy(); + //benchmark_sin(); + //int32_t freq = atoi(argv[1]); + //cout << "Logfreq(" << freq << ") = " << Freqlut::lookup(freq) << endl; + + //mkdx7note(sample_rate); + mksaw(sample_rate); + //test_ringbuffer(); + return 0; +} diff --git a/cpp/src/main.gyp b/cpp/src/main.gyp new file mode 100644 index 0000000..eb272b5 --- /dev/null +++ b/cpp/src/main.gyp @@ -0,0 +1,17 @@ +{ + 'targets': [ + { + 'target_name': 'main', + 'type': 'executable', + 'sources': [ + 'main.cc', + 'wavout.cc', + ], + 'dependencies': [ + 'core.gyp:core', + ], + 'include_dirs': ['.'], + }, + ], +} + diff --git a/cpp/src/module.h b/cpp/src/module.h new file mode 100644 index 0000000..2d7624c --- /dev/null +++ b/cpp/src/module.h @@ -0,0 +1,25 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +class Module { + public: + static const int lg_n = 6; + static const int n = 1 << lg_n; + virtual void process(const int32_t **inbufs, const int32_t *control_in, + const int32_t *control_last, int32_t **outbufs) = 0; +}; diff --git a/cpp/src/resofilter.cc b/cpp/src/resofilter.cc new file mode 100644 index 0000000..8a6f861 --- /dev/null +++ b/cpp/src/resofilter.cc @@ -0,0 +1,66 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "module.h" +#include "resofilter.h" + +double this_sample_rate; + +void ResoFilter::init(double sample_rate) { + this_sample_rate = sample_rate; +} + +ResoFilter::ResoFilter() { + for (int i = 0; i < 4; i++) { + x[i] = 0; + } +} + +int32_t compute_alpha(int32_t logf) { + // TODO + return 1 << 21; +} + +void ResoFilter::process(const int32_t **inbufs, const int32_t *control_in, + const int32_t *control_last, int32_t **outbufs) { + int32_t alpha = compute_alpha(control_last[0]); + int32_t alpha_in = compute_alpha(control_in[0]); + int32_t delta_alpha = (alpha_in - alpha) >> lg_n; + int32_t k = control_last[1]; + int32_t k_in = control_in[1]; + int32_t delta_k = (k_in - k) >> lg_n; + const int32_t *ibuf = inbufs[0]; + int32_t *obuf = outbufs[0]; + int x0 = x[0]; + int x1 = x[1]; + int x2 = x[2]; + int x3 = x[3]; + for (int i = 0; i < n; i++) { + alpha += delta_alpha; + k += delta_k; + int32_t signal = ibuf[i]; + int32_t fb = ((int64_t)k * (int64_t)x3) >> 24; + x0 = x0 + ((((int64_t)(signal - fb - x0) * (int64_t)alpha)) >> 24); + x1 = x1 + ((((int64_t)(x0 - x1) * (int64_t)alpha)) >> 24); + x2 = x2 + ((((int64_t)(x1 - x2) * (int64_t)alpha)) >> 24); + x3 = x3 + ((((int64_t)(x2 - x3) * (int64_t)alpha)) >> 24); + obuf[i] = x3; + } + x[0] = x0; + x[1] = x1; + x[2] = x2; + x[3] = x3; +} diff --git a/cpp/src/resofilter.h b/cpp/src/resofilter.h new file mode 100644 index 0000000..f6437e0 --- /dev/null +++ b/cpp/src/resofilter.h @@ -0,0 +1,27 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class ResoFilter : Module { + public: + ResoFilter(); + + static void init(double sample_rate); + static int32_t lookup(int32_t phase, int32_t log_f); + void process(const int32_t **inbufs, const int32_t *control_in, + const int32_t *control_last, int32_t **outbufs); + private: + int32_t x[4]; +}; diff --git a/cpp/src/ringbuffer.cc b/cpp/src/ringbuffer.cc new file mode 100644 index 0000000..387b373 --- /dev/null +++ b/cpp/src/ringbuffer.cc @@ -0,0 +1,71 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "synth.h" +#include "ringbuffer.h" + +RingBuffer::RingBuffer() { + rd_ix_ = 0; + wr_ix_ = 0; +} + +int RingBuffer::BytesAvailable() { + return (wr_ix_ - rd_ix_) & (kBufSize - 1); +} + +int RingBuffer::Read(int size, uint8_t *bytes) { + int rd_ix = rd_ix_; + SynthMemoryBarrier(); // read barrier, make sure data is committed before ix + int fragment_size = std::min(size, kBufSize - rd_ix); + memcpy(bytes, buf_ + rd_ix, fragment_size); + if (size > fragment_size) { + memcpy(bytes + fragment_size, buf_, size - fragment_size); + } + SynthMemoryBarrier(); // full barrier, make sure read commits before updating + rd_ix_ = (rd_ix + size) & (kBufSize - 1); + return size; +} + +int RingBuffer::Write(const uint8_t *bytes, int size) { + int remaining = size; + while (remaining > 0) { + int rd_ix = rd_ix_; + int wr_ix = wr_ix_; + int space_available = (rd_ix - wr_ix - 1) & (kBufSize - 1); + if (space_available == 0) { + struct timespec sleepTime; + sleepTime.tv_sec = 0; + sleepTime.tv_nsec = 1000000; + nanosleep(&sleepTime, NULL); + } else { + int wr_size = std::min(remaining, space_available); + int fragment_size = std::min(wr_size, kBufSize - wr_ix); + memcpy(buf_ + wr_ix, bytes, fragment_size); + if (wr_size > fragment_size) { + memcpy(buf_, bytes + fragment_size, wr_size - fragment_size); + } + SynthMemoryBarrier(); // write barrier, make sure data commits + wr_ix_ = (wr_ix + wr_size) & (kBufSize - 1); + remaining -= wr_size; + bytes += wr_size; + } + } +} + diff --git a/cpp/src/ringbuffer.h b/cpp/src/ringbuffer.h new file mode 100644 index 0000000..250a590 --- /dev/null +++ b/cpp/src/ringbuffer.h @@ -0,0 +1,41 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYNTH_RINGBUFFER_H_ +#define SYNTH_RINGBUFFER_H_ + +class RingBuffer { + public: + RingBuffer(); + + // Returns number of bytes available for reading. + int BytesAvailable(); + + // Reads bytes. It is the caller's responsibility to make sure that + // size <= a previous value of BytesAvailable(). + int Read(int size, uint8_t *bytes); + + // Writes bytes into the buffer. If the buffer is full, the method will + // block until space is available. + int Write(const uint8_t *bytes, int size); + private: + static const int kBufSize = 8192; + uint8_t buf_[kBufSize]; + volatile int rd_ix_; + volatile int wr_ix_; +}; + +#endif // SYNTH_RINGBUFFER_H_ diff --git a/cpp/src/sawtooth.cc b/cpp/src/sawtooth.cc new file mode 100644 index 0000000..0be01c9 --- /dev/null +++ b/cpp/src/sawtooth.cc @@ -0,0 +1,192 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +using namespace std; + +#include "module.h" +#include "sawtooth.h" +#include "freqlut.h" + +// There's a fair amount of lookup table and so on that needs to be set before +// generating any signal. In Java, this would be done by a separate factory class. +// Here, we're just going to do it as globals. + +#define HIGH_QUALITY + +#define noLOW_FREQ_HACK + +#define FANCY_GOERTZEL_SIN +#define noPRINT_ERROR + +#define R (1 << 29) + +#ifdef LOW_FREQ_HACK +#define LG_N_SAMPLES 9 +#else +#define LG_N_SAMPLES 11 +#endif +#define N_SAMPLES (1 << LG_N_SAMPLES) +#define N_PARTIALS_MAX (N_SAMPLES / 2) + +#define LG_SLICES_PER_OCTAVE 2 +#define SLICES_PER_OCTAVE (1 << LG_SLICES_PER_OCTAVE) +#define SLICE_SHIFT (24 - LG_SLICES_PER_OCTAVE) + +#define LG_N_SLICES (LG_SLICES_PER_OCTAVE + 4) +#define N_SLICES (1 << LG_N_SLICES) + +#define NEG2OVERPI -0.63661977236758138 + +int32_t sawtooth[N_SLICES][N_SAMPLES]; + +void Sawtooth::init(double sample_rate) { + int32_t lut[N_SAMPLES / 2]; + + for (int i = 0; i < N_SAMPLES / 2; i++) { + lut[i] = 0; + } + + double slice_inc = pow(2.0, 1.0 / SLICES_PER_OCTAVE); + double f_0 = pow(slice_inc, N_SLICES - 1); + int n_partials_last = 0; + for (int j = N_SLICES - 1; j >= 0; j--) { + int n_partials = floor(0.5 * sample_rate / f_0); + n_partials = n_partials < N_PARTIALS_MAX ? n_partials : N_PARTIALS_MAX; + for (int k = n_partials_last + 1; k <= n_partials; k++) { + double scale = NEG2OVERPI / k; + scale = (N_PARTIALS_MAX - k) > (N_PARTIALS_MAX >> 2) ? scale : + scale * (N_PARTIALS_MAX - k) / (N_PARTIALS_MAX >> 2); + double dphase = k * 2 * M_PI / N_SAMPLES; +#ifdef PRINT_ERROR + int32_t maxerr = 0; +#endif +#ifdef FANCY_GOERTZEL_SIN + double ds_d = (1 << 30) * scale * sin(dphase); + double cm2_d = (1 << 29) * (2 * (cos(dphase) - 1)); + int dshift = 0; + for (dshift = 0; dshift < 16; dshift++) { + if (ds_d < -(1 << (30 - dshift))) break; + if (cm2_d < -(1 << (30 - dshift))) break; + } + int32_t ds = (int32_t)floor((1 << dshift) * ds_d + 0.5); + int32_t cm2 = (int32_t)floor((1 << dshift) * cm2_d + 0.5); + // cout << cm2_d << " " << cm2 << " " << dphase << " " << ds << " " << dshift << endl; + int32_t s = 0; + int32_t round = (1 << dshift) >> 1; + for (int i = 0; i < N_SAMPLES / 2; i++) { + lut[i] += s; +#ifdef PRINT_ERROR + int32_t good = (int32_t)floor(scale * sin(dphase * i) * (1 << 30) + 0.5); + int err = s - good; + int abs_err = err > 0 ? err : -err; + maxerr = abs_err > maxerr ? abs_err : maxerr; +#endif + ds += ((int64_t)cm2 * (int64_t)s + R) >> 29; + s += (ds + round) >> dshift; + } +#else + int32_t c = (int32_t)floor(cos(dphase) * (1 << 30) + 0.5); + int32_t s = (int32_t)floor(sin(dphase) * (1 << 30) + 0.5); + int32_t u = (int32_t)floor(scale * (1 << 30)); + int32_t v = 0; + for (int i = 0; i < N_SAMPLES / 2; i++) { + lut[i] += v; +#ifdef PRINT_ERROR + int32_t good = (int32_t)floor(scale * sin(dphase * i) * (1 << 30) + 0.5); + int err = v - good; + int abs_err = err > 0 ? err : -err; + maxerr = abs_err > maxerr ? abs_err : maxerr; +#endif + int32_t t = ((int64_t)u * (int64_t)s + (int64_t)v * (int64_t)c + R) >> 30; + u = ((int64_t)u * (int64_t)c - (int64_t)v * (int64_t)s + R) >> 30; + v = t; + } +#endif +#ifdef PRINT_ERROR + cout << maxerr << endl; +#endif + } + sawtooth[j][0] = 0; + sawtooth[j][N_SAMPLES / 2] = 0; + for (int i = 1; i < N_SAMPLES / 2; i++) { + int32_t value = (lut[i] + 32) >> 6; + sawtooth[j][i] = value; + sawtooth[j][N_SAMPLES - i] = -value; + } + n_partials_last = n_partials; + f_0 *= 1.0 / slice_inc; + } +} + +Sawtooth::Sawtooth() { + phase = 0; +} + +int32_t Sawtooth::lookup(int32_t phase, int32_t log_f) { + + log_f = log_f < 0 ? 0 : log_f; + int slice = (log_f + (1 << SLICE_SHIFT) - 1) >> SLICE_SHIFT; + int phase_int = (phase >> (24 - LG_N_SAMPLES)) & (N_SAMPLES - 1); + int lowbits = phase & ((1 << (24 - LG_N_SAMPLES)) - 1); + int y0 = sawtooth[slice][phase_int]; + int y1 = sawtooth[slice][(phase_int + 1) & (N_SAMPLES - 1)]; + + int y4 = y0 + ((((int64_t)(y1 - y0) * (int64_t)lowbits)) >> (24 - LG_N_SAMPLES)); + + // TODO: lift this out of loop + // TODO: optimal threshold probably depends on sample rate +#ifdef LOW_FREQ_HACK + if (log_f < (8 << 24)) + y4 = phase * 2 - (1 << 24); +#endif + +#ifdef HIGH_QUALITY + int y2 = sawtooth[slice + 1][phase_int]; + int y3 = sawtooth[slice + 1][(phase_int + 1) & (N_SAMPLES - 1)]; + int y5 = y2 + ((((int64_t)(y3 - y2) * (int64_t)lowbits)) >> (24 - LG_N_SAMPLES)); +#ifdef LOW_FREQ_HACK + if (log_f < (8 << 24) - (1 << SLICE_SHIFT)) + y5 = phase * 2 - (1 << 24); +#endif + int slice_lowbits = log_f & ((1 << SLICE_SHIFT) - 1); + int y = y4 + ((((int64_t)(y5 - y4) * (int64_t)slice_lowbits)) >> SLICE_SHIFT); + return y; +#else + return y4; +#endif +} + +void Sawtooth::process(const int32_t **inbufs, const int32_t *control_in, + const int32_t *control_last, int32_t **outbufs) { + int32_t logf = control_last[0]; + int32_t logf_in = control_in[0]; + int32_t *obuf = outbufs[0]; + int32_t delta_logf = (logf_in - logf) >> lg_n; + int f = Freqlut::lookup(logf); + int f_in = Freqlut::lookup(logf_in); + int32_t delta_f = (f_in - f) >> lg_n; + int32_t p = phase; + for (int i = 0; i < n; i++) { + f += delta_f; + logf += delta_logf; + obuf[i] = lookup(p, logf); + p += f; + p &= (1 << 24) - 1; + } + phase = p; +} diff --git a/cpp/src/sawtooth.h b/cpp/src/sawtooth.h new file mode 100644 index 0000000..ac85c59 --- /dev/null +++ b/cpp/src/sawtooth.h @@ -0,0 +1,27 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class Sawtooth : Module { + public: + Sawtooth(); + + static void init(double sample_rate); + static int32_t lookup(int32_t phase, int32_t log_f); + void process(const int32_t **inbufs, const int32_t *control_in, + const int32_t *control_last, int32_t **outbufs); + private: + int32_t phase; +}; diff --git a/cpp/src/sin.cc b/cpp/src/sin.cc new file mode 100644 index 0000000..5839528 --- /dev/null +++ b/cpp/src/sin.cc @@ -0,0 +1,142 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +using namespace std; + +#include "sin.h" + +#define R (1 << 29) + +#ifdef SIN_DELTA +int32_t sintab[SIN_N_SAMPLES << 1]; +#else +int32_t sintab[SIN_N_SAMPLES + 1]; +#endif + +void Sin::init() { + double dphase = 2 * M_PI / SIN_N_SAMPLES; + int32_t c = (int32_t)floor(cos(dphase) * (1 << 30) + 0.5); + int32_t s = (int32_t)floor(sin(dphase) * (1 << 30) + 0.5); + int32_t u = 1 << 30; + int32_t v = 0; + for (int i = 0; i < SIN_N_SAMPLES / 2; i++) { +#ifdef SIN_DELTA + sintab[(i << 1) + 1] = (v + 32) >> 6; + sintab[((i + SIN_N_SAMPLES / 2) << 1) + 1] = -((v + 32) >> 6); +#else + sintab[i] = (v + 32) >> 6; + sintab[i + SIN_N_SAMPLES / 2] = -((v + 32) >> 6); +#endif + int32_t t = ((int64_t)u * (int64_t)s + (int64_t)v * (int64_t)c + R) >> 30; + u = ((int64_t)u * (int64_t)c - (int64_t)v * (int64_t)s + R) >> 30; + v = t; + } +#ifdef SIN_DELTA + for (int i = 0; i < SIN_N_SAMPLES - 1; i++) { + sintab[i << 1] = sintab[(i << 1) + 3] - sintab[(i << 1) + 1]; + } + sintab[(SIN_N_SAMPLES << 1) - 2] = -sintab[(SIN_N_SAMPLES << 1) - 1]; +#else + sintab[SIN_N_SAMPLES] = 0; +#endif +} + +#ifndef SIN_INLINE +int32_t Sin::lookup(int32_t phase) { + const int SHIFT = 24 - SIN_LG_N_SAMPLES; + int lowbits = phase & ((1 << SHIFT) - 1); +#ifdef SIN_DELTA + int phase_int = (phase >> (SHIFT - 1)) & ((SIN_N_SAMPLES - 1) << 1); + int dy = sintab[phase_int]; + int y0 = sintab[phase_int + 1]; + + return y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); +#else + int phase_int = (phase >> SHIFT) & (SIN_N_SAMPLES - 1); + int y0 = sintab[phase_int]; + int y1 = sintab[phase_int + 1]; + + return y0 + (((int64_t)(y1 - y0) * (int64_t)lowbits) >> SHIFT); +#endif +} +#endif + + +#if 0 +// The following is an implementation designed not to use any lookup tables, +// based on the following implementation by Basile Graf: +// http://www.rossbencina.com/static/code/sinusoids/even_polynomial_sin_approximation.txt + +#define C0 (1 << 24) +#define C1 (331121857 >> 2) +#define C2 (1084885537 >> 4) +#define C3 (1310449902 >> 6) + +int32_t Sin::compute(int32_t phase) { + int32_t x = (phase & ((1 << 23) - 1)) - (1 << 22); + int32_t x2 = ((int64_t)x * (int64_t)x) >> 22; + int32_t x4 = ((int64_t)x2 * (int64_t)x2) >> 24; + int32_t x6 = ((int64_t)x2 * (int64_t)x4) >> 24; + int32_t y = C0 - + (((int64_t)C1 * (int64_t)x2) >> 24) + + (((int64_t)C2 * (int64_t)x4) >> 24) - + (((int64_t)C3 * (int64_t)x6) >> 24); + y ^= -((phase >> 23) & 1); + return y; +} +#endif + +#if 1 +// coefficients are Chebyshev polynomial, computed by compute_cos_poly.py +#define C8_0 16777216 +#define C8_2 -331168742 +#define C8_4 1089453524 +#define C8_6 -1430910663 +#define C8_8 950108533 + +int32_t Sin::compute(int32_t phase) { + int32_t x = (phase & ((1 << 23) - 1)) - (1 << 22); + int32_t x2 = ((int64_t)x * (int64_t)x) >> 16; + int32_t y = (((((((((((((int64_t)C8_8 + * (int64_t)x2) >> 32) + C8_6) + * (int64_t)x2) >> 32) + C8_4) + * (int64_t)x2) >> 32) + C8_2) + * (int64_t)x2) >> 32) + C8_0); + y ^= -((phase >> 23) & 1); + return y; +} +#endif + +#define C10_0 (1 << 30) +#define C10_2 -1324675874 // scaled * 4 +#define C10_4 1089501821 +#define C10_6 -1433689867 +#define C10_8 1009356886 +#define C10_10 -421101352 +int32_t Sin::compute10(int32_t phase) { + int32_t x = (phase & ((1 << 29) - 1)) - (1 << 28); + int32_t x2 = ((int64_t)x * (int64_t)x) >> 26; + int32_t y = ((((((((((((((((int64_t)C10_10 + * (int64_t)x2) >> 34) + C10_8) + * (int64_t)x2) >> 34) + C10_6) + * (int64_t)x2) >> 34) + C10_4) + * (int64_t)x2) >> 32) + C10_2) + * (int64_t)x2) >> 30) + C10_0); + y ^= -((phase >> 29) & 1); + return y; +} diff --git a/cpp/src/sin.h b/cpp/src/sin.h new file mode 100644 index 0000000..d84a7d3 --- /dev/null +++ b/cpp/src/sin.h @@ -0,0 +1,62 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class Sin { + public: + Sin(); + + static void init(); + static int32_t lookup(int32_t phase); + static int32_t compute(int32_t phase); + + // A more accurate sine, both input and output Q30 + static int32_t compute10(int32_t phase); +}; + +#define SIN_LG_N_SAMPLES 10 +#define SIN_N_SAMPLES (1 << SIN_LG_N_SAMPLES) + +#define SIN_INLINE + +// Use twice as much RAM for the LUT but avoid a little computation +#define SIN_DELTA + +#ifdef SIN_DELTA +extern int32_t sintab[SIN_N_SAMPLES << 1]; +#else +extern int32_t sintab[SIN_N_SAMPLES + 1]; +#endif + +#ifdef SIN_INLINE +inline +int32_t Sin::lookup(int32_t phase) { + const int SHIFT = 24 - SIN_LG_N_SAMPLES; + int lowbits = phase & ((1 << SHIFT) - 1); +#ifdef SIN_DELTA + int phase_int = (phase >> (SHIFT - 1)) & ((SIN_N_SAMPLES - 1) << 1); + int dy = sintab[phase_int]; + int y0 = sintab[phase_int + 1]; + + return y0 + (((int64_t)dy * (int64_t)lowbits) >> SHIFT); +#else + int phase_int = (phase >> SHIFT) & (SIN_N_SAMPLES - 1); + int y0 = sintab[phase_int]; + int y1 = sintab[phase_int + 1]; + + return y0 + (((int64_t)(y1 - y0) * (int64_t)lowbits) >> SHIFT); +#endif +} +#endif diff --git a/cpp/src/synth.h b/cpp/src/synth.h new file mode 100644 index 0000000..f5aca84 --- /dev/null +++ b/cpp/src/synth.h @@ -0,0 +1,42 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __SYNTH_H +#define __SYNTH_H + +// This may not be present on MSVC. +// See http://stackoverflow.com/questions/126279/c99-stdint-h-header-and-ms-visual-studio +#include + +#define LG_N 6 +#define N (1 << LG_N) + +#if defined(__APPLE__) +#include +#define SynthMemoryBarrier() OSMemoryBarrier() +#elif defined(__GNUC__) +#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) +#define SynthMemoryBarrier __sync_synchronize() +#else +#warning Memory barrier is not enabled +#endif +#warning Memory barrier is not enabled +#endif + +// #undef SynthMemoryBarrier() +// #define SynthMemoryBarrier() + +#endif // __SYNTH_H diff --git a/cpp/src/synth_unit.cc b/cpp/src/synth_unit.cc new file mode 100644 index 0000000..406dd12 --- /dev/null +++ b/cpp/src/synth_unit.cc @@ -0,0 +1,159 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "synth.h" +#include "synth_unit.h" + +char epiano[] = { + 95, 29, 20, 50, 99, 95, 0, 0, 41, 0, 19, 0, 115, 24, 79, 2, 0, + 95, 20, 20, 50, 99, 95, 0, 0, 0, 0, 0, 0, 3, 0, 99, 2, 0, + 95, 29, 20, 50, 99, 95, 0, 0, 0, 0, 0, 0, 59, 24, 89, 2, 0, + 95, 20, 20, 50, 99, 95, 0, 0, 0, 0, 0, 0, 59, 8, 99, 2, 0, + 95, 50, 35, 78, 99, 75, 0, 0, 0, 0, 0, 0, 59, 28, 58, 28, 0, + 96, 25, 25, 67, 99, 75, 0, 0, 0, 0, 0, 0, 83, 8, 99, 2, 0, + + 94, 67, 95, 60, 50, 50, 50, 50, 4, 6, 34, 33, 0, 0, 56, 24, + 69, 46, 80, 73, 65, 78, 79, 32, 49, 32 +}; + +SynthUnit::SynthUnit(RingBuffer *ring_buffer) { + ring_buffer_ = ring_buffer; + for (int note = 0; note < max_active_notes; ++note) { + active_note_[note].dx7_note = NULL; + } + input_buffer_index_ = 0; + std::memcpy(patch_data_, epiano, sizeof(epiano)); + current_patch_ = 0; +} + +// Transfer as many bytes as possible from ring buffer to input buffer. +// Note that this implementation has a fair amount of copying - we'd probably +// do it a bit differently if it were bulk data, but in this case we're +// optimizing for simplicity of implementation. +void SynthUnit::TransferInput() { + size_t bytes_available = ring_buffer_->BytesAvailable(); + int bytes_to_read = std::min(bytes_available, + sizeof(input_buffer_) - input_buffer_index_); + if (bytes_to_read > 0) { + ring_buffer_->Read(bytes_to_read, input_buffer_ + input_buffer_index_); + input_buffer_index_ += bytes_to_read; + } +} + +void SynthUnit::ConsumeInput(int n_input_bytes) { + if (n_input_bytes < input_buffer_index_) { + std::memmove(input_buffer_, input_buffer_ + n_input_bytes, + input_buffer_index_ - n_input_bytes); + } + input_buffer_index_ -= n_input_bytes; +} + +int SynthUnit::ProcessMidiMessage(const uint8_t *buf, int buf_size) { + uint8_t cmd = buf[0]; + uint8_t cmd_type = cmd & 0xf0; + if (cmd_type == 0x80 || (cmd_type == 0x90 && buf[2] == 0)) { + if (buf_size >= 3) { + // note off + for (int note = 0; note < max_active_notes; ++note) { + if (active_note_[note].midi_note == buf[1] && + active_note_[note].dx7_note != NULL) { + active_note_[note].dx7_note->keyup(); + } + } + return 3; + } + return 0; + } else if (cmd_type == 0x90) { + if (buf_size >= 3) { + // note on + // Note that this implements round-robin (same as original Dx7). A more + // sophisticated note-stealing algorithm is probably worthwhile. + delete active_note_[current_note_].dx7_note; + active_note_[current_note_].midi_note = buf[1]; + const uint8_t *patch = patch_data_ + 128 * current_patch_; + active_note_[current_note_].dx7_note = + new Dx7Note((const char *)patch, buf[1], buf[2]); + current_note_ = (current_note_ + 1) % max_active_notes; + return 3; + } + return 0; + } else if (cmd_type == 0xc0) { + if (buf_size >= 2) { + // program change + int program_number = buf[1]; + current_patch_ = std::min(program_number, 31); + char name[11]; + std::memcpy(name, patch_data_ + 128 * current_patch_ + 118, 10); + name[10] = 0; + std::cout << "Loaded patch " << current_patch_ << ": " << name << "\r"; + std::cout.flush(); + return 2; + } + return 0; + } else if (cmd == 0xf0) { + // sysex + if (buf_size >= 6 && buf[1] == 0x43 && buf[2] == 0x00 && buf[3] == 0x09 && + buf[4] == 0x20 && buf[5] == 0x00) { + if (buf_size >= 4104) { + // TODO: check checksum? + std::memcpy(patch_data_, buf + 6, 4096); + return 4104; + } + return 0; + } + } + + // TODO: more robust handling + std::cout << "Unknown message " << std::hex << (int)cmd << + ", skipping " << std::dec << buf_size << " bytes" << std::endl; + return buf_size; +} + +void SynthUnit::GetSamples(int n_samples, int16_t *buffer) { + TransferInput(); + size_t input_offset; + for (input_offset = 0; input_offset < input_buffer_index_; ) { + int bytes_available = input_buffer_index_ - input_offset; + int bytes_consumed = ProcessMidiMessage(input_buffer_ + input_offset, + bytes_available); + if (bytes_consumed == 0) { + break; + } + input_offset += bytes_consumed; + } + ConsumeInput(input_offset); + + for (int i = 0; i < n_samples; i += N) { + int32_t audiobuf[N]; + for (int j = 0; j < N; ++j) { + audiobuf[j] = 0; + } + for (int note = 0; note < max_active_notes; ++note) { + if (active_note_[note].dx7_note != NULL) { + active_note_[note].dx7_note->compute(audiobuf); + } + } + for (int j = 0; j < N; ++j) { + int32_t val = audiobuf[j] >> 4; + int clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff : + val >> 9; + // TODO: maybe some dithering? + buffer[i + j] = clip_val; + } + } +} diff --git a/cpp/src/synth_unit.h b/cpp/src/synth_unit.h new file mode 100644 index 0000000..5ad59c0 --- /dev/null +++ b/cpp/src/synth_unit.h @@ -0,0 +1,46 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dx7note.h" +#include "ringbuffer.h" + +struct ActiveNote { + int midi_note; + Dx7Note *dx7_note; +}; + +class SynthUnit { + public: + explicit SynthUnit(RingBuffer *ring_buffer); + + void GetSamples(int n_samples, int16_t *buffer); + private: + void TransferInput(); + + void ConsumeInput(int n_input_bytes); + + int ProcessMidiMessage(const uint8_t *buf, int buf_size); + + RingBuffer *ring_buffer_; + static const int max_active_notes = 16; + ActiveNote active_note_[max_active_notes]; + int current_note_; + uint8_t input_buffer_[8192]; + size_t input_buffer_index_; + + uint8_t patch_data_[4096]; + int current_patch_; +}; diff --git a/cpp/src/test_ringbuffer.cc b/cpp/src/test_ringbuffer.cc new file mode 100644 index 0000000..e3b3352 --- /dev/null +++ b/cpp/src/test_ringbuffer.cc @@ -0,0 +1,70 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "ringbuffer.h" + +#define kNumIter 100000 +#define kBufSize 256 + +using namespace ::std; + +void *consumer_thread(void *arg) { + RingBuffer *rb = (RingBuffer *)arg; + uint8_t buf[kBufSize]; + for (int i = 0; i < kNumIter; ++i) { + int bytes_available = rb->BytesAvailable(); + // cout << "bytes_available = " << bytes_available << endl; + while (bytes_available < kBufSize) { + struct timespec sleepTime; + sleepTime.tv_sec = 0; + sleepTime.tv_nsec = 1000000; + nanosleep(&sleepTime, NULL); + bytes_available = rb->BytesAvailable(); + } + // cout << "reading..." << endl; + rb->Read(kBufSize, buf); + for (int j = 0; j < kBufSize; ++j) { + int expected = (i + j) & 0xff; + if (buf[j] != expected) { + cout << "error at " << i << ", " << j << " expected " << expected << + " got " << buf[j] << endl; + } + } + } + return NULL; +} + +void test_ringbuffer() { + RingBuffer rb; + uint8_t buf[kBufSize]; + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_create(&thread, &attr, consumer_thread, (void *)&rb); + for (int i = 0; i < kNumIter; ++i) { + for (int j = 0; j < kBufSize; ++j) { + buf[j] = i + j; + } + // cout << "writing..." << rb.BytesAvailable() << endl; + rb.Write(buf, kBufSize); + } + pthread_join(thread, NULL); +} + diff --git a/cpp/src/wavout.cc b/cpp/src/wavout.cc new file mode 100644 index 0000000..982b8fc --- /dev/null +++ b/cpp/src/wavout.cc @@ -0,0 +1,75 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Create a WAV file + +#include "wavout.h" + +using namespace std; + +void w32le(char *buf, int offset, int32_t val) { + buf[offset + 0] = val & 0xff; + buf[offset + 1] = (val >> 8) & 0xff; + buf[offset + 2] = (val >> 16) & 0xff; + buf[offset + 3] = (val >> 24) & 0xff; +} + +void w16le(char *buf, int offset, int val) { + buf[offset + 0] = val & 0xff; + buf[offset + 1] = (val >> 8) & 0xff; +} + +void w4cc(char *buf, int offset, const char *s) { + for (int i = 0; i < 4; i++) { + buf[offset + i] = s[i]; + } +} + +WavOut::WavOut(const char *filename, double sample_rate, int n_samples) { + char header[44]; + fs = new fstream(filename, fstream::out | fstream::trunc | fstream::binary); + w4cc(header, 0, "RIFF"); + w32le(header, 4, 36 + 2 * n_samples); + w4cc(header, 8, "WAVE"); + w4cc(header, 12, "fmt "); + w32le(header, 16, 16); + w16le(header, 20, 1); // AudioFormat + w16le(header, 22, 1); // NumChannels + w32le(header, 24, (int32_t)sample_rate); + w32le(header, 28, 2 * (int32_t)sample_rate); + w16le(header, 32, 2); // BlockAlign + w16le(header, 34, 16); // BitsPerSample + w4cc(header, 36, "data"); + w32le(header, 40, 2 * n_samples); + fs->write(header, 44); +} + +void WavOut::write_data(const int32_t *buf, int n) { + int32_t delta = 0x100; + for (int i = 0; i < n; i++) { + int32_t val = buf[i]; + int clip_val = val < -(1 << 24) ? 0x8000 : (val >= (1 << 24) ? 0x7fff : + (val + delta) >> 9); + delta = (delta + val) & 0x1ff; + sample_buf[i * 2] = clip_val & 0xff; + sample_buf[i * 2 + 1] = (clip_val >> 8) & 0xff; + } + fs->write(sample_buf, n * 2); +} + +void WavOut::close() { + fs->close(); +} diff --git a/cpp/src/wavout.h b/cpp/src/wavout.h new file mode 100644 index 0000000..10ea159 --- /dev/null +++ b/cpp/src/wavout.h @@ -0,0 +1,29 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +class WavOut { + public: + WavOut(const char *filename, double sample_rate, int n_samples); + + void write_data(const int32_t *buf, int n); + + void close(); + private: + char sample_buf[128]; + std::fstream *fs; +};