parent
72190eccf2
commit
a99ac7a38c
@ -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. |
||||
|
@ -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', |
||||
], |
||||
}, |
||||
], |
||||
} |
@ -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 = "<group>"; }; |
||||
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 = "<group>"; }; |
||||
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 = "<group>"; }; |
||||
645192D86D7D39B0CAB2E0FC /* SynthApp.gyp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SynthApp.gyp; sourceTree = "<group>"; }; |
||||
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 = "<group>"; }; |
||||
AC00D3CE197617EB5BC7110C /* English */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; }; |
||||
AFA13CCA8E0B32555D59A9B3 /* SynthMain.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SynthMain.mm; sourceTree = "<group>"; }; |
||||
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 = "<group>"; }; |
||||
/* 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 = "<group>"; |
||||
}; |
||||
26BC9EF25519FDFECC66EBA5 /* Products */ = { |
||||
isa = PBXGroup; |
||||
children = ( |
||||
E4B4F7598D96CC2CE29666B4 /* SynthApp.app */, |
||||
); |
||||
name = Products; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
56669AEF9C728CE587C3E8A3 = { |
||||
isa = PBXGroup; |
||||
children = ( |
||||
C611344839FF3B4608474E37 /* Source */, |
||||
710624B4F3ADB1F020942DB0 /* Projects */, |
||||
08D7B67857704EB61BBC1112 /* Frameworks */, |
||||
26BC9EF25519FDFECC66EBA5 /* Products */, |
||||
FF670D1787E80284877493D1 /* Build */, |
||||
); |
||||
sourceTree = "<group>"; |
||||
}; |
||||
710624B4F3ADB1F020942DB0 /* Projects */ = { |
||||
isa = PBXGroup; |
||||
children = ( |
||||
B5F1A11C57F7DE82D8F9F9C2 /* core.xcodeproj */, |
||||
); |
||||
name = Projects; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
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 = "<group>"; |
||||
}; |
||||
D9149B7557AF29282FBD3555 /* Products */ = { |
||||
isa = PBXGroup; |
||||
children = ( |
||||
BACEAAFABBABE1D12902220B /* libcore.a */, |
||||
); |
||||
name = Products; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
FF670D1787E80284877493D1 /* Build */ = { |
||||
isa = PBXGroup; |
||||
children = ( |
||||
645192D86D7D39B0CAB2E0FC /* SynthApp.gyp */, |
||||
); |
||||
name = Build; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
/* 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 = "<group>"; |
||||
}; |
||||
D651559601BF84BEA261778D /* MainMenu.xib */ = { |
||||
isa = PBXVariantGroup; |
||||
children = ( |
||||
47E3EC1A3593850470FB7E33 /* English */, |
||||
); |
||||
name = MainMenu.xib; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
/* 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 */; |
||||
} |
@ -0,0 +1,2 @@ |
||||
/* Localized versions of Info.plist keys */ |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,32 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
<plist version="1.0"> |
||||
<dict> |
||||
<key>CFBundleDevelopmentRegion</key> |
||||
<string>English</string> |
||||
<key>CFBundleExecutable</key> |
||||
<string>${EXECUTABLE_NAME}</string> |
||||
<key>CFBundleIconFile</key> |
||||
<string></string> |
||||
<key>CFBundleIdentifier</key> |
||||
<string>com.yourcompany.${PRODUCT_NAME:rfc1034identifier}</string> |
||||
<key>CFBundleInfoDictionaryVersion</key> |
||||
<string>6.0</string> |
||||
<key>CFBundleName</key> |
||||
<string>${PRODUCT_NAME}</string> |
||||
<key>CFBundlePackageType</key> |
||||
<string>APPL</string> |
||||
<key>CFBundleShortVersionString</key> |
||||
<string>1.0</string> |
||||
<key>CFBundleSignature</key> |
||||
<string>????</string> |
||||
<key>CFBundleVersion</key> |
||||
<string>1</string> |
||||
<key>LSMinimumSystemVersion</key> |
||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string> |
||||
<key>NSMainNibFile</key> |
||||
<string>MainMenu</string> |
||||
<key>NSPrincipalClass</key> |
||||
<string>NSApplication</string> |
||||
</dict> |
||||
</plist> |
@ -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 <Cocoa/Cocoa.h> |
||||
|
||||
#import <SynthMain.h> |
||||
|
||||
@interface SynthAppDelegate : NSObject <NSApplicationDelegate> { |
||||
NSWindow *window; |
||||
SynthMain synthMain; |
||||
} |
||||
|
||||
@property (assign) IBOutlet NSWindow *window; |
||||
|
||||
@end |
@ -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 |
@ -0,0 +1,7 @@ |
||||
// |
||||
// Prefix header for all source files of the 'Empty' target in the 'Empty' project |
||||
// |
||||
|
||||
#ifdef __OBJC__ |
||||
#import <Cocoa/Cocoa.h> |
||||
#endif |
@ -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 <AudioUnit/AudioUnit.h> |
||||
|
||||
#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
|
@ -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 <iostream> |
||||
#include <fstream> |
||||
#include <cstdlib> |
||||
|
||||
#import <Carbon/Carbon.h> |
||||
#import <AudioToolbox/AudioToolbox.h> |
||||
|
||||
#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; |
||||
} |
@ -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 <Cocoa/Cocoa.h> |
||||
|
||||
int main(int argc, char *argv[]) |
||||
{ |
||||
return NSApplicationMain(argc, (const char **) argv); |
||||
} |
@ -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 <iostream> |
||||
#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_); |
||||
} |
||||
|
@ -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 <CoreMIDI/CoreMIDI.h> |
||||
|
||||
#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_; |
||||
}; |
||||
|
@ -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': ['.'], |
||||
}, |
||||
], |
||||
} |
||||
|
@ -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 = "<group>"; }; |
||||
48B6535400CF3AC8BABB3299 /* sawtooth.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sawtooth.cc; sourceTree = "<group>"; }; |
||||
509D811344DB98984FD6C126 /* fm_op_kernel.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = fm_op_kernel.cc; sourceTree = "<group>"; }; |
||||
521793C71CAA078F5598EFC0 /* test_ringbuffer.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_ringbuffer.cc; sourceTree = "<group>"; }; |
||||
68FD17910F296961541A67E4 /* ringbuffer.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ringbuffer.cc; sourceTree = "<group>"; }; |
||||
6C1B71FBACD041F20E7F828A /* core.gyp */ = {isa = PBXFileReference; lastKnownFileType = text; path = core.gyp; sourceTree = "<group>"; }; |
||||
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 = "<group>"; }; |
||||
9FF02D488CE6FD5017D7D81A /* freqlut.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = freqlut.cc; sourceTree = "<group>"; }; |
||||
B73D485E55EBD9CD5950A375 /* env.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = env.cc; sourceTree = "<group>"; }; |
||||
BA00975977E2704F74104728 /* dx7note.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dx7note.cc; sourceTree = "<group>"; }; |
||||
D1D8B6FB01C9E7E2D99378F0 /* sin.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sin.cc; sourceTree = "<group>"; }; |
||||
DA7AAEE2AD874001F6B71D52 /* fm_core.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = fm_core.cc; sourceTree = "<group>"; }; |
||||
/* 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 = "<group>"; |
||||
}; |
||||
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 = "<group>"; |
||||
}; |
||||
72A5A7469C1AF9FDDAB23BD1 /* Build */ = { |
||||
isa = PBXGroup; |
||||
children = ( |
||||
6C1B71FBACD041F20E7F828A /* core.gyp */, |
||||
); |
||||
name = Build; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
ADABDC1FC75C07B5469F92DE /* Products */ = { |
||||
isa = PBXGroup; |
||||
children = ( |
||||
8B1FC9FF853D5C32F4771091 /* libcore.a */, |
||||
); |
||||
name = Products; |
||||
sourceTree = "<group>"; |
||||
}; |
||||
/* 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 */; |
||||
} |
@ -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 <iostream> |
||||
#include <math.h> |
||||
#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); |
||||
} |
||||
} |
||||
|
@ -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_
|
@ -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 <iostream> |
||||
#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)); |
||||
} |
||||
} |
||||
|
@ -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
|
||||
|
@ -0,0 +1,127 @@ |
||||
#include <iostream> |
||||
#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; |
||||
} |
||||
} |
@ -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
|
@ -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 <math.h> |
||||
#include <iostream> |
||||
|
||||
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; |
||||
} |
@ -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); |
||||
}; |
@ -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 <iostream> // for debugging |
||||
#include <stdint.h> |
||||
#include <math.h> |
||||
|
||||
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); |
||||
} |
@ -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); |
||||
}; |
@ -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 <iostream> |
||||
#include <cstdlib> |
||||
#include <math.h> |
||||
|
||||
#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; |
||||
} |
@ -0,0 +1,17 @@ |
||||
{ |
||||
'targets': [ |
||||
{ |
||||
'target_name': 'main', |
||||
'type': 'executable', |
||||
'sources': [ |
||||
'main.cc', |
||||
'wavout.cc', |
||||
], |
||||
'dependencies': [ |
||||
'core.gyp:core', |
||||
], |
||||
'include_dirs': ['.'], |
||||
}, |
||||
], |
||||
} |
||||
|
@ -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 <stdint.h> |
||||
|
||||
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; |
||||
}; |
@ -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; |
||||
} |
@ -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]; |
||||
}; |
@ -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 <time.h> |
||||
#include <string.h> |
||||
#include <algorithm> |
||||
|
||||
#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; |
||||
} |
||||
} |
||||
} |
||||
|
@ -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_
|
@ -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 <math.h> |
||||
#include <iostream> |
||||
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; |
||||
} |
@ -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; |
||||
}; |
@ -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 <math.h> |
||||
#include <iostream> |
||||
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; |
||||
} |
@ -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 |
@ -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 <stdint.h> |
||||
|
||||
#define LG_N 6 |
||||
#define N (1 << LG_N) |
||||
|
||||
#if defined(__APPLE__) |
||||
#include <libkern/OSAtomic.h> |
||||
#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
|
@ -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 <iostream> |
||||
|
||||
#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; |
||||
} |
||||
} |
||||
} |
@ -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_; |
||||
}; |
@ -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 <time.h> |
||||
#include <pthread.h> |
||||
#include <iostream> |
||||
|
||||
#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); |
||||
} |
||||
|
@ -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(); |
||||
} |
@ -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 <fstream> |
||||
|
||||
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; |
||||
}; |
Loading…
Reference in new issue