63 Commits
v1.1 ... v1.13

Author SHA1 Message Date
Jack Andersen
055e73183a Add standalone bootstrap script 2016-07-18 13:18:15 -10:00
Jack Andersen
a048605011 Support for loading revised Factor5 N64 ROMs 2016-07-18 12:38:28 -10:00
Jack Andersen
260ec5bb93 Add standalone bootstrap script 2016-07-18 07:52:17 -10:00
Jack Andersen
2a2a16fd17 Typing refinements for song events 2016-07-17 11:23:29 -10:00
Jack Andersen
b421412cac Add lookup table for Rogue Squadron SNG Setup mappings 2016-07-16 11:55:13 -10:00
Jack Andersen
5bae40d3c3 Handle additional corner cases for getChanProgram 2016-07-14 09:42:54 -10:00
Jack Andersen
d06a5ebcfa Fix amuserender crash 2016-07-13 20:51:59 -10:00
Jack Andersen
e2581cea8b Better master volume lookup 2016-07-13 20:36:12 -10:00
Jack Andersen
d602fbacd3 Added master volume API for entire engine 2016-07-13 20:16:00 -10:00
Jack Andersen
2dcb9dd1c7 Working Studio implementation 2016-07-13 18:54:46 -10:00
Jack Andersen
d3d5595422 Initial multiple-referencing submix refactor 2016-07-12 17:04:55 -10:00
Jack Andersen
596bc66ce6 Windows build fix 2016-07-07 09:37:50 -10:00
Jack Andersen
3a7b43a63a Mask out high bit on Tempo changes 2016-07-07 09:17:30 -10:00
Jack Andersen
83a2bf0b4e Merge branch 'master' of https://github.com/AxioDL/amuse 2016-07-06 18:21:49 -10:00
Jack Andersen
695fc10b8f Setup ID prompt for amuserender 2016-07-06 18:19:40 -10:00
feea7c2ecc Add PaperMario TTYD Song Group descs 2016-07-06 21:10:02 -07:00
Jack Andersen
1be5d6e821 Windows fixes 2016-07-06 11:45:41 -10:00
Jack Andersen
2d31313594 Add amuserender executable 2016-07-06 11:30:46 -10:00
Jack Andersen
52cba61f76 Refactored audio supply dispatch across two passes 2016-07-04 15:08:00 -10:00
Jack Andersen
fe78a675d7 Change default volume to 80% to fill newfound headroom 2016-07-03 17:35:37 -10:00
Jack Andersen
3427515960 Add Starfox Adventures midi.wad support 2016-07-03 12:41:31 -10:00
Jack Andersen
5ad8c06b99 add SongState::DetectVersion for much less hacky version-detection 2016-07-02 11:50:38 -10:00
Jack Andersen
e99dbc7e0a Initialization fix in SongConverter as indicated by valgrind 2016-06-30 09:30:13 -10:00
Jack Andersen
d6b9d4fca1 PC SNG decoder fixes 2016-06-27 12:43:04 -10:00
Jack Andersen
c7f093c5ee Minor adjustments to test PC SNG data extract 2016-06-27 11:02:10 -10:00
Jack Andersen
22a8534887 Support for N64 SNG Control and Program Change encoding 2016-06-22 14:28:02 -10:00
Jack Andersen
0c606fa9b7 Windows Fixes 2016-06-22 12:15:53 -10:00
Jack Andersen
a0241574ba Plenty of SongConverter bug fixes 2016-06-22 11:43:45 -10:00
Jack Andersen
bd10015024 Work on MIDI-to-SNG conversion 2016-06-21 18:18:28 -10:00
Jack Andersen
a0bb35433a Initial amuseconv implementation with SNG extraction 2016-06-20 20:40:13 -10:00
Jack Andersen
3bc47baa1d N64 SNG fixes 2016-06-19 22:08:32 -10:00
Jack Andersen
7666b51c50 Merge branch 'master' of https://github.com/AxioDL/amuse 2016-06-19 17:36:32 -10:00
Jack Andersen
ee29fb4b1e Initial N64 SNG support; pitch-wheel fix 2016-06-19 17:35:57 -10:00
Jack Andersen
ad23f9d0c4 Symbol pollution is bad for your health 2016-06-19 09:19:16 -10:00
Jack Andersen
d2a8430746 Sightly faster CoInitialize flag 2016-06-17 10:41:39 -10:00
Jack Andersen
b231cf9104 Ensure COM is initialized for VST hosts that might not have done so 2016-06-17 10:11:35 -10:00
22610f32e9 Add missing cstring include 2016-06-16 23:10:15 -07:00
Jack Andersen
3f98ada70a Less hacky Win32 API use 2016-06-16 15:30:18 -10:00
Jack Andersen
7b9c7a4eb6 Page selection bug fixes; working drum pages 2016-06-16 12:18:17 -10:00
Jack Andersen
797908a126 Non-windows linkage fixes 2016-06-15 17:30:42 -10:00
Jack Andersen
f69af0e9af Audio buffering bug fix 2016-06-14 17:51:56 -10:00
Jack Andersen
3521d94d1c VST bug fixes, still having some odd discontinuity issues 2016-06-14 14:36:25 -10:00
Jack Andersen
7659371cb6 VST state save/restore 2016-06-13 22:04:37 -10:00
Jack Andersen
e7c7e5ffd3 VST UI logic complete, needs some audio output debugging 2016-06-13 15:54:24 -10:00
Jack Andersen
fa3007b65c wchar_t support for key path handling functions on Windows 2016-06-12 19:47:07 -10:00
Jack Andersen
e4ae1f1f88 Initial VST FilePresenter implementation 2016-06-11 18:52:40 -10:00
Jack Andersen
6f538dc19c Additional VST UI work 2016-06-10 20:40:23 -10:00
Jack Andersen
6f7a7405d7 Initial work on VST editor UI 2016-06-10 14:47:02 -10:00
Jack Andersen
fd0dd8922a Initial VST plugin implementation 2016-06-09 11:33:26 -10:00
Jack Andersen
57bb631f9b Minor variable fix 2016-06-08 15:26:29 -10:00
Jack Andersen
ad784966e9 Merge branch 'master' of https://github.com/AxioDL/amuse 2016-06-08 15:25:42 -10:00
Jack Andersen
e8c6418633 Default volume and pan CC values, MIDI controller ADSR mode 2016-06-08 15:22:18 -10:00
Jack Andersen
117d7046dd Initial working AudioUnit integration 2016-06-07 18:33:15 -10:00
Jack Andersen
f260019b89 More AudioUnit integration 2016-06-06 17:42:51 -10:00
Jack Andersen
85b6f406f1 Add Sample and SFX tables 2016-06-05 12:37:49 -10:00
Jack Andersen
168c4d3cfd Container loading bug fixes, drag-n-drop support 2016-06-04 17:11:24 -10:00
Jack Andersen
62ece61cb2 More UI bindings, metadata management 2016-06-04 12:38:55 -10:00
Jack Andersen
9ae92313e3 Work on AudioUnit data UI 2016-06-03 16:34:35 -10:00
Jack Andersen
81bd897ec1 Work on AudioUnit 2016-06-02 16:53:52 -10:00
Jack Andersen
8930e005df Integrate relevant evaluators into Voice state 2016-06-01 16:28:48 -10:00
Jack Andersen
e932539ec4 Properly advertise Sequencer tempo to voice SoundMacros 2016-06-01 11:32:48 -10:00
Jack Andersen
a047b1f6c8 Add Channel-Portamento support 2016-06-01 11:17:16 -10:00
Jack Andersen
d132b1be34 Support for effect sample-rate changes 2016-05-31 18:49:35 -10:00
84 changed files with 9704 additions and 2566 deletions

31
.clang-format Normal file
View File

@@ -0,0 +1,31 @@
---
IndentWidth: 4
ColumnLimit: 120
UseTab: Never
---
Language: Cpp
DerivePointerAlignment: false
PointerAlignment: Left
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
BreakBeforeBraces: Allman
IndentCaseLabels: false
AllowShortBlocksOnASingleLine: true
AlignOperands: true
AlignTrailingComments: true
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BreakConstructorInitializersBeforeComma: true
BreakStringLiterals: true
AlwaysBreakAfterReturnType: None
AlwaysBreakAfterDefinitionReturnType: None
AllowShortFunctionsOnASingleLine: All
Cpp11BracedListStyle: true
NamespaceIndentation: None
ReflowComments: true
BinPackArguments: true
BinPackParameters: true
SortIncludes: false
AccessModifierOffset: -4
ConstructorInitializerIndentWidth: 0
ConstructorInitializerAllOnOneLineOrOnePerLine: true

View File

@@ -6,6 +6,10 @@
<string>@APPLE_TEAM_ID@.@APPLE_BUNDLE_ID@</string>
<key>com.apple.developer.team-identifier</key>
<string>@APPLE_TEAM_ID@</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.github.axiodl.Amuse.AudioGroups</string>

View File

@@ -17,7 +17,10 @@
<connections>
<outlet property="creditsView" destination="AWS-Zd-8ha" id="O75-15-wzr"/>
<outlet property="dataOutline" destination="Vlv-Jw-A4U" id="Ggn-DC-cUY"/>
<outlet property="dataSearchField" destination="Huk-pR-ayq" id="MbL-0b-KGw"/>
<outlet property="mainWindow" destination="FDh-Mc-OFY" id="CQQ-Dm-Kv3"/>
<outlet property="removeDataButton" destination="eh8-gl-WoS" id="U9P-dt-tN5"/>
<outlet property="removeDataMenu" destination="pa3-QI-u2k" id="xnq-of-WvG"/>
<outlet property="samplesTable" destination="Frt-wZ-1ZI" id="riC-O4-JXo"/>
<outlet property="sfxTable" destination="4nw-rf-Dh4" id="ebY-ro-tqk"/>
</connections>
@@ -69,7 +72,7 @@
<items>
<menuItem title="Import…" keyEquivalent="i" id="IAo-SY-fd9">
<connections>
<action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
<action selector="importFile:" target="Y3H-Qy-a7C" id="j6G-7T-YiQ"/>
</connections>
</menuItem>
</items>
@@ -77,17 +80,15 @@
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<menu key="submenu" title="Edit" autoenablesItems="NO" id="W48-6f-4Dl">
<items>
<menuItem title="Delete" id="pa3-QI-u2k">
<menuItem title="Delete" enabled="NO" id="pa3-QI-u2k">
<string key="keyEquivalent" base64-UTF8="YES">
CA
</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
<action selector="removeDataItem:" target="Y3H-Qy-a7C" id="aAl-Ko-LrT"/>
</connections>
</menuItem>
</items>
@@ -166,128 +167,86 @@
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Alc-jx-Z3v">
<rect key="frame" x="0.0" y="-1" width="30" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="QjK-Fn-bKW"/>
<constraint firstAttribute="width" constant="30" id="4fr-kb-eaI"/>
<constraint firstAttribute="height" constant="30" id="Sib-AA-UGB"/>
</constraints>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSAddTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="1BM-r8-8er">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="importFile:" target="Y3H-Qy-a7C" id="bBJ-EO-41Y"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eh8-gl-WoS">
<rect key="frame" x="29" y="-1" width="31" height="31"/>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSRemoveTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="edx-l4-aHD">
<rect key="frame" x="29" y="-1" width="30" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="6ej-Xv-zwY"/>
</constraints>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSRemoveTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" enabled="NO" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="edx-l4-aHD">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="removeDataItem:" target="Y3H-Qy-a7C" id="hau-zt-Ozz"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mpK-AK-Otf" customClass="InactiveButton">
<rect key="frame" x="58" y="-1" width="486" height="32"/>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="JwG-gw-HOj">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mpK-AK-Otf">
<rect key="frame" x="59" y="-1" width="485" height="32"/>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="JwG-gw-HOj">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XHK-NM-ZAP">
<scrollView autohidesScrollers="YES" horizontalLineScroll="22" horizontalPageScroll="10" verticalLineScroll="22" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XHK-NM-ZAP">
<rect key="frame" x="0.0" y="28" width="544" height="339"/>
<clipView key="contentView" id="cFf-wa-ZPx">
<rect key="frame" x="1" y="23" width="542" height="315"/>
<clipView key="contentView" drawsBackground="NO" id="cFf-wa-ZPx">
<rect key="frame" x="1" y="0.0" width="542" height="338"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="jN4-UQ-VfT" viewBased="YES" indentationPerLevel="16" outlineTableColumn="DlG-iE-h1a" id="Vlv-Jw-A4U">
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" alternatingRowBackgroundColors="YES" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="22" headerView="jN4-UQ-VfT" indentationPerLevel="14" outlineTableColumn="DlG-iE-h1a" id="Vlv-Jw-A4U" customClass="DataOutlineView">
<rect key="frame" x="0.0" y="0.0" width="542" height="315"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="intercellSpacing" width="3" height="0.0"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="116" minWidth="40" maxWidth="1000" id="DlG-iE-h1a">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<tableColumn identifier="CollectionColumn" width="300" minWidth="100" maxWidth="1000" id="DlG-iE-h1a">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Collection">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="nfQ-a8-ezY">
<buttonCell key="dataCell" type="check" title="CollectionTitle" bezelStyle="regularSquare" imagePosition="left" alignment="left" lineBreakMode="truncatingTail" refusesFirstResponder="YES" allowsMixedState="YES" inset="2" id="Lpp-wh-qX7" customClass="RestrictedCheckButton">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</buttonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="1eD-qr-jb9">
<rect key="frame" x="1" y="1" width="116" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="cAp-67-fuc">
<rect key="frame" x="0.0" y="0.0" width="100" height="17"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="n5E-QL-7SS">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="cAp-67-fuc" firstAttribute="centerY" secondItem="1eD-qr-jb9" secondAttribute="centerY" id="5cE-qL-mGq"/>
<constraint firstItem="cAp-67-fuc" firstAttribute="leading" secondItem="1eD-qr-jb9" secondAttribute="leading" constant="2" id="WPE-2V-xp9"/>
<constraint firstAttribute="trailing" secondItem="cAp-67-fuc" secondAttribute="trailing" constant="18" id="tKa-Es-CKH"/>
</constraints>
<connections>
<outlet property="textField" destination="cAp-67-fuc" id="oYq-0I-143"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn width="420" minWidth="40" maxWidth="1000" id="alh-ut-BoX">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<tableColumn identifier="DetailsColumn" width="236" minWidth="40" maxWidth="1000" id="alh-ut-BoX">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Details">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="ywm-jc-RPk">
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" allowsUndo="NO" title="Text Cell" id="ywm-jc-RPk">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" name="disabledControlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView misplaced="YES" id="REu-Qt-X84">
<rect key="frame" x="120" y="1" width="420" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="zPc-LM-WxA">
<rect key="frame" x="0.0" y="0.0" width="100" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="96" id="hKb-R5-AZg"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="wy9-p6-MVa">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="zPc-LM-WxA" firstAttribute="centerY" secondItem="REu-Qt-X84" secondAttribute="centerY" id="HWb-8H-JTZ"/>
<constraint firstAttribute="trailing" secondItem="zPc-LM-WxA" secondAttribute="trailing" constant="-58" id="pfE-FE-k76"/>
<constraint firstItem="zPc-LM-WxA" firstAttribute="leading" secondItem="REu-Qt-X84" secondAttribute="leading" constant="2" id="qPe-rp-KwI"/>
</constraints>
<connections>
<outlet property="textField" destination="zPc-LM-WxA" id="XMS-Lt-7aA"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</outlineView>
</subviews>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="ZGl-Ws-7HQ">
<rect key="frame" x="1" y="19" width="158" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="cwa-Vo-ZEb">
<rect key="frame" x="159" y="23" width="15" height="-4"/>
<rect key="frame" x="159" y="23" width="15" height="4"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" id="jN4-UQ-VfT">
@@ -296,36 +255,34 @@
</tableHeaderView>
</scrollView>
<searchField wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Huk-pR-ayq">
<rect key="frame" x="65" y="4" width="474" height="22"/>
<constraints>
<constraint firstAttribute="height" constant="22" id="bta-rr-Rjn"/>
</constraints>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="u9E-Ld-d6I">
<rect key="frame" x="63" y="3" width="477" height="22"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" sendsSearchStringImmediately="YES" id="u9E-Ld-d6I">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<action selector="filterDataOutline:" target="Y3H-Qy-a7C" id="D8g-Qv-0QG"/>
</connections>
</searchField>
</subviews>
<constraints>
<constraint firstItem="XHK-NM-ZAP" firstAttribute="top" secondItem="GKx-9K-n29" secondAttribute="top" id="11d-cK-KTn"/>
<constraint firstAttribute="trailing" secondItem="Huk-pR-ayq" secondAttribute="trailing" constant="5" id="1Bp-rN-FgN"/>
<constraint firstAttribute="bottom" secondItem="Huk-pR-ayq" secondAttribute="bottom" constant="4" id="3Px-xo-dAv"/>
<constraint firstItem="XHK-NM-ZAP" firstAttribute="trailing" secondItem="mpK-AK-Otf" secondAttribute="trailing" id="5i2-xa-qrD"/>
<constraint firstItem="Alc-jx-Z3v" firstAttribute="baseline" secondItem="eh8-gl-WoS" secondAttribute="baseline" id="8uY-Ir-nmM"/>
<constraint firstAttribute="trailing" secondItem="XHK-NM-ZAP" secondAttribute="trailing" id="9gd-HE-7uf"/>
<constraint firstItem="Huk-pR-ayq" firstAttribute="leading" secondItem="eh8-gl-WoS" secondAttribute="trailing" constant="5" id="Aav-lf-aM8"/>
<constraint firstItem="eh8-gl-WoS" firstAttribute="baseline" secondItem="mpK-AK-Otf" secondAttribute="baseline" id="Inf-xH-Xiu"/>
<constraint firstItem="XHK-NM-ZAP" firstAttribute="leading" secondItem="Alc-jx-Z3v" secondAttribute="leading" id="Qwp-2y-pUU"/>
<constraint firstItem="Huk-pR-ayq" firstAttribute="top" secondItem="XHK-NM-ZAP" secondAttribute="bottom" constant="2" id="S29-sA-pR1"/>
<constraint firstItem="mpK-AK-Otf" firstAttribute="centerY" secondItem="Huk-pR-ayq" secondAttribute="centerY" id="Y1b-wA-j5b"/>
<constraint firstItem="Alc-jx-Z3v" firstAttribute="centerY" secondItem="eh8-gl-WoS" secondAttribute="centerY" id="YXF-DS-Qzu"/>
<constraint firstItem="mpK-AK-Otf" firstAttribute="leading" secondItem="Alc-jx-Z3v" secondAttribute="trailing" constant="29" id="bF7-N0-Fff"/>
<constraint firstItem="eh8-gl-WoS" firstAttribute="leading" secondItem="GKx-9K-n29" secondAttribute="leading" constant="29" id="flM-s6-dSI"/>
<constraint firstAttribute="bottom" secondItem="Alc-jx-Z3v" secondAttribute="bottom" id="iKi-2E-TFe"/>
<constraint firstItem="XHK-NM-ZAP" firstAttribute="leading" secondItem="GKx-9K-n29" secondAttribute="leading" id="lRg-3u-LsT"/>
<constraint firstItem="Alc-jx-Z3v" firstAttribute="firstBaseline" secondItem="mpK-AK-Otf" secondAttribute="firstBaseline" id="pp9-Zc-noQ"/>
<constraint firstItem="Huk-pR-ayq" firstAttribute="centerX" secondItem="mpK-AK-Otf" secondAttribute="centerX" id="xqj-LS-MnT"/>
<constraint firstAttribute="trailing" secondItem="XHK-NM-ZAP" secondAttribute="trailing" id="1pE-DU-h1S"/>
<constraint firstItem="eh8-gl-WoS" firstAttribute="firstBaseline" secondItem="mpK-AK-Otf" secondAttribute="firstBaseline" id="3Y4-En-NmZ"/>
<constraint firstItem="XHK-NM-ZAP" firstAttribute="leading" secondItem="GKx-9K-n29" secondAttribute="leading" id="B0v-IP-0Ic"/>
<constraint firstAttribute="bottom" secondItem="Alc-jx-Z3v" secondAttribute="bottom" id="KIX-cn-mNM"/>
<constraint firstItem="Alc-jx-Z3v" firstAttribute="firstBaseline" secondItem="eh8-gl-WoS" secondAttribute="firstBaseline" id="O7r-da-zgD"/>
<constraint firstItem="XHK-NM-ZAP" firstAttribute="leading" secondItem="Alc-jx-Z3v" secondAttribute="leading" id="QqG-Ek-LI8"/>
<constraint firstAttribute="bottom" secondItem="Huk-pR-ayq" secondAttribute="bottom" constant="3" id="R5r-Tf-sig"/>
<constraint firstItem="XHK-NM-ZAP" firstAttribute="trailing" secondItem="mpK-AK-Otf" secondAttribute="trailing" id="Xa6-xa-785"/>
<constraint firstItem="Alc-jx-Z3v" firstAttribute="baseline" secondItem="eh8-gl-WoS" secondAttribute="baseline" id="ZPG-ox-LD0"/>
<constraint firstItem="eh8-gl-WoS" firstAttribute="baseline" secondItem="mpK-AK-Otf" secondAttribute="baseline" id="ax4-ux-lgm"/>
<constraint firstItem="Huk-pR-ayq" firstAttribute="top" secondItem="XHK-NM-ZAP" secondAttribute="bottom" constant="3" id="iG2-ku-8X4"/>
<constraint firstItem="XHK-NM-ZAP" firstAttribute="top" secondItem="GKx-9K-n29" secondAttribute="top" id="mp7-MY-xub"/>
<constraint firstItem="Huk-pR-ayq" firstAttribute="centerX" secondItem="mpK-AK-Otf" secondAttribute="centerX" id="uB6-uO-1Wh"/>
<constraint firstItem="mpK-AK-Otf" firstAttribute="leading" secondItem="Alc-jx-Z3v" secondAttribute="trailing" constant="28" id="vkC-Xs-dXI"/>
<constraint firstItem="eh8-gl-WoS" firstAttribute="leading" secondItem="GKx-9K-n29" secondAttribute="leading" constant="29" id="wTd-sN-gen"/>
<constraint firstItem="Huk-pR-ayq" firstAttribute="leading" secondItem="eh8-gl-WoS" secondAttribute="trailing" constant="4" id="xpV-H6-3B5"/>
</constraints>
</view>
</tabViewItem>
@@ -334,74 +291,39 @@
<rect key="frame" x="0.0" y="0.0" width="544" height="367"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KBz-ap-rBr">
<rect key="frame" x="0.0" y="-1" width="30" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="JPF-XW-Zrm"/>
</constraints>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSAddTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="eb5-rf-bFE">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="3Ss-Wh-6dp">
<rect key="frame" x="29" y="-1" width="31" height="31"/>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSRemoveTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="xGw-iM-KTu">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="AnJ-nZ-Zfp">
<rect key="frame" x="59" y="-1" width="485" height="32"/>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="SjA-P5-3He">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<searchField wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oAG-fe-WMm">
<rect key="frame" x="65" y="4" width="474" height="22"/>
<constraints>
<constraint firstAttribute="height" constant="22" id="NcP-aU-Wkl"/>
</constraints>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="gRb-aR-GFw">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
</searchField>
<scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4I7-lP-08K">
<rect key="frame" x="0.0" y="29" width="544" height="338"/>
<clipView key="contentView" id="vf9-4W-0Zi">
<rect key="frame" x="1" y="23" width="542" height="314"/>
<rect key="frame" x="0.0" y="0.0" width="544" height="367"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="vf9-4W-0Zi">
<rect key="frame" x="1" y="0.0" width="542" height="366"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="9pb-bl-sSa" viewBased="YES" id="4nw-rf-Dh4">
<rect key="frame" x="0.0" y="0.0" width="542" height="314"/>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="9pb-bl-sSa" viewBased="YES" id="4nw-rf-Dh4">
<rect key="frame" x="0.0" y="0.0" width="542" height="343"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="116" minWidth="40" maxWidth="1000" id="lNw-az-m8v">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<tableColumn identifier="SFXIDColumn" width="116" minWidth="40" maxWidth="1000" id="lNw-az-m8v">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="ID">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="RNG-D2-ueb">
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" allowsUndo="NO" title="Text Cell" id="RNG-D2-ueb">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="Bld-o1-jXJ">
<tableCellView id="XFL-Ls-02w">
<rect key="frame" x="1" y="1" width="116" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="ZZj-P8-9ts">
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="2PL-fA-QSc">
<rect key="frame" x="0.0" y="0.0" width="100" height="17"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="TAi-Dq-tMT">
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Table View Cell" id="xYV-rG-09O">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@@ -409,39 +331,39 @@
</textField>
</subviews>
<constraints>
<constraint firstItem="ZZj-P8-9ts" firstAttribute="leading" secondItem="Bld-o1-jXJ" secondAttribute="leading" constant="2" id="AY8-Jl-YdX"/>
<constraint firstAttribute="trailing" secondItem="ZZj-P8-9ts" secondAttribute="trailing" constant="18" id="IDi-fM-pVu"/>
<constraint firstItem="ZZj-P8-9ts" firstAttribute="centerY" secondItem="Bld-o1-jXJ" secondAttribute="centerY" id="LPm-yn-FQX"/>
<constraint firstAttribute="trailing" secondItem="2PL-fA-QSc" secondAttribute="trailing" constant="18" id="6Rm-bD-4WK"/>
<constraint firstItem="2PL-fA-QSc" firstAttribute="centerY" secondItem="XFL-Ls-02w" secondAttribute="centerY" id="enx-iG-2oB"/>
<constraint firstItem="2PL-fA-QSc" firstAttribute="leading" secondItem="XFL-Ls-02w" secondAttribute="leading" constant="2" id="qeB-0G-dXO"/>
</constraints>
<connections>
<outlet property="textField" destination="ZZj-P8-9ts" id="39a-MV-EbQ"/>
<outlet property="textField" destination="2PL-fA-QSc" id="RpZ-Uw-Grp"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn width="420" minWidth="40" maxWidth="1000" id="cir-wf-zZH">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<tableColumn identifier="SFXDetailsColumn" width="420" minWidth="40" maxWidth="1000" id="cir-wf-zZH">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Details">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="Emw-gX-CND">
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" allowsUndo="NO" title="Text Cell" id="Emw-gX-CND">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="MSH-I7-WP0">
<tableCellView id="gVd-1Z-stf">
<rect key="frame" x="120" y="1" width="420" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="sZh-af-mID">
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="w19-X3-abH">
<rect key="frame" x="0.0" y="0.0" width="100" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="96" id="Olp-J5-pIs"/>
<constraint firstAttribute="width" constant="96" id="Gae-0I-VGs"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="g74-lI-1lW">
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Table View Cell" id="PxJ-bH-77o">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@@ -449,11 +371,11 @@
</textField>
</subviews>
<constraints>
<constraint firstItem="sZh-af-mID" firstAttribute="centerY" secondItem="MSH-I7-WP0" secondAttribute="centerY" id="TR4-l3-M5x"/>
<constraint firstItem="sZh-af-mID" firstAttribute="leading" secondItem="MSH-I7-WP0" secondAttribute="leading" constant="2" id="txo-P4-p8A"/>
<constraint firstItem="w19-X3-abH" firstAttribute="centerY" secondItem="gVd-1Z-stf" secondAttribute="centerY" id="71k-IB-NPp"/>
<constraint firstItem="w19-X3-abH" firstAttribute="leading" secondItem="gVd-1Z-stf" secondAttribute="leading" constant="2" id="rX3-HS-TGq"/>
</constraints>
<connections>
<outlet property="textField" destination="sZh-af-mID" id="fET-Gy-kz6"/>
<outlet property="textField" destination="w19-X3-abH" id="BS3-ol-wil"/>
</connections>
</tableCellView>
</prototypeCellViews>
@@ -461,13 +383,13 @@
</tableColumns>
</tableView>
</subviews>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="uOf-Jb-IVN">
<rect key="frame" x="1" y="119" width="223" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="AvT-7G-lW8">
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="AvT-7G-lW8">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
@@ -478,24 +400,10 @@
</scrollView>
</subviews>
<constraints>
<constraint firstItem="oAG-fe-WMm" firstAttribute="top" secondItem="4I7-lP-08K" secondAttribute="bottom" constant="3" id="2Ga-AL-Moz"/>
<constraint firstItem="AnJ-nZ-Zfp" firstAttribute="leading" secondItem="KBz-ap-rBr" secondAttribute="trailing" constant="29" id="3sF-mo-yXM"/>
<constraint firstItem="4I7-lP-08K" firstAttribute="leading" secondItem="KBz-ap-rBr" secondAttribute="leading" id="BHY-wE-9xs"/>
<constraint firstItem="KBz-ap-rBr" firstAttribute="centerY" secondItem="3Ss-Wh-6dp" secondAttribute="centerY" id="CqR-JP-dag"/>
<constraint firstItem="4I7-lP-08K" firstAttribute="top" secondItem="qIJ-rh-cnU" secondAttribute="top" id="DKu-qi-8Zi"/>
<constraint firstItem="3Ss-Wh-6dp" firstAttribute="leading" secondItem="qIJ-rh-cnU" secondAttribute="leading" constant="29" id="GdU-tv-IPD"/>
<constraint firstItem="KBz-ap-rBr" firstAttribute="firstBaseline" secondItem="AnJ-nZ-Zfp" secondAttribute="firstBaseline" id="HTn-NF-EgY"/>
<constraint firstAttribute="bottom" secondItem="KBz-ap-rBr" secondAttribute="bottom" id="IOx-sd-Ta0"/>
<constraint firstItem="3Ss-Wh-6dp" firstAttribute="baseline" secondItem="AnJ-nZ-Zfp" secondAttribute="baseline" id="Ipx-yq-qlA"/>
<constraint firstAttribute="trailing" secondItem="oAG-fe-WMm" secondAttribute="trailing" constant="5" id="QMV-Df-EKc"/>
<constraint firstItem="3Ss-Wh-6dp" firstAttribute="top" secondItem="4I7-lP-08K" secondAttribute="bottom" id="Qj2-aa-7cc"/>
<constraint firstItem="KBz-ap-rBr" firstAttribute="baseline" secondItem="3Ss-Wh-6dp" secondAttribute="baseline" id="W08-Un-JWq"/>
<constraint firstItem="4I7-lP-08K" firstAttribute="leading" secondItem="qIJ-rh-cnU" secondAttribute="leading" id="WhF-Wb-PFB"/>
<constraint firstItem="AnJ-nZ-Zfp" firstAttribute="centerY" secondItem="oAG-fe-WMm" secondAttribute="centerY" id="Zox-TK-kuL"/>
<constraint firstItem="oAG-fe-WMm" firstAttribute="leading" secondItem="3Ss-Wh-6dp" secondAttribute="trailing" constant="5" id="aJS-aa-4eq"/>
<constraint firstItem="4I7-lP-08K" firstAttribute="trailing" secondItem="AnJ-nZ-Zfp" secondAttribute="trailing" id="ss6-VM-Yi5"/>
<constraint firstItem="oAG-fe-WMm" firstAttribute="centerX" secondItem="AnJ-nZ-Zfp" secondAttribute="centerX" id="z9w-zt-20K"/>
<constraint firstAttribute="trailing" secondItem="4I7-lP-08K" secondAttribute="trailing" id="zdJ-hh-V8T"/>
<constraint firstItem="4I7-lP-08K" firstAttribute="leading" secondItem="qIJ-rh-cnU" secondAttribute="leading" id="ofT-nM-Uob"/>
<constraint firstAttribute="trailing" secondItem="4I7-lP-08K" secondAttribute="trailing" id="weV-m7-oie"/>
<constraint firstAttribute="bottom" secondItem="4I7-lP-08K" secondAttribute="bottom" id="yOi-OG-adc"/>
<constraint firstItem="4I7-lP-08K" firstAttribute="top" secondItem="qIJ-rh-cnU" secondAttribute="top" id="ykR-eE-XY3"/>
</constraints>
</view>
</tabViewItem>
@@ -504,74 +412,39 @@
<rect key="frame" x="0.0" y="0.0" width="544" height="367"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YfR-tZ-Cqm">
<rect key="frame" x="0.0" y="-1" width="30" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="LNb-6x-6BN"/>
</constraints>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSAddTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="HJf-MA-3Rg">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5lG-v1-xlR">
<rect key="frame" x="29" y="-1" width="31" height="31"/>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSRemoveTemplate" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Uhf-Ik-kst">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="meL-4m-liG">
<rect key="frame" x="59" y="-1" width="485" height="32"/>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="LJN-Sj-A70">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<searchField wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8fs-yS-j3u">
<rect key="frame" x="65" y="4" width="474" height="22"/>
<constraints>
<constraint firstAttribute="height" constant="22" id="e8E-TS-Dd8"/>
</constraints>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="qxx-lO-6Nw">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
</searchField>
<scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aIh-No-CEz">
<rect key="frame" x="0.0" y="29" width="544" height="338"/>
<clipView key="contentView" id="MzV-rp-Rp8">
<rect key="frame" x="1" y="23" width="542" height="314"/>
<rect key="frame" x="0.0" y="1" width="544" height="366"/>
<clipView key="contentView" drawsBackground="NO" id="MzV-rp-Rp8">
<rect key="frame" x="1" y="0.0" width="542" height="365"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="fQK-tA-ezw" viewBased="YES" id="Frt-wZ-1ZI">
<rect key="frame" x="0.0" y="0.0" width="542" height="314"/>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" headerView="fQK-tA-ezw" viewBased="YES" id="Frt-wZ-1ZI">
<rect key="frame" x="0.0" y="0.0" width="542" height="342"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="116" minWidth="40" maxWidth="1000" id="RF7-J2-oFq">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<tableColumn identifier="SampleIDColumn" width="116" minWidth="40" maxWidth="1000" id="RF7-J2-oFq">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="ID">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="O7U-y5-ovx">
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" allowsUndo="NO" title="Text Cell" id="O7U-y5-ovx">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="ffN-yY-DyX">
<tableCellView id="nAU-e4-oze">
<rect key="frame" x="1" y="1" width="116" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="DUu-Bo-MP0">
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tZr-rU-2Pd">
<rect key="frame" x="0.0" y="0.0" width="100" height="17"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="N99-9O-D40">
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Table View Cell" id="AJI-LY-8Wf">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@@ -579,39 +452,39 @@
</textField>
</subviews>
<constraints>
<constraint firstItem="DUu-Bo-MP0" firstAttribute="leading" secondItem="ffN-yY-DyX" secondAttribute="leading" constant="2" id="2he-p0-Rsl"/>
<constraint firstItem="DUu-Bo-MP0" firstAttribute="centerY" secondItem="ffN-yY-DyX" secondAttribute="centerY" id="YDQ-Wo-FVN"/>
<constraint firstAttribute="trailing" secondItem="DUu-Bo-MP0" secondAttribute="trailing" constant="18" id="zUo-KU-6TP"/>
<constraint firstItem="tZr-rU-2Pd" firstAttribute="leading" secondItem="nAU-e4-oze" secondAttribute="leading" constant="2" id="I9j-sV-Ivw"/>
<constraint firstAttribute="trailing" secondItem="tZr-rU-2Pd" secondAttribute="trailing" constant="18" id="UmQ-yh-qIc"/>
<constraint firstItem="tZr-rU-2Pd" firstAttribute="centerY" secondItem="nAU-e4-oze" secondAttribute="centerY" id="qfU-4d-RVC"/>
</constraints>
<connections>
<outlet property="textField" destination="DUu-Bo-MP0" id="KVw-BO-rFN"/>
<outlet property="textField" destination="tZr-rU-2Pd" id="qXZ-eW-rn8"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn width="420" minWidth="40" maxWidth="1000" id="MqW-cb-qx7">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<tableColumn identifier="SampleDetailsColumn" width="420" minWidth="40" maxWidth="1000" id="MqW-cb-qx7">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Details">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="FTl-PH-ryo">
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" allowsUndo="NO" title="Text Cell" id="FTl-PH-ryo">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="Syu-AI-iQO">
<tableCellView id="Td7-KD-77Y">
<rect key="frame" x="120" y="1" width="420" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="42J-2Q-ggr">
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="aa7-sT-ACB">
<rect key="frame" x="0.0" y="0.0" width="100" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="96" id="0Ia-Hu-xUz"/>
<constraint firstAttribute="width" constant="96" id="NiS-2g-Lrp"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="5ml-w5-V8a">
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Table View Cell" id="bTT-92-guS">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@@ -619,11 +492,11 @@
</textField>
</subviews>
<constraints>
<constraint firstItem="42J-2Q-ggr" firstAttribute="centerY" secondItem="Syu-AI-iQO" secondAttribute="centerY" id="6ea-XB-hmz"/>
<constraint firstItem="42J-2Q-ggr" firstAttribute="leading" secondItem="Syu-AI-iQO" secondAttribute="leading" constant="2" id="9Bv-2m-jNN"/>
<constraint firstItem="aa7-sT-ACB" firstAttribute="leading" secondItem="Td7-KD-77Y" secondAttribute="leading" constant="2" id="CMk-N2-FLW"/>
<constraint firstItem="aa7-sT-ACB" firstAttribute="centerY" secondItem="Td7-KD-77Y" secondAttribute="centerY" id="Wi6-jL-iGR"/>
</constraints>
<connections>
<outlet property="textField" destination="42J-2Q-ggr" id="pLh-XE-CfB"/>
<outlet property="textField" destination="aa7-sT-ACB" id="Cbv-CJ-AQD"/>
</connections>
</tableCellView>
</prototypeCellViews>
@@ -631,13 +504,13 @@
</tableColumns>
</tableView>
</subviews>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="MZB-ZS-SbU">
<rect key="frame" x="1" y="119" width="223" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="X6u-4b-0Ia">
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="X6u-4b-0Ia">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
@@ -648,24 +521,10 @@
</scrollView>
</subviews>
<constraints>
<constraint firstItem="YfR-tZ-Cqm" firstAttribute="baseline" secondItem="5lG-v1-xlR" secondAttribute="baseline" id="0yW-5L-6xL"/>
<constraint firstItem="aIh-No-CEz" firstAttribute="trailing" secondItem="meL-4m-liG" secondAttribute="trailing" id="1KY-Rr-WjB"/>
<constraint firstItem="aIh-No-CEz" firstAttribute="top" secondItem="A6d-Tc-9Ch" secondAttribute="top" id="2o4-JJ-xTa"/>
<constraint firstAttribute="trailing" secondItem="aIh-No-CEz" secondAttribute="trailing" id="4cN-48-TIp"/>
<constraint firstItem="meL-4m-liG" firstAttribute="leading" secondItem="YfR-tZ-Cqm" secondAttribute="trailing" constant="29" id="B2C-oR-2je"/>
<constraint firstItem="aIh-No-CEz" firstAttribute="leading" secondItem="A6d-Tc-9Ch" secondAttribute="leading" id="CgO-aA-i0w"/>
<constraint firstAttribute="bottom" secondItem="YfR-tZ-Cqm" secondAttribute="bottom" id="KXP-Wn-AEZ"/>
<constraint firstItem="8fs-yS-j3u" firstAttribute="top" secondItem="aIh-No-CEz" secondAttribute="bottom" constant="3" id="Ras-NE-DOd"/>
<constraint firstItem="aIh-No-CEz" firstAttribute="leading" secondItem="YfR-tZ-Cqm" secondAttribute="leading" id="WTG-q1-AbI"/>
<constraint firstItem="5lG-v1-xlR" firstAttribute="leading" secondItem="A6d-Tc-9Ch" secondAttribute="leading" constant="29" id="Z81-xl-EM4"/>
<constraint firstItem="YfR-tZ-Cqm" firstAttribute="centerY" secondItem="5lG-v1-xlR" secondAttribute="centerY" id="hXw-Al-NS4"/>
<constraint firstItem="8fs-yS-j3u" firstAttribute="leading" secondItem="5lG-v1-xlR" secondAttribute="trailing" constant="5" id="kCf-4j-RgY"/>
<constraint firstItem="YfR-tZ-Cqm" firstAttribute="firstBaseline" secondItem="meL-4m-liG" secondAttribute="firstBaseline" id="kiM-MZ-94m"/>
<constraint firstItem="meL-4m-liG" firstAttribute="centerY" secondItem="8fs-yS-j3u" secondAttribute="centerY" id="lO3-6v-BmR"/>
<constraint firstItem="5lG-v1-xlR" firstAttribute="baseline" secondItem="meL-4m-liG" secondAttribute="baseline" id="lWj-xA-tcg"/>
<constraint firstItem="5lG-v1-xlR" firstAttribute="top" secondItem="aIh-No-CEz" secondAttribute="bottom" id="rwW-Ez-40s"/>
<constraint firstItem="8fs-yS-j3u" firstAttribute="centerX" secondItem="meL-4m-liG" secondAttribute="centerX" id="tHD-vu-oEw"/>
<constraint firstAttribute="trailing" secondItem="8fs-yS-j3u" secondAttribute="trailing" constant="5" id="vjS-qe-dVe"/>
<constraint firstAttribute="trailing" secondItem="aIh-No-CEz" secondAttribute="trailing" id="DYN-OA-95T"/>
<constraint firstItem="aIh-No-CEz" firstAttribute="top" secondItem="A6d-Tc-9Ch" secondAttribute="top" id="LVF-DP-KHg"/>
<constraint firstItem="aIh-No-CEz" firstAttribute="leading" secondItem="A6d-Tc-9Ch" secondAttribute="leading" id="SF5-ox-STl"/>
<constraint firstItem="aIh-No-CEz" firstAttribute="centerY" secondItem="A6d-Tc-9Ch" secondAttribute="centerY" id="xg8-Qp-9kO"/>
</constraints>
</view>
</tabViewItem>
@@ -685,10 +544,10 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<size key="minSize" width="527" height="365"/>
<size key="maxSize" width="541" height="10000000"/>
<size key="maxSize" width="544" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="527" height="365"/>
<size key="maxSize" width="541" height="10000000"/>
<size key="maxSize" width="544" height="10000000"/>
</textView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
@@ -715,10 +574,10 @@
</tabView>
</subviews>
<constraints>
<constraint firstItem="Kht-FW-Kf6" firstAttribute="leading" secondItem="9Rk-3e-xVS" secondAttribute="leading" id="EaK-L0-wht"/>
<constraint firstAttribute="trailing" secondItem="Kht-FW-Kf6" secondAttribute="trailing" id="JMc-UC-ORE"/>
<constraint firstItem="Kht-FW-Kf6" firstAttribute="top" secondItem="9Rk-3e-xVS" secondAttribute="top" id="joC-pf-f1v"/>
<constraint firstAttribute="bottom" secondItem="Kht-FW-Kf6" secondAttribute="bottom" id="yTz-NI-FWd"/>
<constraint firstAttribute="trailing" secondItem="Kht-FW-Kf6" secondAttribute="trailing" id="QKQ-cu-CcX"/>
<constraint firstAttribute="bottom" secondItem="Kht-FW-Kf6" secondAttribute="bottom" id="aJq-Lv-Drp"/>
<constraint firstItem="Kht-FW-Kf6" firstAttribute="top" secondItem="9Rk-3e-xVS" secondAttribute="top" id="cHb-PJ-LRW"/>
<constraint firstItem="Kht-FW-Kf6" firstAttribute="leading" secondItem="9Rk-3e-xVS" secondAttribute="leading" id="qWs-qF-70Y"/>
</constraints>
</view>
<toolbar key="toolbar" implicitIdentifier="B4A5636B-DFED-420B-B2FF-540D729FD569" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="r4I-P0-82j">

View File

@@ -0,0 +1,61 @@
#ifndef __AMUSE_AUDIOUNIT_CONTAININGAPP_HPP__
#define __AMUSE_AUDIOUNIT_CONTAININGAPP_HPP__
#import <AppKit/AppKit.h>
#import "AudioGroupFilePresenter.hpp"
#include <amuse/BooBackend.hpp>
#include <boo/audiodev/IAudioVoiceEngine.hpp>
@interface DataOutlineView : NSOutlineView
{
@public
IBOutlet NSButton* removeDataButton;
IBOutlet NSMenuItem* deleteMenuItem;
}
@end
@interface SamplesTableController : NSObject <NSTableViewDataSource, NSTableViewDelegate>
{
AudioGroupFilePresenter* presenter;
}
- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present;
@end
@interface SFXTableController : NSObject <NSTableViewDataSource, NSTableViewDelegate>
{
AudioGroupFilePresenter* presenter;
}
- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present;
@end
@interface AppDelegate : NSObject <NSApplicationDelegate, AudioGroupClient>
{
IBOutlet NSWindow* mainWindow;
IBOutlet NSOutlineView* dataOutline;
IBOutlet NSSearchField* dataSearchField;
IBOutlet NSTableView* sfxTable;
IBOutlet NSTableView* samplesTable;
IBOutlet NSTextView* creditsView;
IBOutlet NSButton* removeDataButton;
IBOutlet NSMenuItem* removeDataMenu;
AudioGroupFilePresenter* groupFilePresenter;
SamplesTableController* samplesController;
SFXTableController* sfxController;
@public
std::unique_ptr<boo::IAudioVoiceEngine> booEngine;
std::experimental::optional<amuse::BooBackendVoiceAllocator> amuseAllocator;
std::experimental::optional<amuse::Engine> amuseEngine;
std::shared_ptr<amuse::Voice> activeSFXVox;
}
- (BOOL)importURL:(NSURL*)url;
- (void)outlineView:(DataOutlineView*)ov selectionChanged:(id)item;
- (void)reloadTables;
- (void)startSFX:(int)sfxId;
- (void)startSample:(int)sampId;
@end
#endif // __AMUSE_AUDIOUNIT_CONTAININGAPP_HPP__

View File

@@ -2,30 +2,47 @@
#import <AudioUnit/AudioUnit.h>
#import <CoreAudioKit/AUViewController.h>
#import "AudioUnitViewController.hpp"
#import "AmuseContainingApp.hpp"
#include <amuse/amuse.hpp>
@interface MainView : NSView
@class DataOutlineController;
@class SamplesTableController;
@class SFXTableController;
/* Blocks mousedown events (so button may be used as a visual element only) */
@interface InactiveButton : NSButton {}
@end
@implementation InactiveButton
- (void)mouseDown:(NSEvent *)theEvent {}
@end
/* Restricts mousedown to checkbox */
@interface RestrictedCheckButton : NSButtonCell {}
@end
@implementation RestrictedCheckButton
- (NSCellHitResult)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView
{
AudioUnitViewController* amuseVC;
NSRect restrictFrame = cellFrame;
restrictFrame.size.width = 22;
if (NSPointInRect([controlView convertPoint:[event locationInWindow] fromView:nil], restrictFrame))
return NSCellHitTrackableArea;
return NSCellHitNone;
}
@end
@implementation MainView
@implementation DataOutlineView
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
[self registerForDraggedTypes:@[NSURLPboardType]];
return self;
}
- (id)initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
if (!self)
return nil;
amuseVC = [[AudioUnitViewController alloc] initWithNibName:nil bundle:nil];
[self addSubview:amuseVC.view];
[self registerForDraggedTypes:@[NSURLPboardType]];
return self;
}
- (BOOL)translatesAutoresizingMaskIntoConstraints
{
return NO;
}
@end
@interface MainTabView : NSTabView
@@ -55,22 +72,153 @@
}
@end
@interface AppDelegate : NSObject <NSApplicationDelegate>
@implementation SamplesTableController
- (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView
{
IBOutlet NSWindow* mainWindow;
IBOutlet NSOutlineView* dataOutline;
IBOutlet NSTableView* sfxTable;
IBOutlet NSTableView* samplesTable;
IBOutlet NSTextView* creditsView;
return presenter->m_sampleTableData.size();
}
- (NSView*)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
if (presenter->m_sampleTableData.size() <= row)
return nil;
NSTableCellView* view = [tableView makeViewWithIdentifier:@"SampleIDColumn" owner:self];
AudioGroupSampleToken* sampToken = presenter->m_sampleTableData[row];
if ([tableColumn.identifier isEqualToString:@"SampleIDColumn"])
view.textField.attributedStringValue = sampToken->m_name;
else if ([tableColumn.identifier isEqualToString:@"SampleDetailsColumn"])
view.textField.stringValue = @"";
else
view.textField.attributedStringValue = sampToken->m_name;
return view;
}
- (BOOL)tableView:(NSTableView *)tableView isGroupRow:(NSInteger)row
{
if (presenter->m_sampleTableData.size() <= row)
return NO;
AudioGroupSampleToken* sampToken = presenter->m_sampleTableData[row];
if (!sampToken->m_sample)
return YES;
return NO;
}
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
{
if (presenter->m_sampleTableData.size() <= row)
return NO;
AudioGroupSampleToken* sampToken = presenter->m_sampleTableData[row];
if (!sampToken->m_sample)
return NO;
return YES;
}
- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present
{
self = [super init];
presenter = present;
return self;
}
@end
@implementation SFXTableController
- (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView
{
return presenter->m_sfxTableData.size();
}
- (NSView*)tableView:(NSTableView *)tableView viewForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
if (presenter->m_sfxTableData.size() <= row)
return nil;
NSTableCellView* view = [tableView makeViewWithIdentifier:@"SFXIDColumn" owner:self];
AudioGroupSFXToken* sfxToken = presenter->m_sfxTableData[row];
if ([tableColumn.identifier isEqualToString:@"SFXIDColumn"])
view.textField.attributedStringValue = sfxToken->m_name;
else if ([tableColumn.identifier isEqualToString:@"SFXDetailsColumn"])
view.textField.stringValue = @"";
else
view.textField.attributedStringValue = sfxToken->m_name;
return view;
}
- (BOOL)tableView:(NSTableView *)tableView isGroupRow:(NSInteger)row
{
if (presenter->m_sfxTableData.size() <= row)
return NO;
AudioGroupSFXToken* sfxToken = presenter->m_sfxTableData[row];
if (!sfxToken->m_sfx)
return YES;
return NO;
}
- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row
{
if (presenter->m_sfxTableData.size() <= row)
return NO;
AudioGroupSFXToken* sfxToken = presenter->m_sfxTableData[row];
if (!sfxToken->m_sfx)
return NO;
return YES;
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSTableView* table = notification.object;
NSInteger row = table.selectedRow;
if (presenter->m_sfxTableData.size() <= row)
return;
AudioGroupSFXToken* sfxToken = presenter->m_sfxTableData[row];
AppDelegate* delegate = (AppDelegate*)NSApp.delegate;
[delegate startSFX:sfxToken->m_loadId];
}
- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present
{
self = [super init];
presenter = present;
return self;
}
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification*)notification
- (void)applicationWillFinishLaunching:(NSNotification*)notification
{
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
booEngine = boo::NewAudioVoiceEngine();
amuseAllocator.emplace(*booEngine);
amuseEngine.emplace(*amuseAllocator);
[mainWindow.toolbar setSelectedItemIdentifier:@"DataTab"];
groupFilePresenter = [[AudioGroupFilePresenter alloc] initWithAudioGroupClient:self];
dataOutline.dataSource = groupFilePresenter;
dataOutline.delegate = groupFilePresenter;
[dataOutline reloadItem:nil reloadChildren:YES];
samplesController = [[SamplesTableController alloc] initWithAudioGroupPresenter:groupFilePresenter];
samplesTable.dataSource = samplesController;
samplesTable.delegate = samplesController;
[samplesTable reloadData];
sfxController = [[SFXTableController alloc] initWithAudioGroupPresenter:groupFilePresenter];
sfxTable.dataSource = sfxController;
sfxTable.delegate = sfxController;
[sfxTable reloadData];
[NSTimer scheduledTimerWithTimeInterval:1.0 / 60.0 target:self selector:@selector(pumpTimer:) userInfo:nil repeats:YES];
}
- (void)pumpTimer:(NSTimer*)timer
{
amuseEngine->pumpEngine();
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
@@ -78,9 +226,88 @@
return YES;
}
- (IBAction)quitApp:(id)sender
- (BOOL)importURL:(NSURL*)url
{
[NSApp terminate:sender];
amuse::ContainerRegistry::Type containerType;
std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>> data =
amuse::ContainerRegistry::LoadContainer(url.path.UTF8String, containerType);
if (data.empty())
{
NSString* err = [NSString stringWithFormat:@"Unable to load Audio Groups from %s", url.path.UTF8String];
NSAlert* alert = [[NSAlert alloc] init];
alert.informativeText = err;
alert.messageText = @"Invalid Data File";
[alert runModal];
return false;
}
std::string name(amuse::ContainerRegistry::TypeToName(containerType));
if (containerType == amuse::ContainerRegistry::Type::Raw4)
name = url.URLByDeletingPathExtension.lastPathComponent.UTF8String;
return [groupFilePresenter addCollectionName:std::move(name) items:std::move(data)];
}
- (IBAction)importFile:(id)sender
{
__block NSOpenPanel* panel = [NSOpenPanel openPanel];
[panel beginSheetModalForWindow:mainWindow completionHandler:^(NSInteger result) {
if (result == NSFileHandlingPanelOKButton)
{
[self importURL:panel.URL];
}
}];
}
- (void)startSFX:(int)sfxId
{
if (activeSFXVox)
activeSFXVox->keyOff();
activeSFXVox = amuseEngine->fxStart(sfxId, 1.f, 0.f);
}
- (void)startSample:(int)sampleId
{
}
- (void)reloadTables
{
[sfxTable reloadData];
[samplesTable reloadData];
}
- (IBAction)filterDataOutline:(id)sender
{
[groupFilePresenter setSearchFilter:[sender stringValue]];
}
- (IBAction)removeDataItem:(id)sender
{
[groupFilePresenter removeSelectedItem];
}
- (void)outlineView:(DataOutlineView *)ov selectionChanged:(id)item
{
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
{
removeDataButton.enabled = TRUE;
removeDataMenu.enabled = TRUE;
}
else
{
removeDataButton.enabled = FALSE;
removeDataMenu.enabled = FALSE;
}
}
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
{
NSURL* url = [NSURL fileURLWithPath:filename isDirectory:NO];
return [self importURL:url];
}
- (amuse::Engine&)getAmuseEngine
{
return *amuseEngine;
}
@end

View File

@@ -2,36 +2,168 @@
#define __AMUSE_AUDIOUNIT_AUDIOGROUPFILEPRESENTER_HPP__
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include <map>
#include <string>
#include "optional.hpp"
#include <amuse/amuse.hpp>
#include <athena/FileReader.hpp>
@class AudioGroupFilePresenter;
@class AudioGroupDataToken;
@class AudioGroupCollectionToken;
@class AudioGroupSFXToken;
@class AudioGroupSampleToken;
@class AudioGroupToken;
@protocol AudioGroupClient
- (amuse::Engine&)getAmuseEngine;
@end
struct AudioGroupDataCollection
{
NSURL* m_proj = nullptr; /* Only this member set for single-file containers */
NSURL* m_pool = nullptr;
NSURL* m_sdir = nullptr;
NSURL* m_samp = nullptr;
std::string m_name;
NSURL* m_proj;
NSURL* m_pool;
NSURL* m_sdir;
NSURL* m_samp;
NSURL* m_meta;
AudioGroupDataToken* m_token;
std::vector<uint8_t> m_projData;
std::vector<uint8_t> m_poolData;
std::vector<uint8_t> m_sdirData;
std::vector<uint8_t> m_sampData;
struct MetaData
{
amuse::DataFormat fmt;
uint32_t absOffs;
uint32_t active;
MetaData(amuse::DataFormat fmtIn, uint32_t absOffsIn, uint32_t activeIn)
: fmt(fmtIn), absOffs(absOffsIn), active(activeIn)
{
}
MetaData(athena::io::FileReader& r)
: fmt(amuse::DataFormat(r.readUint32Little())), absOffs(r.readUint32Little()), active(r.readUint32Little())
{
}
};
std::experimental::optional<MetaData> m_metaData;
std::experimental::optional<amuse::AudioGroupData> m_loadedData;
const amuse::AudioGroup* m_loadedGroup;
std::vector<AudioGroupToken*> m_groupTokens;
bool invalidateURL(NSURL* url);
void moveURL(NSURL* oldUrl, NSURL* newUrl);
std::unique_ptr<uint8_t[]> _coordinateRead(AudioGroupFilePresenter* presenter, size_t& szOut, NSURL* url);
bool loadProj(AudioGroupFilePresenter* presenter);
bool loadPool(AudioGroupFilePresenter* presenter);
bool loadSdir(AudioGroupFilePresenter* presenter);
bool loadSamp(AudioGroupFilePresenter* presenter);
bool loadMeta(AudioGroupFilePresenter* presenter);
std::unique_ptr<uint8_t[]> coordinateProjRead(AudioGroupFilePresenter* presenter, size_t& szOut);
std::unique_ptr<uint8_t[]> coordinatePoolRead(AudioGroupFilePresenter* presenter, size_t& szOut);
std::unique_ptr<uint8_t[]> coordinateSdirRead(AudioGroupFilePresenter* presenter, size_t& szOut);
std::unique_ptr<uint8_t[]> coordinateSampRead(AudioGroupFilePresenter* presenter, size_t& szOut);
AudioGroupDataCollection(const std::string& name, NSURL* proj, NSURL* pool, NSURL* sdir, NSURL* samp, NSURL* meta);
bool isDataComplete() const
{
return m_projData.size() && m_poolData.size() && m_sdirData.size() && m_sampData.size() && m_metaData;
}
bool _attemptLoad(AudioGroupFilePresenter* presenter);
bool _indexData(AudioGroupFilePresenter* presenter);
void enable(AudioGroupFilePresenter* presenter);
void disable(AudioGroupFilePresenter* presenter);
};
@interface AudioGroupFilePresenter : NSObject <NSFilePresenter>
struct AudioGroupCollection
{
NSURL* m_groupURL;
NSOperationQueue* m_dataQueue;
std::map<std::string, AudioGroupDataCollection> m_audioGroupCollections;
}
NSURL* m_url;
AudioGroupCollectionToken* m_token;
std::map<std::string, std::unique_ptr<AudioGroupDataCollection>> m_groups;
std::vector<std::map<std::string, std::unique_ptr<AudioGroupDataCollection>>::iterator> m_filterGroups;
AudioGroupCollection(NSURL* url);
void addCollection(AudioGroupFilePresenter* presenter,
std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>>&& collection);
void update(AudioGroupFilePresenter* presenter);
bool doSearch(const std::string& str);
bool doActiveFilter();
void addSFX(std::vector<AudioGroupSFXToken*>& vecOut);
void addSamples(std::vector<AudioGroupSampleToken*>& vecOut);
};
@interface AudioGroupDataToken : NSObject
{
@public
AudioGroupDataCollection* m_collection;
}
- (id)initWithDataCollection:(AudioGroupDataCollection*)collection;
@end
@interface AudioGroupCollectionToken : NSObject
{
@public
AudioGroupCollection* m_collection;
}
- (id)initWithCollection:(AudioGroupCollection*)collection;
@end
@interface AudioGroupSFXToken : NSObject
{
@public
NSAttributedString* m_name;
int m_loadId;
const amuse::SFXGroupIndex::SFXEntry* m_sfx;
}
- (id)initWithName:(NSAttributedString*)name loadId:(int)loadId sfx:(const amuse::SFXGroupIndex::SFXEntry*)sfx;
@end
@interface AudioGroupSampleToken : NSObject
{
@public
NSAttributedString* m_name;
const std::pair<amuse::AudioGroupSampleDirectory::Entry, amuse::AudioGroupSampleDirectory::ADPCMParms>* m_sample;
}
- (id)
initWithName:(NSAttributedString*)name
samp:(const std::pair<amuse::AudioGroupSampleDirectory::Entry, amuse::AudioGroupSampleDirectory::ADPCMParms>*)
sample;
@end
@interface AudioGroupToken : NSObject
{
@public
NSString* m_name;
int m_id;
const amuse::SongGroupIndex* m_song;
const amuse::SFXGroupIndex* m_sfx;
}
- (id)initWithName:(NSString*)name id:(int)gid songGroup:(const amuse::SongGroupIndex*)group;
- (id)initWithName:(NSString*)name id:(int)gid sfxGroup:(const amuse::SFXGroupIndex*)group;
@end
@interface AudioGroupFilePresenter : NSObject <NSFilePresenter, NSOutlineViewDataSource, NSOutlineViewDelegate>
{
@public
id<AudioGroupClient> m_audioGroupClient;
NSURL* m_groupURL;
std::map<std::string, std::unique_ptr<AudioGroupCollection>> m_audioGroupCollections;
std::vector<std::map<std::string, std::unique_ptr<AudioGroupCollection>>::iterator> m_filterAudioGroupCollections;
NSOutlineView* m_lastOutlineView;
NSString* m_searchStr;
std::vector<AudioGroupSFXToken*> m_sfxTableData;
std::vector<AudioGroupSampleToken*> m_sampleTableData;
}
- (id)initWithAudioGroupClient:(id<AudioGroupClient>)client;
- (BOOL)addCollectionName:(std::string&&)name
items:(std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>>&&)collection;
- (void)update;
- (void)resetIterators;
- (void)setSearchFilter:(NSString*)str;
- (void)removeSelectedItem;
@end
#endif // __AMUSE_AUDIOUNIT_AUDIOGROUPFILEPRESENTER_HPP__

View File

@@ -1,5 +1,75 @@
#include "AudioGroupFilePresenter.hpp"
#include <athena/FileReader.hpp>
#include <amuse/AudioGroupProject.hpp>
#import "AmuseContainingApp.hpp"
#import "AudioUnitBackend.hpp"
#import "AudioUnitViewController.hpp"
static std::string StrToLower(const std::string& str)
{
std::string ret = str;
std::transform(ret.begin(), ret.end(), ret.begin(), tolower);
return ret;
}
@implementation AudioGroupDataToken
- (id)initWithDataCollection:(AudioGroupDataCollection *)collection
{
self = [super init];
m_collection = collection;
return self;
}
@end
@implementation AudioGroupCollectionToken
- (id)initWithCollection:(AudioGroupCollection *)collection
{
self = [super init];
m_collection = collection;
return self;
}
@end
@implementation AudioGroupSFXToken
- (id)initWithName:(NSAttributedString*)name loadId:(int)loadId sfx:(const amuse::SFXGroupIndex::SFXEntry*)sfx
{
self = [super init];
m_name = name;
m_loadId = loadId;
m_sfx = sfx;
return self;
}
@end
@implementation AudioGroupSampleToken
- (id)initWithName:(NSAttributedString*)name samp:(const std::pair<amuse::AudioGroupSampleDirectory::Entry,
amuse::AudioGroupSampleDirectory::ADPCMParms>*)sample
{
self = [super init];
m_name = name;
m_sample = sample;
return self;
}
@end
@implementation AudioGroupToken
- (id)initWithName:(NSString*)name id:(int)gid songGroup:(const amuse::SongGroupIndex*)group
{
self = [super init];
m_name = name;
m_song = group;
m_id = gid;
return self;
}
- (id)initWithName:(NSString*)name id:(int)gid sfxGroup:(const amuse::SFXGroupIndex*)group
{
self = [super init];
m_name = name;
m_sfx = group;
m_id = gid;
return self;
}
@end
@implementation AudioGroupFilePresenter
@@ -10,37 +80,286 @@
- (NSOperationQueue*)presentedItemOperationQueue
{
return m_dataQueue;
return [NSOperationQueue mainQueue];
}
bool AudioGroupDataCollection::invalidateURL(NSURL* url)
AudioGroupCollection::AudioGroupCollection(NSURL* url)
: m_url(url), m_token([[AudioGroupCollectionToken alloc] initWithCollection:this]) {}
void AudioGroupCollection::addCollection(AudioGroupFilePresenter* presenter,
std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>>&& collection)
{
bool valid = false;
if (m_proj)
for (std::pair<std::string, amuse::IntrusiveAudioGroupData>& pair : collection)
{
if ([m_proj isEqual:url])
m_proj = nullptr;
valid |= m_proj != nullptr;
NSURL* collectionUrl = [m_url URLByAppendingPathComponent:@(pair.first.c_str())];
amuse::IntrusiveAudioGroupData& dataIn = pair.second;
auto search = m_groups.find(pair.first);
if (search == m_groups.end())
{
search = m_groups.emplace(pair.first,
std::make_unique<AudioGroupDataCollection>(pair.first,
[collectionUrl URLByAppendingPathComponent:@"proj"],
[collectionUrl URLByAppendingPathComponent:@"pool"],
[collectionUrl URLByAppendingPathComponent:@"sdir"],
[collectionUrl URLByAppendingPathComponent:@"samp"],
[collectionUrl URLByAppendingPathComponent:@"meta"])).first;
}
AudioGroupDataCollection& dataCollection = *search->second;
dataCollection.m_projData.resize(dataIn.getProjSize());
memmove(dataCollection.m_projData.data(), dataIn.getProj(), dataIn.getProjSize());
dataCollection.m_poolData.resize(dataIn.getPoolSize());
memmove(dataCollection.m_poolData.data(), dataIn.getPool(), dataIn.getPoolSize());
dataCollection.m_sdirData.resize(dataIn.getSdirSize());
memmove(dataCollection.m_sdirData.data(), dataIn.getSdir(), dataIn.getSdirSize());
dataCollection.m_sampData.resize(dataIn.getSampSize());
memmove(dataCollection.m_sampData.data(), dataIn.getSamp(), dataIn.getSampSize());
dataCollection.m_metaData.emplace(dataIn.getDataFormat(), dataIn.getAbsoluteProjOffsets(), true);
dataCollection._indexData(presenter);
}
if (m_pool)
}
void AudioGroupCollection::update(AudioGroupFilePresenter* presenter)
{
NSFileManager* fman = [NSFileManager defaultManager];
NSArray<NSURL*>* contents =
[fman contentsOfDirectoryAtURL:m_url
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants |
NSDirectoryEnumerationSkipsHiddenFiles
error:nil];
if (!contents)
return;
for (NSURL* path in contents)
{
if ([m_pool isEqual:url])
m_pool = nullptr;
valid |= m_pool != nullptr;
NSNumber* isDir;
[path getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil];
if (isDir.boolValue)
{
auto search = m_groups.find(path.lastPathComponent.UTF8String);
if (search == m_groups.end())
{
std::string nameStr = path.lastPathComponent.UTF8String;
search =
m_groups.emplace(nameStr,
std::make_unique<AudioGroupDataCollection>(nameStr,
[path URLByAppendingPathComponent:@"proj"],
[path URLByAppendingPathComponent:@"pool"],
[path URLByAppendingPathComponent:@"sdir"],
[path URLByAppendingPathComponent:@"samp"],
[path URLByAppendingPathComponent:@"meta"])).first;
search->second->_attemptLoad(presenter);
}
}
}
if (m_sdir)
}
bool AudioGroupCollection::doSearch(const std::string& str)
{
bool ret = false;
m_filterGroups.clear();
m_filterGroups.reserve(m_groups.size());
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
if (str.empty() || StrToLower(it->first).find(str) != std::string::npos)
{
m_filterGroups.push_back(it);
ret = true;
}
return ret;
}
bool AudioGroupCollection::doActiveFilter()
{
bool ret = false;
m_filterGroups.clear();
m_filterGroups.reserve(m_groups.size());
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
if (it->second->m_metaData->active)
{
m_filterGroups.push_back(it);
ret = true;
}
return ret;
}
void AudioGroupCollection::addSFX(std::vector<AudioGroupSFXToken*>& vecOut)
{
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
{
if ([m_sdir isEqual:url])
m_sdir = nullptr;
valid |= m_sdir != nullptr;
if (!it->second->m_metaData->active)
continue;
const auto& sfxGroups = it->second->m_loadedGroup->getProj().sfxGroups();
std::map<int, const amuse::SFXGroupIndex*> sortGroups;
for (const auto& pair : sfxGroups)
sortGroups[pair.first] = &pair.second;
for (const auto& pair : sortGroups)
{
NSMutableAttributedString* name = [[NSMutableAttributedString alloc] initWithString:m_url.lastPathComponent attributes:@{NSForegroundColorAttributeName: [NSColor grayColor]}];
[name appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %s (%d)", it->first.c_str(), pair.first]]];
vecOut.push_back([[AudioGroupSFXToken alloc] initWithName:name loadId:0 sfx:nil]);
std::map<int, const amuse::SFXGroupIndex::SFXEntry*> sortSfx;
for (const auto& pair : pair.second->m_sfxEntries)
sortSfx[pair.first] = pair.second;
for (const auto& sfx : sortSfx)
{
name = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", sfx.first]];
vecOut.push_back([[AudioGroupSFXToken alloc] initWithName:name loadId:sfx.first sfx:sfx.second]);
}
}
}
if (m_samp)
}
void AudioGroupCollection::addSamples(std::vector<AudioGroupSampleToken*>& vecOut)
{
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
{
if ([m_samp isEqual:url])
m_samp = nullptr;
valid |= m_samp != nullptr;
if (!it->second->m_metaData->active)
continue;
const auto& samps = it->second->m_loadedGroup->getSdir().sampleEntries();
std::map<int, const std::pair<amuse::AudioGroupSampleDirectory::Entry, amuse::AudioGroupSampleDirectory::ADPCMParms>*> sortSamps;
for (const auto& pair : samps)
sortSamps[pair.first] = &pair.second;
NSMutableAttributedString* name = [[NSMutableAttributedString alloc] initWithString:m_url.lastPathComponent attributes:@{NSForegroundColorAttributeName: [NSColor grayColor]}];
[name appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" %s", it->first.c_str()]]];
vecOut.push_back([[AudioGroupSampleToken alloc] initWithName:name samp:nil]);
for (const auto& pair : sortSamps)
{
name = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d", pair.first]];
vecOut.push_back([[AudioGroupSampleToken alloc] initWithName:name samp:pair.second]);
}
}
return valid;
}
AudioGroupDataCollection::AudioGroupDataCollection(const std::string& name, NSURL* proj, NSURL* pool,
NSURL* sdir, NSURL* samp, NSURL* meta)
: m_name(name), m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp), m_meta(meta),
m_token([[AudioGroupDataToken alloc] initWithDataCollection:this]) {}
bool AudioGroupDataCollection::_attemptLoad(AudioGroupFilePresenter* presenter)
{
if (m_metaData && m_loadedData && m_loadedGroup)
return true;
if (!loadProj(presenter))
return false;
if (!loadPool(presenter))
return false;
if (!loadSdir(presenter))
return false;
if (!loadSamp(presenter))
return false;
if (!loadMeta(presenter))
return false;
return _indexData(presenter);
}
bool AudioGroupDataCollection::_indexData(AudioGroupFilePresenter* presenter)
{
amuse::Engine& engine = [presenter->m_audioGroupClient getAmuseEngine];
switch (m_metaData->fmt)
{
case amuse::DataFormat::GCN:
default:
m_loadedData.emplace(m_projData.data(), m_projData.size(),
m_poolData.data(), m_poolData.size(),
m_sdirData.data(), m_sdirData.size(),
m_sampData.data(), m_sampData.size(),
amuse::GCNDataTag{});
break;
case amuse::DataFormat::N64:
m_loadedData.emplace(m_projData.data(), m_projData.size(),
m_poolData.data(), m_poolData.size(),
m_sdirData.data(), m_sdirData.size(),
m_sampData.data(), m_sampData.size(),
m_metaData->absOffs, amuse::N64DataTag{});
break;
case amuse::DataFormat::PC:
m_loadedData.emplace(m_projData.data(), m_projData.size(),
m_poolData.data(), m_poolData.size(),
m_sdirData.data(), m_sdirData.size(),
m_sampData.data(), m_sampData.size(),
m_metaData->absOffs, amuse::PCDataTag{});
break;
}
m_loadedGroup = engine.addAudioGroup(*m_loadedData);
m_groupTokens.clear();
if (m_loadedGroup)
{
m_groupTokens.reserve(m_loadedGroup->getProj().songGroups().size() +
m_loadedGroup->getProj().sfxGroups().size());
{
const auto& songGroups = m_loadedGroup->getProj().songGroups();
std::map<int, const amuse::SongGroupIndex*> sortGroups;
for (const auto& pair : songGroups)
sortGroups[pair.first] = &pair.second;
for (const auto& pair : sortGroups)
{
NSString* name = [NSString stringWithFormat:@"%d", pair.first];
m_groupTokens.push_back([[AudioGroupToken alloc] initWithName:name id:pair.first
songGroup:pair.second]);
}
}
{
const auto& sfxGroups = m_loadedGroup->getProj().sfxGroups();
std::map<int, const amuse::SFXGroupIndex*> sortGroups;
for (const auto& pair : sfxGroups)
sortGroups[pair.first] = &pair.second;
for (const auto& pair : sortGroups)
{
NSString* name = [NSString stringWithFormat:@"%d", pair.first];
m_groupTokens.push_back([[AudioGroupToken alloc] initWithName:name id:pair.first
sfxGroup:pair.second]);
}
}
}
return m_loadedData && m_loadedGroup;
}
void AudioGroupDataCollection::enable(AudioGroupFilePresenter* presenter)
{
m_metaData->active = true;
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
[coord coordinateWritingItemAtURL:m_meta options:0 error:nil
byAccessor:^(NSURL* newUrl)
{
FILE* fp = fopen(newUrl.path.UTF8String, "wb");
if (fp)
{
fwrite(&*m_metaData, 1, sizeof(*m_metaData), fp);
fclose(fp);
}
}];
}
void AudioGroupDataCollection::disable(AudioGroupFilePresenter* presenter)
{
m_metaData->active = false;
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
[coord coordinateWritingItemAtURL:m_meta options:0 error:nil
byAccessor:^(NSURL* newUrl)
{
FILE* fp = fopen(newUrl.path.UTF8String, "wb");
if (fp)
{
fwrite(&*m_metaData, 1, sizeof(*m_metaData), fp);
fclose(fp);
}
}];
}
void AudioGroupDataCollection::moveURL(NSURL* oldUrl, NSURL* newUrl)
@@ -67,119 +386,474 @@ void AudioGroupDataCollection::moveURL(NSURL* oldUrl, NSURL* newUrl)
}
}
std::unique_ptr<uint8_t[]> AudioGroupDataCollection::_coordinateRead(AudioGroupFilePresenter* presenter, size_t& szOut, NSURL* url)
{
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
if (!coord)
return {};
NSError* err;
__block std::unique_ptr<uint8_t[]> ret;
__block size_t retSz = 0;
[coord coordinateReadingItemAtURL:url options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
byAccessor:^(NSURL* newUrl)
{
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
if (r.hasError())
return;
retSz = r.length();
ret = r.readUBytes(retSz);
}];
szOut = retSz;
return std::move(ret);
}
std::unique_ptr<uint8_t[]> AudioGroupDataCollection::coordinateProjRead(AudioGroupFilePresenter* presenter, size_t& szOut)
bool AudioGroupDataCollection::loadProj(AudioGroupFilePresenter* presenter)
{
if (!m_proj)
return {};
return _coordinateRead(presenter, szOut, m_proj);
return false;
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
if (!coord)
return false;
NSError* err;
__block std::vector<uint8_t>& ret = m_projData;
[coord coordinateReadingItemAtURL:m_proj
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
byAccessor:^(NSURL* newUrl)
{
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
if (r.hasError())
return;
size_t len = r.length();
ret.resize(len);
r.readUBytesToBuf(ret.data(), len);
}];
return ret.size();
}
std::unique_ptr<uint8_t[]> AudioGroupDataCollection::coordinatePoolRead(AudioGroupFilePresenter* presenter, size_t& szOut)
bool AudioGroupDataCollection::loadPool(AudioGroupFilePresenter* presenter)
{
if (!m_pool)
return {};
return _coordinateRead(presenter, szOut, m_pool);
return false;
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
if (!coord)
return false;
NSError* err;
__block std::vector<uint8_t>& ret = m_poolData;
[coord coordinateReadingItemAtURL:m_pool
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
byAccessor:^(NSURL* newUrl)
{
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
if (r.hasError())
return;
size_t len = r.length();
ret.resize(len);
r.readUBytesToBuf(ret.data(), len);
}];
return ret.size();
}
std::unique_ptr<uint8_t[]> AudioGroupDataCollection::coordinateSdirRead(AudioGroupFilePresenter* presenter, size_t& szOut)
bool AudioGroupDataCollection::loadSdir(AudioGroupFilePresenter* presenter)
{
if (!m_sdir)
return {};
return _coordinateRead(presenter, szOut, m_sdir);
return false;
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
if (!coord)
return false;
NSError* err;
__block std::vector<uint8_t>& ret = m_sdirData;
[coord coordinateReadingItemAtURL:m_sdir
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
byAccessor:^(NSURL* newUrl)
{
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
if (r.hasError())
return;
size_t len = r.length();
ret.resize(len);
r.readUBytesToBuf(ret.data(), len);
}];
return ret.size();
}
std::unique_ptr<uint8_t[]> AudioGroupDataCollection::coordinateSampRead(AudioGroupFilePresenter* presenter, size_t& szOut)
bool AudioGroupDataCollection::loadSamp(AudioGroupFilePresenter* presenter)
{
if (!m_samp)
return {};
return _coordinateRead(presenter, szOut, m_samp);
return false;
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
if (!coord)
return false;
NSError* err;
__block std::vector<uint8_t>& ret = m_sampData;
[coord coordinateReadingItemAtURL:m_samp
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
byAccessor:^(NSURL* newUrl)
{
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
if (r.hasError())
return;
size_t len = r.length();
ret.resize(len);
r.readUBytesToBuf(ret.data(), len);
}];
return ret.size();
}
- (void)accommodatePresentedSubitemDeletionAtURL:(NSURL*)url completionHandler:(void (^)(NSError* errorOrNil))completionHandler
bool AudioGroupDataCollection::loadMeta(AudioGroupFilePresenter* presenter)
{
for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ;)
{
std::pair<const std::string, AudioGroupDataCollection>& pair = *it;
if (pair.second.invalidateURL(url))
{
it = m_audioGroupCollections.erase(it);
continue;
}
++it;
}
completionHandler(nil);
if (!m_meta)
return false;
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter];
if (!coord)
return false;
NSError* err;
__block std::experimental::optional<MetaData>& ret = m_metaData;
[coord coordinateReadingItemAtURL:m_meta
options:NSFileCoordinatorReadingResolvesSymbolicLink error:&err
byAccessor:^(NSURL* newUrl)
{
athena::io::FileReader r([[newUrl path] UTF8String], 1024 * 32, false);
if (r.hasError())
return;
ret.emplace(r);
}];
return ret.operator bool();
}
- (void)presentedSubitemDidAppearAtURL:(NSURL*)url
- (void)presentedSubitemDidChangeAtURL:(NSURL *)url
{
NSString* path = [url path];
if (!path)
return;
NSString* extension = [url pathExtension];
NSString* lastComp = [url lastPathComponent];
lastComp = [lastComp substringToIndex:[lastComp length] - [extension length]];
AudioGroupDataCollection& collection = m_audioGroupCollections[[lastComp UTF8String]];
if ([extension isEqualToString:@"pro"] || [extension isEqualToString:@"proj"])
{
collection.m_proj = url;
}
else if ([extension isEqualToString:@"poo"] || [extension isEqualToString:@"pool"])
{
collection.m_pool = url;
}
else if ([extension isEqualToString:@"sdi"] || [extension isEqualToString:@"sdir"])
{
collection.m_sdir = url;
}
else if ([extension isEqualToString:@"sam"] || [extension isEqualToString:@"samp"])
{
collection.m_samp = url;
}
else
{
collection.m_proj = url;
}
size_t relComps = url.pathComponents.count - m_groupURL.pathComponents.count;
if (relComps <= 1)
[self update];
}
- (void)presentedSubitemAtURL:(NSURL*)oldUrl didMoveToURL:(NSURL*)newUrl
{
for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it)
for (auto& pair : m_audioGroupCollections)
{
std::pair<const std::string, AudioGroupDataCollection>& pair = *it;
pair.second.moveURL(oldUrl, newUrl);
for (auto& pair2 : pair.second->m_groups)
{
pair2.second->moveURL(oldUrl, newUrl);
}
}
}
- (id)init
- (NSInteger)outlineView:(NSOutlineView*)outlineView numberOfChildrenOfItem:(nullable id)item
{
m_lastOutlineView = outlineView;
if (!item)
return m_filterAudioGroupCollections.size();
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
return collection.m_filterGroups.size();
}
- (id)outlineView:(NSOutlineView*)outlineView child:(NSInteger)index ofItem:(nullable id)item
{
if (!item)
{
if (index >= m_filterAudioGroupCollections.size())
return nil;
return m_filterAudioGroupCollections[index]->second->m_token;
}
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
if (index >= collection.m_filterGroups.size())
return nil;
return collection.m_filterGroups[index]->second->m_token;
}
- (BOOL)outlineView:(NSOutlineView*)outlineView isItemExpandable:(id)item
{
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
return YES;
return NO;
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
{
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
{
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
{
size_t totalOn = 0;
for (auto& pair : collection.m_groups)
if (pair.second->m_metaData->active)
++totalOn;
if (totalOn == 0)
return [NSNumber numberWithInt:NSOffState];
else if (totalOn == collection.m_groups.size())
return [NSNumber numberWithInt:NSOnState];
else
return [NSNumber numberWithInt:NSMixedState];
}
else if ([tableColumn.identifier isEqualToString:@"DetailsColumn"])
return [NSString stringWithFormat:@"%zu Group File%s",
collection.m_groups.size(),
collection.m_groups.size() > 1 ? "s" : ""];
}
else if ([item isKindOfClass:[AudioGroupDataToken class]])
{
AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection;
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
return [NSNumber numberWithInt:data.m_metaData->active ? NSOnState : NSOffState];
else if ([tableColumn.identifier isEqualToString:@"DetailsColumn"])
{
if (!data.m_loadedGroup)
return @"";
if (data.m_loadedGroup->getProj().songGroups().size() && data.m_loadedGroup->getProj().sfxGroups().size())
return [NSString stringWithFormat:@"%zu Song Group%s, %zu SFX Group%s",
data.m_loadedGroup->getProj().songGroups().size(),
data.m_loadedGroup->getProj().songGroups().size() > 1 ? "s" : "",
data.m_loadedGroup->getProj().sfxGroups().size(),
data.m_loadedGroup->getProj().sfxGroups().size() > 1 ? "s" : ""];
else if (data.m_loadedGroup->getProj().songGroups().size())
return [NSString stringWithFormat:@"%zu Song Group%s",
data.m_loadedGroup->getProj().songGroups().size(),
data.m_loadedGroup->getProj().songGroups().size() > 1 ? "s" : ""];
else if (data.m_loadedGroup->getProj().sfxGroups().size())
return [NSString stringWithFormat:@"%zu SFX Group%s",
data.m_loadedGroup->getProj().sfxGroups().size(),
data.m_loadedGroup->getProj().sfxGroups().size() > 1 ? "s" : ""];
else
return @"";
}
}
return nil;
}
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(nullable id)object forTableColumn:(nullable NSTableColumn *)tableColumn byItem:(nullable id)item
{
bool dirty = false;
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
{
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
{
NSInteger active = [object integerValue];
if (active)
for (auto& pair : collection.m_groups)
pair.second->enable(self);
else
for (auto& pair : collection.m_groups)
pair.second->disable(self);
dirty = true;
}
}
else if ([item isKindOfClass:[AudioGroupDataToken class]])
{
AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection;
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
{
NSInteger active = [object integerValue];
if (active)
data.enable(self);
else
data.disable(self);
dirty = true;
}
}
if (dirty)
[self resetIterators];
}
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(nonnull id)cell forTableColumn:(nullable NSTableColumn *)tableColumn item:(nonnull id)item
{
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
{
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
((NSButtonCell*)cell).title = collection.m_url.lastPathComponent;
}
else if ([item isKindOfClass:[AudioGroupDataToken class]])
{
AudioGroupDataCollection& data = *((AudioGroupDataToken*)item)->m_collection;
if ([tableColumn.identifier isEqualToString:@"CollectionColumn"])
((NSButtonCell*)cell).title = @(data.m_name.c_str());
}
}
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
DataOutlineView* ov = notification.object;
id item = [ov itemAtRow:ov.selectedRow];
[(AppDelegate*)NSApp.delegate outlineView:ov selectionChanged:item];
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id<NSDraggingInfo>)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
[outlineView setDropItem:nil dropChildIndex:NSOutlineViewDropOnItemIndex];
NSPasteboard* pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSURLPboardType])
return NSDragOperationCopy;
return NSDragOperationNone;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index
{
NSPasteboard* pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSURLPboardType])
{
NSURL* url = [NSURL URLFromPasteboard:pboard];
[(AppDelegate*)NSApp.delegate importURL:url];
return YES;
}
return NO;
}
- (BOOL)addCollectionName:(std::string&&)name items:(std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>>&&)collection
{
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:self];
if (!coord)
return false;
NSURL* dir = [m_groupURL URLByAppendingPathComponent:@(name.c_str())];
__block AudioGroupCollection& insert = *m_audioGroupCollections.emplace(name, std::make_unique<AudioGroupCollection>(dir)).first->second;
insert.addCollection(self, std::move(collection));
[coord coordinateWritingItemAtURL:m_groupURL options:0 error:nil
byAccessor:^(NSURL* newUrl)
{
for (std::pair<const std::string, std::unique_ptr<AudioGroupDataCollection>>& pair : insert.m_groups)
{
NSURL* collectionUrl = [insert.m_url URLByAppendingPathComponent:@(pair.first.c_str())];
[[NSFileManager defaultManager] createDirectoryAtURL:collectionUrl withIntermediateDirectories:YES attributes:nil error:nil];
FILE* fp = fopen(pair.second->m_proj.path.UTF8String, "wb");
if (fp)
{
fwrite(pair.second->m_projData.data(), 1, pair.second->m_projData.size(), fp);
fclose(fp);
}
fp = fopen(pair.second->m_pool.path.UTF8String, "wb");
if (fp)
{
fwrite(pair.second->m_poolData.data(), 1, pair.second->m_poolData.size(), fp);
fclose(fp);
}
fp = fopen(pair.second->m_sdir.path.UTF8String, "wb");
if (fp)
{
fwrite(pair.second->m_sdirData.data(), 1, pair.second->m_sdirData.size(), fp);
fclose(fp);
}
fp = fopen(pair.second->m_samp.path.UTF8String, "wb");
if (fp)
{
fwrite(pair.second->m_sampData.data(), 1, pair.second->m_sampData.size(), fp);
fclose(fp);
}
fp = fopen(pair.second->m_meta.path.UTF8String, "wb");
if (fp)
{
fwrite(&*pair.second->m_metaData, 1, sizeof(*pair.second->m_metaData), fp);
fclose(fp);
}
}
}];
[self resetIterators];
return true;
}
- (void)update
{
NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:self];
if (!coord)
return;
NSError* coordErr;
__block NSError* managerErr;
__block std::map<std::string, std::unique_ptr<AudioGroupCollection>>& theMap = m_audioGroupCollections;
__block AudioGroupFilePresenter* presenter = self;
[coord coordinateReadingItemAtURL:m_groupURL options:NSFileCoordinatorReadingResolvesSymbolicLink error:&coordErr
byAccessor:^(NSURL* newUrl)
{
NSFileManager* fman = [NSFileManager defaultManager];
NSArray<NSURL*>* contents =
[fman contentsOfDirectoryAtURL:newUrl
includingPropertiesForKeys:@[NSURLIsDirectoryKey]
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants |
NSDirectoryEnumerationSkipsHiddenFiles
error:&managerErr];
if (!contents)
return;
for (NSURL* path in contents)
{
NSNumber* isDir;
[path getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil];
if (isDir.boolValue)
{
auto search = theMap.find(path.lastPathComponent.UTF8String);
if (search == theMap.end())
{
search = theMap.emplace(path.lastPathComponent.UTF8String, std::make_unique<AudioGroupCollection>(path)).first;
search->second->update(presenter);
}
}
}
}];
[self resetIterators];
}
- (void)resetIterators
{
if ([(NSObject*)m_audioGroupClient isKindOfClass:[AppDelegate class]])
{
std::string search;
if (m_searchStr)
search = m_searchStr.UTF8String;
m_sfxTableData.clear();
m_sampleTableData.clear();
m_filterAudioGroupCollections.clear();
m_filterAudioGroupCollections.reserve(m_audioGroupCollections.size());
for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it)
{
it->second->addSFX(m_sfxTableData);
it->second->addSamples(m_sampleTableData);
if (it->second->doSearch(search) || !m_searchStr || StrToLower(it->first).find(search) != std::string::npos)
m_filterAudioGroupCollections.push_back(it);
}
[m_lastOutlineView reloadItem:nil reloadChildren:YES];
[(AppDelegate*)m_audioGroupClient reloadTables];
}
else
{
m_sfxTableData.clear();
m_sampleTableData.clear();
m_filterAudioGroupCollections.clear();
m_filterAudioGroupCollections.reserve(m_audioGroupCollections.size());
for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it)
{
it->second->addSFX(m_sfxTableData);
it->second->addSamples(m_sampleTableData);
if (it->second->doActiveFilter())
m_filterAudioGroupCollections.push_back(it);
}
[((AmuseAudioUnit*)m_audioGroupClient)->m_viewController->m_groupBrowser loadColumnZero];
}
}
- (void)setSearchFilter:(NSString*)str
{
m_searchStr = [str lowercaseString];
[self resetIterators];
}
- (void)removeSelectedItem
{
id item = [m_lastOutlineView itemAtRow:m_lastOutlineView.selectedRow];
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
{
AudioGroupCollection& collection = *((AudioGroupCollectionToken*)item)->m_collection;
NSURL* collectionURL = collection.m_url;
NSString* lastComp = collectionURL.lastPathComponent;
m_audioGroupCollections.erase(lastComp.UTF8String);
[self resetIterators];
[[NSFileManager defaultManager] removeItemAtURL:collectionURL error:nil];
if (m_audioGroupCollections.empty())
[(AppDelegate*)NSApp.delegate outlineView:(DataOutlineView*)m_lastOutlineView selectionChanged:nil];
}
}
- (id)initWithAudioGroupClient:(id<AudioGroupClient>)client
{
m_audioGroupClient = client;
m_groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.io.github.axiodl.Amuse.AudioGroups"];
if (!m_groupURL)
return nil;
m_dataQueue = [NSOperationQueue new];
[NSFileCoordinator addFilePresenter:self];
[self update];
return self;
}

View File

@@ -16,39 +16,38 @@
#include "amuse/IBackendVoice.hpp"
#include "amuse/IBackendSubmix.hpp"
#include "amuse/IBackendVoiceAllocator.hpp"
#import "AudioGroupFilePresenter.hpp"
@class AudioUnitViewController;
namespace amuse
{
/** Backend MIDI event reader for controlling sequencer with external hardware / software */
class AudioUnitBackendMIDIReader : public BooBackendMIDIReader
{
friend class AudioUnitBackendVoiceAllocator;
public:
AudioUnitBackendMIDIReader(Engine& engine)
: BooBackendMIDIReader(engine, "AudioUnit MIDI") {}
};
/** Backend voice allocator implementation for AudioUnit mixer */
class AudioUnitBackendVoiceAllocator : public BooBackendVoiceAllocator
{
friend class AudioUnitBackendMIDIReader;
public:
AudioUnitBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine)
: BooBackendVoiceAllocator(booEngine) {}
AudioUnitBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine) : BooBackendVoiceAllocator(booEngine) {}
};
void RegisterAudioUnit();
}
@interface AmuseAudioUnit : AUAudioUnit
@interface AmuseAudioUnit : AUAudioUnit <AudioGroupClient>
{
@public
AudioUnitViewController* m_viewController;
std::unique_ptr<boo::IAudioVoiceEngine> m_booBackend;
std::experimental::optional<amuse::AudioUnitBackendVoiceAllocator> m_voxAlloc;
std::experimental::optional<amuse::Engine> m_engine;
AudioGroupFilePresenter* m_filePresenter;
AUAudioUnitBus* m_outBus;
AUAudioUnitBusArray* m_outs;
}
- (nullable id)initWithComponentDescription:(AudioComponentDescription)componentDescription
error:(NSError* __nullable* __nonnull)outError
viewController:(AudioUnitViewController* __nonnull)vc;
- (void)requestAudioGroup:(AudioGroupToken* _Nonnull)group;
@end
#endif

View File

@@ -12,13 +12,17 @@
#include "logvisor/logvisor.hpp"
#include "audiodev/AudioVoiceEngine.hpp"
#import "AudioUnitViewController.hpp"
static logvisor::Module Log("amuse::AudioUnitBackend");
struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine
{
AudioGroupToken* m_reqGroup = nullptr;
AudioGroupToken* m_curGroup = nullptr;
std::vector<float> m_interleavedBuf;
std::vector<std::unique_ptr<float[]>> m_renderBufs;
size_t m_frameBytes;
size_t m_renderFrames = 0;
AudioBufferList* m_outputData = nullptr;
boo::AudioChannelSet _getAvailableSet()
@@ -31,12 +35,24 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine
return {};
}
boo::ReceiveFunctor m_midiReceiver = nullptr;
boo::ReceiveFunctor* m_midiReceiver = nullptr;
struct MIDIIn : public boo::IMIDIIn
{
MIDIIn(bool virt, boo::ReceiveFunctor&& receiver)
: IMIDIIn(virt, std::move(receiver)) {}
std::string description() const
{
return "AudioUnit MIDI";
}
};
std::unique_ptr<boo::IMIDIIn> newVirtualMIDIIn(boo::ReceiveFunctor&& receiver)
{
m_midiReceiver = std::move(receiver);
return {};
std::unique_ptr<boo::IMIDIIn> ret = std::make_unique<MIDIIn>(true, std::move(receiver));
m_midiReceiver = &ret->m_receiver;
return ret;
}
std::unique_ptr<boo::IMIDIOut> newVirtualMIDIOut()
@@ -63,106 +79,150 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine
{
return {};
}
bool useMIDILock() const {return false;}
AudioUnitVoiceEngine()
{
m_mixInfo.m_channels = _getAvailableSet();
unsigned chCount = ChannelCount(m_mixInfo.m_channels);
m_mixInfo.m_periodFrames = 512;
m_mixInfo.m_sampleRate = 96000.0;
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
m_mixInfo.m_bitsPerSample = 32;
m_5msFrames = 96000 * 5 / 1000;
_buildAudioRenderClient();
}
void _buildAudioRenderClient()
{
m_mixInfo.m_channels = _getAvailableSet();
unsigned chCount = ChannelCount(m_mixInfo.m_channels);
m_5msFrames = m_mixInfo.m_sampleRate * 5 / 1000;
boo::ChannelMap& chMapOut = m_mixInfo.m_channelMap;
chMapOut.m_channelCount = 2;
chMapOut.m_channels[0] = boo::AudioChannel::FrontLeft;
chMapOut.m_channels[1] = boo::AudioChannel::FrontRight;
while (chMapOut.m_channelCount < chCount)
chMapOut.m_channels[chMapOut.m_channelCount++] = boo::AudioChannel::Unknown;
m_mixInfo.m_periodFrames = 2400;
m_frameBytes = m_mixInfo.m_periodFrames * m_mixInfo.m_channelMap.m_channelCount * 4;
}
void _rebuildAudioRenderClient(double sampleRate, size_t periodFrames)
{
m_mixInfo.m_periodFrames = periodFrames;
m_mixInfo.m_sampleRate = sampleRate;
_buildAudioRenderClient();
for (boo::AudioVoice* vox : m_activeVoices)
vox->_resetSampleRate(vox->m_sampleRateIn);
for (boo::AudioSubmix* smx : m_activeSubmixes)
smx->_resetOutputSampleRate();
}
void pumpAndMixVoices()
{
if (m_renderBufs.size() < m_outputData->mNumberBuffers)
m_renderBufs.resize(m_outputData->mNumberBuffers);
_pumpAndMixVoices(m_renderFrames, m_interleavedBuf.data());
for (int i=0 ; i<m_outputData->mNumberBuffers ; ++i)
for (size_t i=0 ; i<m_renderBufs.size() ; ++i)
{
std::unique_ptr<float[]>& buf = m_renderBufs[i];
AudioBuffer& auBuf = m_outputData->mBuffers[i];
if (!auBuf.mData)
{
buf.reset(new float[auBuf.mDataByteSize]);
buf.reset(new float[auBuf.mDataByteSize / 4]);
auBuf.mData = buf.get();
}
_pumpAndMixVoices(auBuf.mDataByteSize / 2 / 4, reinterpret_cast<float*>(auBuf.mData));
for (size_t f=0 ; f<m_renderFrames ; ++f)
{
float* bufOut = reinterpret_cast<float*>(auBuf.mData);
bufOut[f] = m_interleavedBuf[f*2+i];
}
}
}
double getCurrentSampleRate() const {return m_mixInfo.m_sampleRate;}
};
@implementation AmuseAudioUnit
- (id)initWithComponentDescription:(AudioComponentDescription)componentDescription
error:(NSError * _Nullable *)outError
viewController:(AudioUnitViewController*)vc
{
m_viewController = vc;
vc->m_audioUnit = self;
self = [super initWithComponentDescription:componentDescription error:outError];
return self;
}
- (id)initWithComponentDescription:(AudioComponentDescription)componentDescription
options:(AudioComponentInstantiationOptions)options
error:(NSError * _Nullable *)outError;
error:(NSError * _Nullable *)outError
{
self = [super initWithComponentDescription:componentDescription options:options error:outError];
if (!self)
return nil;
AUAudioUnitBus* outBus = [[AUAudioUnitBus alloc] initWithFormat:
[[AVAudioFormat alloc] initWithCommonFormat:AVAudioPCMFormatFloat32
sampleRate:96000.0
channels:2
interleaved:TRUE]
error:outError];
if (!outBus)
AVAudioFormat* format = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:96000.0 channels:2];
m_outBus = [[AUAudioUnitBus alloc] initWithFormat:format error:outError];
if (!m_outBus)
return nil;
m_outs = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self busType:AUAudioUnitBusTypeOutput busses:@[outBus]];
self.maximumFramesToRender = 2400;
return self;
}
- (BOOL)allocateRenderResourcesAndReturnError:(NSError * _Nullable *)outError
{
if (![super allocateRenderResourcesAndReturnError:outError])
return FALSE;
//m_outBus.supportedChannelCounts = @[@1,@2];
m_outBus.maximumChannelCount = 2;
m_outs = [[AUAudioUnitBusArray alloc] initWithAudioUnit:self
busType:AUAudioUnitBusTypeOutput
busses:@[m_outBus]];
m_booBackend = std::make_unique<AudioUnitVoiceEngine>();
if (!m_booBackend)
{
*outError = [NSError errorWithDomain:@"amuse" code:-1
userInfo:@{NSLocalizedDescriptionKey:@"Unable to construct boo mixer"}];
userInfo:@{NSLocalizedDescriptionKey:@"Unable to construct boo mixer"}];
return FALSE;
}
m_voxAlloc.emplace(*m_booBackend);
m_engine.emplace(*m_voxAlloc);
dispatch_sync(dispatch_get_main_queue(),
^{
m_filePresenter = [[AudioGroupFilePresenter alloc] initWithAudioGroupClient:self];
});
self.maximumFramesToRender = 512;
return self;
}
- (void)requestAudioGroup:(AudioGroupToken*)group
{
AudioUnitVoiceEngine& voxEngine = static_cast<AudioUnitVoiceEngine&>(*m_booBackend);
voxEngine.m_reqGroup = group;
}
- (BOOL)allocateRenderResourcesAndReturnError:(NSError **)outError
{
if (![super allocateRenderResourcesAndReturnError:outError])
return FALSE;
size_t chanCount = m_outBus.format.channelCount;
size_t renderFrames = self.maximumFramesToRender;
NSLog(@"Alloc Chans: %zu Frames: %zu SampRate: %f", chanCount, renderFrames, m_outBus.format.sampleRate);
AudioUnitVoiceEngine& voxEngine = static_cast<AudioUnitVoiceEngine&>(*m_booBackend);
voxEngine.m_renderFrames = renderFrames;
voxEngine.m_interleavedBuf.resize(renderFrames * std::max(2ul, chanCount));
voxEngine.m_renderBufs.resize(chanCount);
voxEngine._rebuildAudioRenderClient(m_outBus.format.sampleRate, renderFrames);
*outError = nil;
return TRUE;
}
- (void)deallocateRenderResources
{
m_engine = std::experimental::nullopt;
m_voxAlloc = std::experimental::nullopt;
m_booBackend.reset();
[super deallocateRenderResources];
}
- (BOOL)renderResourcesAllocated
{
if (m_engine)
return TRUE;
return FALSE;
AudioUnitVoiceEngine& voxEngine = static_cast<AudioUnitVoiceEngine&>(*m_booBackend);
voxEngine.m_renderBufs.clear();
}
- (AUAudioUnitBusArray*)outputBusses
@@ -184,11 +244,25 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine
{
__block AudioUnitVoiceEngine& voxEngine = static_cast<AudioUnitVoiceEngine&>(*m_booBackend);
__block amuse::Engine& amuseEngine = *m_engine;
__block std::shared_ptr<amuse::Sequencer> curSeq;
return ^AUAudioUnitStatus(AudioUnitRenderActionFlags* actionFlags, const AudioTimeStamp* timestamp,
AUAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList* outputData,
const AURenderEvent* realtimeEventListHead, AURenderPullInputBlock pullInputBlock)
{
/* Handle group load request */
AudioGroupToken* reqGroup = voxEngine.m_reqGroup;
if (voxEngine.m_curGroup != reqGroup)
{
voxEngine.m_curGroup = reqGroup;
if (reqGroup->m_song)
{
if (curSeq)
curSeq->kill();
curSeq = amuseEngine.seqPlay(reqGroup->m_id, -1, nullptr);
}
}
/* Process MIDI events first */
if (voxEngine.m_midiReceiver)
{
@@ -197,18 +271,25 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine
{
if (event->eventType == AURenderEventMIDI)
{
voxEngine.m_midiReceiver(std::vector<uint8_t>(std::cbegin(event->data),
std::cbegin(event->data) + event->length));
(*voxEngine.m_midiReceiver)(std::vector<uint8_t>(std::cbegin(event->data),
std::cbegin(event->data) + event->length),
event->eventSampleTime / voxEngine.getCurrentSampleRate());
}
}
}
/* Output buffers */
voxEngine.m_renderFrames = frameCount;
voxEngine.m_outputData = outputData;
amuseEngine.pumpEngine();
return noErr;
};
}
- (amuse::Engine&)getAmuseEngine
{
return *m_engine;
}
@end
namespace amuse

View File

@@ -2,9 +2,24 @@
#define __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__
#import <CoreAudioKit/CoreAudioKit.h>
#import "AudioGroupFilePresenter.hpp"
@class AmuseAudioUnit;
@interface GroupBrowserDelegate : NSObject <NSBrowserDelegate>
{
AmuseAudioUnit* m_audioUnit;
}
- (id)initWithAudioUnit:(AmuseAudioUnit*)au;
@end
@interface AudioUnitViewController : AUViewController <AUAudioUnitFactory>
{
@public
AmuseAudioUnit* m_audioUnit;
IBOutlet NSBrowser* m_groupBrowser;
GroupBrowserDelegate* m_groupBrowserDelegate;
}
@end
#endif // __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__

View File

@@ -5,73 +5,166 @@
#error ARC Required
#endif
@interface AudioUnitView : NSView
@implementation GroupBrowserDelegate
- (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column
{
NSButton* m_fileButton;
if (column == 0)
return YES;
else if (column == 1)
{
AudioGroupCollectionToken* collection = [sender selectedCellInColumn:0];
if (collection)
return YES;
}
else if (column == 2)
{
AudioGroupDataToken* groupFile = [sender selectedCellInColumn:1];
if (groupFile)
return YES;
}
return NO;
}
- (void)clickFileButton;
@end
@implementation AudioUnitView
- (id)init
- (NSInteger)browser:(NSBrowser *)sender numberOfRowsInColumn:(NSInteger)column
{
self = [super initWithFrame:NSMakeRect(0, 0, 200, 300)];
m_fileButton = [[NSButton alloc] initWithFrame:NSMakeRect(100, 100, 30, 10)];
m_fileButton.target = self;
m_fileButton.action = @selector(clickFileButton);
[self addSubview:m_fileButton];
if (column == 0)
return m_audioUnit->m_filePresenter->m_filterAudioGroupCollections.size();
else if (column == 1)
{
AudioGroupCollectionToken* collection = [sender selectedCellInColumn:0];
if (!collection)
return 0;
return collection->m_collection->m_filterGroups.size();
}
else if (column == 2)
{
AudioGroupDataToken* groupFile = [sender selectedCellInColumn:1];
if (!groupFile)
return 0;
const amuse::AudioGroup* audioGroupFile = groupFile->m_collection->m_loadedGroup;
return audioGroupFile->getProj().songGroups().size() + audioGroupFile->getProj().sfxGroups().size();
}
return 0;
}
- (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item
{
if (!item)
return m_audioUnit->m_filePresenter->m_filterAudioGroupCollections.size();
else if ([item isKindOfClass:[AudioGroupCollectionToken class]])
{
AudioGroupCollectionToken* collection = item;
return collection->m_collection->m_filterGroups.size();
}
else if ([item isKindOfClass:[AudioGroupDataToken class]])
{
AudioGroupDataToken* groupFile = item;
const amuse::AudioGroup* audioGroupFile = groupFile->m_collection->m_loadedGroup;
return audioGroupFile->getProj().songGroups().size() + audioGroupFile->getProj().sfxGroups().size();
}
else
return 0;
}
- (NSString *)browser:(NSBrowser *)sender titleOfColumn:(NSInteger)column
{
if (column == 0)
return @"Collection";
else if (column == 1)
return @"File";
else if (column == 2)
return @"Group";
return nil;
}
- (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item
{
if (!item)
return m_audioUnit->m_filePresenter->m_filterAudioGroupCollections[index]->second->m_token;
else if ([item isKindOfClass:[AudioGroupCollectionToken class]])
{
AudioGroupCollectionToken* collection = item;
return collection->m_collection->m_filterGroups[index]->second->m_token;
}
else if ([item isKindOfClass:[AudioGroupDataToken class]])
{
AudioGroupDataToken* groupFile = item;
return groupFile->m_collection->m_groupTokens[index];
}
else
return 0;
}
- (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item
{
if ([item isKindOfClass:[AudioGroupToken class]])
return YES;
return NO;
}
- (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item
{
return NO;
}
- (id)browser:(NSBrowser *)browser objectValueForItem:(id)item
{
if ([item isKindOfClass:[AudioGroupCollectionToken class]])
{
AudioGroupCollectionToken* collection = item;
return collection->m_collection->m_url.lastPathComponent;
}
else if ([item isKindOfClass:[AudioGroupDataToken class]])
{
AudioGroupDataToken* groupFile = item;
return @(groupFile->m_collection->m_name.c_str());
}
else if ([item isKindOfClass:[AudioGroupToken class]])
{
AudioGroupToken* group = item;
return group->m_name;
}
return nil;
}
- (NSIndexSet *)browser:(NSBrowser *)browser selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes inColumn:(NSInteger)column
{
if (column == 2)
{
AudioGroupToken* token = [browser itemAtRow:proposedSelectionIndexes.firstIndex inColumn:column];
[m_audioUnit requestAudioGroup:token];
}
return proposedSelectionIndexes;
}
- (id)initWithAudioUnit:(AmuseAudioUnit*)au
{
self = [super init];
m_audioUnit = au;
return self;
}
- (void)clickFileButton
{
NSLog(@"Click");
}
@end
@interface AudioUnitViewController ()
@end
@implementation AudioUnitViewController {
AUAudioUnit *audioUnit;
}
@implementation AudioUnitViewController
- (void) viewDidLoad {
[super viewDidLoad];
if (!audioUnit) {
return;
}
self.preferredContentSize = NSMakeSize(510, 312);
// Get the parameter tree and add observers for any parameters that the UI needs to keep in sync with the AudioUnit
}
- (void)loadView
{
self.view = [AudioUnitView new];
}
- (NSSize)preferredContentSize
{
return NSMakeSize(200, 300);
}
- (NSSize)preferredMaximumSize
{
return NSMakeSize(200, 300);
}
- (NSSize)preferredMinimumSize
{
return NSMakeSize(200, 300);
}
- (AUAudioUnit*)createAudioUnitWithComponentDescription:(AudioComponentDescription)desc error:(NSError**)error {
audioUnit = [[AmuseAudioUnit alloc] initWithComponentDescription:desc error:error];
return audioUnit;
m_audioUnit = [[AmuseAudioUnit alloc] initWithComponentDescription:desc error:error viewController:self];
m_groupBrowserDelegate = [[GroupBrowserDelegate alloc] initWithAudioUnit:m_audioUnit];
dispatch_sync(dispatch_get_main_queue(), ^
{
m_groupBrowser.delegate = m_groupBrowserDelegate;
[m_groupBrowser loadColumnZero];
});
return m_audioUnit;
}
@end

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10116" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10116"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AudioUnitViewController">
<connections>
<outlet property="m_groupBrowser" destination="Pbx-jL-10p" id="BEh-LF-c7K"/>
<outlet property="view" destination="Pbx-jL-10p" id="dfL-uY-h99"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<browser verticalHuggingPriority="750" allowsExpansionToolTips="YES" enabled="YES" hasHorizontalScroller="YES" allowsEmptySelection="YES" defaultColumnWidth="100" minColumnWidth="100" maxVisibleColumns="3" titled="YES" separatesColumns="YES" allowsTypeSelect="YES" sendsActionOnArrowKeys="YES" columnResizingType="auto" id="Pbx-jL-10p">
<rect key="frame" x="0.0" y="0.0" width="510" height="312"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<point key="canvasLocation" x="313" y="621"/>
</browser>
</objects>
</document>

View File

@@ -21,9 +21,18 @@ if (APPLE AND (NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VE
if(EXISTS "${PROV_PROFILE}")
# Extension App
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/AudioUnitViewController.nib
COMMAND ibtool --errors --warnings --notices --module amuse_au --auto-activate-custom-fonts
--target-device mac --minimum-deployment-target 10.11 --output-format human-readable-text --compile
${CMAKE_CURRENT_BINARY_DIR}/AudioUnitViewController.nib
${CMAKE_CURRENT_SOURCE_DIR}/AudioUnitViewController.xib
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/AudioUnitViewController.xib
)
add_executable(amuse-au MACOSX_BUNDLE AudioUnitBackend.hpp AudioUnitBackend.mm
AudioUnitViewController.hpp AudioUnitViewController.mm
AudioGroupFilePresenter.hpp AudioGroupFilePresenter.mm)
AudioGroupFilePresenter.hpp AudioGroupFilePresenter.mm
AudioUnitViewController.nib)
set(APPLE_BUNDLE_ID "io.github.axiodl.Amuse.AudioUnit")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AmuseExtension.entitlements.in
@@ -48,7 +57,7 @@ if (APPLE AND (NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VE
${CMAKE_CURRENT_SOURCE_DIR}/AmuseContainerMainMenu.xib
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/AmuseContainerMainMenu.xib
)
add_executable(amuse-au-container MACOSX_BUNDLE AmuseContainingApp.mm
add_executable(amuse-au-container MACOSX_BUNDLE AmuseContainingApp.hpp AmuseContainingApp.mm
AudioUnitBackend.hpp AudioUnitBackend.mm
AudioUnitViewController.hpp AudioUnitViewController.mm
AudioGroupFilePresenter.hpp AudioGroupFilePresenter.mm
@@ -57,7 +66,7 @@ if (APPLE AND (NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VE
AmuseContainingApp.mm AudioGroupFilePresenter.mm
PROPERTIES COMPILE_FLAGS -fobjc-arc)
target_link_libraries(amuse-au-container amuse boo soxr ${AUDIOUNIT_LIBRARY} ${COREAUDIOKIT_LIBRARY}
${AVFOUNDATION_LIBRARY} ${BOO_SYS_LIBS} logvisor athena-core)
${AVFOUNDATION_LIBRARY} ${ZLIB_LIBRARIES} ${BOO_SYS_LIBS} logvisor athena-core)
set(APPLE_BUNDLE_ID "io.github.axiodl.Amuse")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AmuseContainer.entitlements.in
@@ -69,6 +78,9 @@ if (APPLE AND (NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VE
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "${APPLE_DEV_ID}")
add_custom_command(TARGET amuse-au POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_BINARY_DIR}/AudioUnitViewController.nib"
"$<TARGET_FILE_DIR:amuse-au>/../Resources/AudioUnitViewController.nib"
COMMAND ${CMAKE_COMMAND} -E copy "${PROV_PROFILE}" "$<TARGET_FILE_DIR:amuse-au>/../embedded.provisionprofile"
COMMAND ${CMAKE_COMMAND} -E remove_directory "$<TARGET_FILE_DIR:amuse-au-container>/../PlugIns/amuse-au.appex"
COMMAND ${CMAKE_COMMAND} -E copy_directory "$<TARGET_FILE_DIR:amuse-au>/../.."

View File

@@ -4,6 +4,17 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>amuse-au-container</string>
<key>CFBundleIconFile</key>

View File

@@ -1,7 +1,14 @@
cmake_minimum_required(VERSION 3.0)
project(amuse)
if(EXISTS boo)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/boo AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/athena)
message(STATUS "Preparing standalone build")
if (NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wno-narrowing")
endif()
add_subdirectory(boo)
add_subdirectory(athena)
include_directories(athena/include)
endif()
set(SOURCES
@@ -10,17 +17,19 @@ set(SOURCES
lib/AudioGroupPool.cpp
lib/AudioGroupProject.cpp
lib/AudioGroupSampleDirectory.cpp
lib/DirectoryEnumerator.cpp
lib/Emitter.cpp
lib/Engine.cpp
lib/Envelope.cpp
lib/Listener.cpp
lib/Sequencer.cpp
lib/SoundMacroState.cpp
lib/SongConverter.cpp
lib/SongState.cpp
lib/Voice.cpp
lib/VolumeLUT.cpp
lib/VolumeLUT.c
lib/Submix.cpp
lib/EffectBase.cpp
lib/Studio.cpp
lib/EffectReverb.cpp
lib/EffectChorus.cpp
lib/EffectDelay.cpp
@@ -35,6 +44,7 @@ set(HEADERS
include/amuse/AudioGroupPool.hpp
include/amuse/AudioGroupProject.hpp
include/amuse/AudioGroupSampleDirectory.hpp
include/amuse/DirectoryEnumerator.hpp
include/amuse/Emitter.hpp
include/amuse/Engine.hpp
include/amuse/Entity.hpp
@@ -42,9 +52,11 @@ set(HEADERS
include/amuse/Listener.hpp
include/amuse/Sequencer.hpp
include/amuse/SoundMacroState.hpp
include/amuse/SongConverter.hpp
include/amuse/SongState.hpp
include/amuse/Voice.hpp
include/amuse/Submix.hpp
include/amuse/Studio.hpp
include/amuse/IBackendSubmix.hpp
include/amuse/IBackendVoice.hpp
include/amuse/IBackendVoiceAllocator.hpp
@@ -81,7 +93,17 @@ if(TARGET boo)
# VST Target
add_subdirectory(VST)
# Multi-platform CLI tool
add_executable(amuseplay WIN32 driver/main.cpp)
# Multi-platform CLI tools
# Player
add_executable(amuseplay WIN32 driver/amuseplay.cpp)
target_link_libraries(amuseplay amuse boo ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES})
# Converter
add_executable(amuseconv driver/amuseconv.cpp)
target_link_libraries(amuseconv amuse ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES})
# Renderer
add_executable(amuserender driver/amuserender.cpp)
target_link_libraries(amuserender amuse boo ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES})
endif()

View File

@@ -0,0 +1,587 @@
#include "AudioGroupFilePresenter.hpp"
#include "VSTBackend.hpp"
#include <Shellapi.h>
#include <Shlwapi.h>
namespace amuse
{
static const wchar_t* const GMNames[128] = {L"Acoustic Grand Piano",
L"Bright Acoustic Piano",
L"Electric Grand Piano",
L"Honky-tonk Piano",
L"Rhodes Piano",
L"Chorused Piano",
L"Harpsichord",
L"Clavinet",
L"Celesta",
L"Glockenspiel",
L"Music Box",
L"Vibraphone",
L"Marimba",
L"Xylophone",
L"Tubular Bells",
L"Dulcimer",
L"Drawbar Organ",
L"Percussive Organ",
L"Rock Organ",
L"Church Organ",
L"Reed Organ",
L"Accordion",
L"Harmonica",
L"Tango Accordion",
L"Acoustic Guitar (nylon)",
L"Acoustic Guitar (steel)",
L"Electric Guitar (jazz)",
L"Electric Guitar (clean)",
L"Electric Guitar (muted)",
L"Overdriven Guitar",
L"Distortion Guitar",
L"Guitar Harmonics",
L"Acoustic Bass",
L"Electric Bass (finger)",
L"Electric Bass (pick)",
L"Fretless Bass",
L"Slap Bass 1",
L"Slap Bass 2",
L"Synth Bass 1",
L"Synth Bass 2",
L"Violin",
L"Viola",
L"Cello",
L"Contrabass",
L"Tremelo Strings",
L"Pizzicato Strings",
L"Orchestral Harp",
L"Timpani",
L"String Ensemble 1",
L"String Ensemble 2",
L"SynthStrings 1",
L"SynthStrings 2",
L"Choir Aahs",
L"Voice Oohs",
L"Synth Voice",
L"Orchestra Hit",
L"Trumpet",
L"Trombone",
L"Tuba",
L"Muted Trumpet",
L"French Horn",
L"Brass Section",
L"Synth Brass 1",
L"Synth Brass 2",
L"Soprano Sax",
L"Alto Sax",
L"Tenor Sax",
L"Baritone Sax",
L"Oboe",
L"English Horn",
L"Bassoon",
L"Clarinet",
L"Piccolo",
L"Flute",
L"Recorder",
L"Pan Flute",
L"Bottle Blow",
L"Shakuhachi",
L"Whistle",
L"Ocarina",
L"Lead 1 (square)",
L"Lead 2 (sawtooth)",
L"Lead 3 (calliope lead)",
L"Lead 4 (chiff lead)",
L"Lead 5 (charang)",
L"Lead 6 (voice)",
L"Lead 7 (fifths)",
L"Lead 8 (bass + lead)",
L"Pad 1 (new age)",
L"Pad 2 (warm)",
L"Pad 3 (polysynth)",
L"Pad 4 (choir)",
L"Pad 5 (bowed)",
L"Pad 6 (metallic)",
L"Pad 7 (halo)",
L"Pad 8 (sweep)",
L"FX 1 (rain)",
L"FX 2 (soundtrack)",
L"FX 3 (crystal)",
L"FX 4 (atmosphere)",
L"FX 5 (brightness)",
L"FX 6 (goblins)",
L"FX 7 (echoes)",
L"FX 8 (sci-fi)",
L"Sitar",
L"Banjo",
L"Shamisen",
L"Koto",
L"Kalimba",
L"Bagpipe",
L"Fiddle",
L"Shanai",
L"Tinkle Bell",
L"Agogo",
L"Steel Drums",
L"Woodblock",
L"Taiko Drum",
L"Melodic Tom",
L"Synth Drum",
L"Reverse Cymbal",
L"Guitar Fret Noise",
L"Breath Noise",
L"Seashore",
L"Bird Tweet",
L"Telephone Ring",
L"Helicopter",
L"Applause",
L"Gunshot"};
static const wchar_t* const GMPercNames[128] = {
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, L"Acoustic Bass Drum",
L"Bass Drum 1", L"Side Stick", L"Acoustic Snare", L"Hand Clap", L"Electric Snare",
L"Low Floor Tom", L"Closed Hi-Hat", L"High Floor Tom", L"Pedal Hi-Hat", L"Low Tom",
L"Open Hi-Hat", L"Low-Mid Tom", L"Hi-Mid Tom", L"Crash Cymbal 1", L"High Tom",
L"Ride Cymbal 1", L"Chinese Cymbal", L"Ride Bell", L"Tambourine", L"Splash Cymbal",
L"Cowbell", L"Crash Cymbal 2", L"Vibraslap", L"Ride Cymbal 2", L"Hi Bongo",
L"Low Bongo", L"Mute Hi Conga", L"Open Hi Conga", L"Low Conga", L"High Timbale",
L"Low Timbale", L"High Agogo", L"Low Agogo", L"Cabasa", L"Maracas",
L"Short Whistle", L"Long Whistle", L"Short Guiro", L"Long Guiro", L"Claves",
L"Hi Wood Block", L"Low Wood Block", L"Mute Cuica", L"Open Cuica", L"Mute Triangle",
L"Open Triangle"};
bool AudioGroupDataCollection::loadProj()
{
std::wstring path = m_path + L"\\proj";
athena::io::FileReader r(path, 1024 * 32, false);
if (r.hasError())
return false;
std::vector<uint8_t>& ret = m_projData;
size_t len = r.length();
ret.resize(len);
r.readUBytesToBuf(ret.data(), len);
return ret.size() != 0;
}
bool AudioGroupDataCollection::loadPool()
{
std::wstring path = m_path + L"\\pool";
athena::io::FileReader r(path, 1024 * 32, false);
if (r.hasError())
return false;
std::vector<uint8_t>& ret = m_poolData;
size_t len = r.length();
ret.resize(len);
r.readUBytesToBuf(ret.data(), len);
return ret.size() != 0;
}
bool AudioGroupDataCollection::loadSdir()
{
std::wstring path = m_path + L"\\sdir";
athena::io::FileReader r(path, 1024 * 32, false);
if (r.hasError())
return false;
std::vector<uint8_t>& ret = m_sdirData;
size_t len = r.length();
ret.resize(len);
r.readUBytesToBuf(ret.data(), len);
return ret.size() != 0;
}
bool AudioGroupDataCollection::loadSamp()
{
std::wstring path = m_path + L"\\samp";
athena::io::FileReader r(path, 1024 * 32, false);
if (r.hasError())
return false;
std::vector<uint8_t>& ret = m_sampData;
size_t len = r.length();
ret.resize(len);
r.readUBytesToBuf(ret.data(), len);
return ret.size() != 0;
}
bool AudioGroupDataCollection::loadMeta()
{
std::wstring path = m_path + L"\\meta";
athena::io::FileReader r(path, 1024 * 32, false);
if (r.hasError())
return false;
std::experimental::optional<MetaData>& ret = m_metaData;
ret.emplace(r);
return ret.operator bool();
}
AudioGroupDataCollection::AudioGroupDataCollection(const std::wstring& path, const std::wstring& name)
: m_path(path), m_name(name)
{
}
bool AudioGroupDataCollection::_attemptLoad()
{
if (m_metaData && m_loadedData && m_loadedGroup)
return true;
if (!loadProj())
return false;
if (!loadPool())
return false;
if (!loadSdir())
return false;
if (!loadSamp())
return false;
if (!loadMeta())
return false;
return _indexData();
}
bool AudioGroupDataCollection::_indexData()
{
switch (m_metaData->fmt)
{
case amuse::DataFormat::GCN:
default:
m_loadedData.emplace(m_projData.data(), m_projData.size(), m_poolData.data(), m_poolData.size(),
m_sdirData.data(), m_sdirData.size(), m_sampData.data(), m_sampData.size(),
amuse::GCNDataTag{});
break;
case amuse::DataFormat::N64:
m_loadedData.emplace(m_projData.data(), m_projData.size(), m_poolData.data(), m_poolData.size(),
m_sdirData.data(), m_sdirData.size(), m_sampData.data(), m_sampData.size(),
m_metaData->absOffs, amuse::N64DataTag{});
break;
case amuse::DataFormat::PC:
m_loadedData.emplace(m_projData.data(), m_projData.size(), m_poolData.data(), m_poolData.size(),
m_sdirData.data(), m_sdirData.size(), m_sampData.data(), m_sampData.size(),
m_metaData->absOffs, amuse::PCDataTag{});
break;
}
return m_loadedData.operator bool();
}
void AudioGroupDataCollection::addToEngine(amuse::Engine& engine)
{
m_loadedGroup = engine.addAudioGroup(*m_loadedData);
m_groupTokens.clear();
if (m_loadedGroup)
{
m_groupTokens.reserve(m_loadedGroup->getProj().songGroups().size() +
m_loadedGroup->getProj().sfxGroups().size());
{
const auto& songGroups = m_loadedGroup->getProj().songGroups();
std::map<int, const amuse::SongGroupIndex*> sortGroups;
for (const auto& pair : songGroups)
sortGroups[pair.first] = &pair.second;
for (const auto& pair : sortGroups)
m_groupTokens.emplace_back(pair.first, pair.second);
}
{
const auto& sfxGroups = m_loadedGroup->getProj().sfxGroups();
std::map<int, const amuse::SFXGroupIndex*> sortGroups;
for (const auto& pair : sfxGroups)
sortGroups[pair.first] = &pair.second;
for (const auto& pair : sortGroups)
m_groupTokens.emplace_back(pair.first, pair.second);
}
}
}
void AudioGroupDataCollection::removeFromEngine(amuse::Engine& engine) const { engine.removeAudioGroup(*m_loadedData); }
AudioGroupCollection::AudioGroupCollection(const std::wstring& path, const std::wstring& name)
: m_path(path), m_name(name)
{
}
void AudioGroupCollection::addCollection(
std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>>&& collection)
{
for (std::pair<std::wstring, amuse::IntrusiveAudioGroupData>& pair : collection)
{
std::wstring collectionPath = m_path + L'\\' + pair.first;
amuse::IntrusiveAudioGroupData& dataIn = pair.second;
auto search = m_groups.find(pair.first);
if (search == m_groups.end())
{
search =
m_groups.emplace(pair.first, std::make_unique<AudioGroupDataCollection>(collectionPath, pair.first))
.first;
}
AudioGroupDataCollection& dataCollection = *search->second;
dataCollection.m_projData.resize(dataIn.getProjSize());
memmove(dataCollection.m_projData.data(), dataIn.getProj(), dataIn.getProjSize());
dataCollection.m_poolData.resize(dataIn.getPoolSize());
memmove(dataCollection.m_poolData.data(), dataIn.getPool(), dataIn.getPoolSize());
dataCollection.m_sdirData.resize(dataIn.getSdirSize());
memmove(dataCollection.m_sdirData.data(), dataIn.getSdir(), dataIn.getSdirSize());
dataCollection.m_sampData.resize(dataIn.getSampSize());
memmove(dataCollection.m_sampData.data(), dataIn.getSamp(), dataIn.getSampSize());
dataCollection.m_metaData.emplace(dataIn.getDataFormat(), dataIn.getAbsoluteProjOffsets(), true);
dataCollection._indexData();
}
}
void AudioGroupCollection::update(AudioGroupFilePresenter& presenter)
{
std::wstring path = m_path + L"\\*";
WIN32_FIND_DATAW d;
HANDLE dir = FindFirstFileW(path.c_str(), &d);
if (dir == INVALID_HANDLE_VALUE)
return;
do
{
if (!wcscmp(d.cFileName, L".") || !wcscmp(d.cFileName, L".."))
continue;
if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
std::wstring nameStr(d.cFileName);
auto search = m_groups.find(nameStr);
if (search == m_groups.end())
{
search =
m_groups
.emplace(nameStr, std::make_unique<AudioGroupDataCollection>(m_path + L'\\' + nameStr, nameStr))
.first;
search->second->_attemptLoad();
}
}
} while (FindNextFileW(dir, &d));
FindClose(dir);
}
void AudioGroupFilePresenter::update()
{
std::wstring path = m_backend.getUserDir() + L"\\*";
std::map<std::wstring, std::unique_ptr<AudioGroupCollection>>& theMap = m_audioGroupCollections;
WIN32_FIND_DATAW d;
HANDLE dir = FindFirstFileW(path.c_str(), &d);
if (dir == INVALID_HANDLE_VALUE)
return;
do
{
if (!wcscmp(d.cFileName, L".") || !wcscmp(d.cFileName, L".."))
continue;
if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
std::wstring nameStr(d.cFileName);
auto search = theMap.find(nameStr);
if (search == theMap.end())
{
search = theMap
.emplace(nameStr, std::make_unique<AudioGroupCollection>(
m_backend.getUserDir() + L'\\' + nameStr, nameStr))
.first;
search->second->update(*this);
}
}
} while (FindNextFileW(dir, &d));
FindClose(dir);
}
void AudioGroupFilePresenter::addCollection(
const std::wstring& name, std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>>&& collection)
{
std::wstring path = m_backend.getUserDir() + L'\\' + name;
AudioGroupCollection& insert =
*m_audioGroupCollections.emplace(name, std::make_unique<AudioGroupCollection>(path, name)).first->second;
CreateDirectory(insert.m_path.c_str(), nullptr);
insert.addCollection(std::move(collection));
for (std::pair<const std::wstring, std::unique_ptr<AudioGroupDataCollection>>& pair : insert.m_groups)
{
std::wstring collectionPath = insert.m_path + L'\\' + pair.first;
CreateDirectory(collectionPath.c_str(), nullptr);
FILE* fp = _wfopen((collectionPath + L"\\proj").c_str(), L"wb");
if (fp)
{
fwrite(pair.second->m_projData.data(), 1, pair.second->m_projData.size(), fp);
fclose(fp);
}
fp = _wfopen((collectionPath + L"\\pool").c_str(), L"wb");
if (fp)
{
fwrite(pair.second->m_poolData.data(), 1, pair.second->m_poolData.size(), fp);
fclose(fp);
}
fp = _wfopen((collectionPath + L"\\sdir").c_str(), L"wb");
if (fp)
{
fwrite(pair.second->m_sdirData.data(), 1, pair.second->m_sdirData.size(), fp);
fclose(fp);
}
fp = _wfopen((collectionPath + L"\\samp").c_str(), L"wb");
if (fp)
{
fwrite(pair.second->m_sampData.data(), 1, pair.second->m_sampData.size(), fp);
fclose(fp);
}
fp = _wfopen((collectionPath + L"\\meta").c_str(), L"wb");
if (fp)
{
fwrite(&*pair.second->m_metaData, 1, sizeof(*pair.second->m_metaData), fp);
fclose(fp);
}
}
}
void AudioGroupFilePresenter::removeCollection(unsigned idx)
{
if (idx < m_iteratorVec.size())
{
CollectionIterator& it = m_iteratorVec[idx];
std::wstring collectionPath = it->second->m_path + L'\0';
SHFILEOPSTRUCT op = {};
op.wFunc = FO_DELETE;
op.pFrom = collectionPath.c_str();
op.fFlags = FOF_NO_UI;
SHFileOperation(&op);
m_audioGroupCollections.erase(it);
}
}
void AudioGroupCollection::populateFiles(VSTEditor& editor, HTREEITEM colHandle, size_t parentIdx)
{
TVINSERTSTRUCT ins = {};
ins.item.mask = TVIF_TEXT | TVIF_PARAM;
ins.hParent = colHandle;
ins.hInsertAfter = TVI_LAST;
m_iteratorVec.clear();
m_iteratorVec.reserve(m_groups.size());
for (auto it = m_groups.begin(); it != m_groups.end(); ++it)
{
ins.item.pszText = LPWSTR(it->first.c_str());
ins.item.lParam = LPARAM(0x80000000 | (parentIdx << 16) | m_iteratorVec.size());
HTREEITEM item = TreeView_InsertItem(editor.m_collectionTree, &ins);
if (editor.m_selCollectionIdx == parentIdx && editor.m_selFileIdx == m_iteratorVec.size())
editor.m_deferredCollectionSel = item;
m_iteratorVec.push_back(it);
}
}
void AudioGroupFilePresenter::populateCollectionColumn(VSTEditor& editor)
{
TreeView_DeleteAllItems(editor.m_collectionTree);
TVINSERTSTRUCT ins = {};
ins.hParent = TVI_ROOT;
ins.hInsertAfter = TVI_LAST;
ins.item.mask = TVIF_CHILDREN | TVIF_TEXT | TVIF_PARAM;
m_iteratorVec.clear();
m_iteratorVec.reserve(m_audioGroupCollections.size());
for (auto it = m_audioGroupCollections.begin(); it != m_audioGroupCollections.end(); ++it)
{
ins.item.cChildren = it->second->m_groups.size() ? 1 : 0;
ins.item.pszText = LPWSTR(it->first.c_str());
ins.item.lParam = LPARAM(m_iteratorVec.size() << 16);
HTREEITEM item = TreeView_InsertItem(editor.m_collectionTree, &ins);
it->second->populateFiles(editor, item, m_iteratorVec.size());
if (editor.m_selCollectionIdx == m_iteratorVec.size() && editor.m_selFileIdx == -1)
editor.m_deferredCollectionSel = item;
m_iteratorVec.push_back(it);
}
}
void AudioGroupFilePresenter::populateGroupColumn(VSTEditor& editor, int collectionIdx, int fileIdx)
{
LVITEM item = {};
item.mask = LVIF_TEXT;
ListView_DeleteAllItems(editor.m_groupListView);
ListView_DeleteAllItems(editor.m_pageListView);
if (collectionIdx < m_iteratorVec.size())
{
CollectionIterator& it = m_iteratorVec[collectionIdx];
if (fileIdx < it->second->m_iteratorVec.size())
{
size_t idx = 0;
AudioGroupCollection::GroupIterator& git = it->second->m_iteratorVec[fileIdx];
for (AudioGroupDataCollection::GroupToken& gtok : git->second->m_groupTokens)
{
wchar_t name[256];
wnsprintf(name, 256, L"%d (%s)", gtok.m_groupId, gtok.m_song ? L"Song" : L"SFX");
item.pszText = name;
item.iItem = idx++;
ListView_InsertItem(editor.m_groupListView, &item);
}
}
}
}
void AudioGroupFilePresenter::populatePageColumn(VSTEditor& editor, int collectionIdx, int fileIdx, int groupIdx)
{
LVITEM item = {};
item.mask = LVIF_TEXT | LVIF_PARAM;
ListView_DeleteAllItems(editor.m_pageListView);
if (collectionIdx < m_iteratorVec.size())
{
CollectionIterator& it = m_iteratorVec[collectionIdx];
if (fileIdx < it->second->m_iteratorVec.size())
{
AudioGroupCollection::GroupIterator& git = it->second->m_iteratorVec[fileIdx];
if (groupIdx < git->second->m_groupTokens.size())
{
AudioGroupDataCollection::GroupToken& groupTok = git->second->m_groupTokens[groupIdx];
if (groupTok.m_song)
{
std::map<uint8_t, const amuse::SongGroupIndex::PageEntry*> sortPages;
for (auto& pair : groupTok.m_song->m_normPages)
sortPages[pair.first] = pair.second;
size_t idx = 0;
for (auto& pair : sortPages)
{
wchar_t name[256];
wnsprintf(name, 256, L"%d (%s)", pair.first,
GMNames[pair.first] ? GMNames[pair.first] : L"???");
item.pszText = name;
item.iItem = idx++;
item.lParam = pair.first;
ListView_InsertItem(editor.m_pageListView, &item);
}
sortPages.clear();
for (auto& pair : groupTok.m_song->m_drumPages)
sortPages[pair.first] = pair.second;
for (auto& pair : sortPages)
{
wchar_t name[256];
wnsprintf(name, 256, L"%d (%s)", pair.first,
GMPercNames[pair.first] ? GMPercNames[pair.first] : L"???");
item.pszText = name;
item.iItem = idx++;
item.lParam = 0x80000000 | pair.first;
ListView_InsertItem(editor.m_pageListView, &item);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,116 @@
#ifndef __AMUSE_AUDIOGROUPFILEPRESENTER_HPP__
#define __AMUSE_AUDIOGROUPFILEPRESENTER_HPP__
#include <map>
#include <memory>
#include "optional.hpp"
#include <amuse/amuse.hpp>
#include <athena/FileReader.hpp>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <CommCtrl.h>
namespace amuse
{
class VSTBackend;
class VSTEditor;
class AudioGroupFilePresenter;
struct AudioGroupDataCollection
{
std::wstring m_path;
std::wstring m_name;
std::vector<uint8_t> m_projData;
std::vector<uint8_t> m_poolData;
std::vector<uint8_t> m_sdirData;
std::vector<uint8_t> m_sampData;
struct MetaData
{
amuse::DataFormat fmt;
uint32_t absOffs;
uint32_t active;
MetaData(amuse::DataFormat fmtIn, uint32_t absOffsIn, uint32_t activeIn)
: fmt(fmtIn), absOffs(absOffsIn), active(activeIn)
{
}
MetaData(athena::io::FileReader& r)
: fmt(amuse::DataFormat(r.readUint32Little())), absOffs(r.readUint32Little()), active(r.readUint32Little())
{
}
};
std::experimental::optional<MetaData> m_metaData;
std::experimental::optional<amuse::AudioGroupData> m_loadedData;
const amuse::AudioGroup* m_loadedGroup;
struct GroupToken
{
int m_groupId;
const amuse::SongGroupIndex* m_song = nullptr;
const amuse::SFXGroupIndex* m_sfx = nullptr;
GroupToken(int id, const amuse::SongGroupIndex* song) : m_groupId(id), m_song(song) {}
GroupToken(int id, const amuse::SFXGroupIndex* sfx) : m_groupId(id), m_sfx(sfx) {}
};
std::vector<GroupToken> m_groupTokens;
bool loadProj();
bool loadPool();
bool loadSdir();
bool loadSamp();
bool loadMeta();
AudioGroupDataCollection(const std::wstring& path, const std::wstring& name);
bool isDataComplete() const
{
return m_projData.size() && m_poolData.size() && m_sdirData.size() && m_sampData.size() && m_metaData;
}
bool _attemptLoad();
bool _indexData();
void addToEngine(amuse::Engine& engine);
void removeFromEngine(amuse::Engine& engine) const;
};
struct AudioGroupCollection
{
using GroupIterator = std::map<std::wstring, std::unique_ptr<AudioGroupDataCollection>>::iterator;
std::wstring m_path;
std::wstring m_name;
std::map<std::wstring, std::unique_ptr<AudioGroupDataCollection>> m_groups;
std::vector<GroupIterator> m_iteratorVec;
AudioGroupCollection(const std::wstring& path, const std::wstring& name);
void addCollection(std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>>&& collection);
void update(AudioGroupFilePresenter& presenter);
void populateFiles(VSTEditor& editor, HTREEITEM colHandle, size_t parentIdx);
};
class AudioGroupFilePresenter
{
friend class VSTBackend;
public:
using CollectionIterator = std::map<std::wstring, std::unique_ptr<AudioGroupCollection>>::iterator;
private:
VSTBackend& m_backend;
std::map<std::wstring, std::unique_ptr<AudioGroupCollection>> m_audioGroupCollections;
std::vector<CollectionIterator> m_iteratorVec;
public:
AudioGroupFilePresenter(VSTBackend& backend) : m_backend(backend) {}
void update();
void populateCollectionColumn(VSTEditor& editor);
void populateGroupColumn(VSTEditor& editor, int collectionIdx, int fileIdx);
void populatePageColumn(VSTEditor& editor, int collectionIdx, int fileIdx, int groupIdx);
void addCollection(const std::wstring& name,
std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>>&& collection);
void removeCollection(unsigned idx);
VSTBackend& getBackend() { return m_backend; }
};
}
#endif // __AMUSE_AUDIOGROUPFILEPRESENTER_HPP__

View File

@@ -0,0 +1,18 @@
set(VST3_SDK_ROOT "" CACHE PATH "Path to VST 3.x SDK directory containing 'public.sdk' and 'plugininterfaces'")
if (WIN32 AND (EXISTS ${VST3_SDK_ROOT}))
message(STATUS "Found VST SDK; building plugin")
include_directories(${VST3_SDK_ROOT} ${VST3_SDK_ROOT}/public.sdk/source/vst2.x)
set(VST2_DIR ${VST3_SDK_ROOT}/public.sdk/source/vst2.x)
add_definitions(${BOO_SYS_DEFINES})
add_library(amuse-vst SHARED
VSTBackend.hpp VSTBackend.cpp
VSTEditor.hpp VSTEditor.cpp
AudioGroupFilePresenter.hpp AudioGroupFilePresenter.cpp
${VST2_DIR}/vstplugmain.cpp
${VST2_DIR}/audioeffect.cpp
${VST2_DIR}/audioeffectx.cpp
FileOpenDialog.hpp FileOpenDialog.cpp)
target_link_libraries(amuse-vst amuse boo soxr ${ZLIB_LIBRARIES} Winmm soxr
Msimg32 Shlwapi logvisor athena-core)
set_target_properties(amuse-vst PROPERTIES LINK_FLAGS "/EXPORT:VSTPluginMain")
endif()

233
VST/FileOpenDialog.cpp Normal file
View File

@@ -0,0 +1,233 @@
#include "FileOpenDialog.hpp"
#define WIN32_LEAN_AND_MEAN
#include <windows.h> // For common windows data types and function headers
#define STRICT_TYPED_ITEMIDS
#include <objbase.h> // For COM headers
#include <shobjidl.h> // for IFileDialogEvents and IFileDialogControlEvents
#include <shlwapi.h>
#include <knownfolders.h> // for KnownFolder APIs/datatypes/function headers
#include <propvarutil.h> // for PROPVAR-related functions
#include <propkey.h> // for the Property key APIs/datatypes
#include <propidl.h> // for the Property System APIs
#include <strsafe.h> // for StringCchPrintfW
#include <shtypes.h> // for COMDLG_FILTERSPEC
#include <new>
// Controls
#define CONTROL_GROUP 2000
#define CONTROL_RADIOBUTTONLIST 2
#define CONTROL_RADIOBUTTON1 1
#define CONTROL_RADIOBUTTON2 2 // It is OK for this to have the same IDas CONTROL_RADIOBUTTONLIST,
// because it is a child control under CONTROL_RADIOBUTTONLIST
// IDs for the Task Dialog Buttons
#define IDC_BASICFILEOPEN 100
#define IDC_ADDITEMSTOCUSTOMPLACES 101
#define IDC_ADDCUSTOMCONTROLS 102
#define IDC_SETDEFAULTVALUESFORPROPERTIES 103
#define IDC_WRITEPROPERTIESUSINGHANDLERS 104
#define IDC_WRITEPROPERTIESWITHOUTUSINGHANDLERS 105
HWND ghMainWnd = 0;
HINSTANCE ghAppInst = 0;
RECT winRect;
class CDialogEventHandler : public IFileDialogEvents, public IFileDialogControlEvents
{
public:
// IUnknown methods
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] = {
QITABENT(CDialogEventHandler, IFileDialogEvents),
QITABENT(CDialogEventHandler, IFileDialogControlEvents),
{0},
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_cRef); }
IFACEMETHODIMP_(ULONG) Release()
{
long cRef = InterlockedDecrement(&_cRef);
if (!cRef)
delete this;
return cRef;
}
// IFileDialogEvents methods
IFACEMETHODIMP OnFileOk(IFileDialog*) { return S_OK; };
IFACEMETHODIMP OnFolderChange(IFileDialog*) { return S_OK; };
IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return S_OK; };
IFACEMETHODIMP OnHelp(IFileDialog*) { return S_OK; };
IFACEMETHODIMP OnSelectionChange(IFileDialog*) { return S_OK; };
IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return S_OK; };
IFACEMETHODIMP OnTypeChange(IFileDialog* pfd);
IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return S_OK; };
// IFileDialogControlEvents methods
IFACEMETHODIMP OnItemSelected(IFileDialogCustomize* pfdc, DWORD dwIDCtl, DWORD dwIDItem);
IFACEMETHODIMP OnButtonClicked(IFileDialogCustomize*, DWORD) { return S_OK; };
IFACEMETHODIMP OnCheckButtonToggled(IFileDialogCustomize*, DWORD, BOOL) { return S_OK; };
IFACEMETHODIMP OnControlActivating(IFileDialogCustomize*, DWORD) { return S_OK; };
CDialogEventHandler() : _cRef(1){};
private:
~CDialogEventHandler(){};
long _cRef;
};
HRESULT CDialogEventHandler_CreateInstance(REFIID riid, void** ppv);
std::wstring openDB()
{
std::wstring ret;
CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
// Cocreate the file open dialog object
IFileDialog* pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Stuff needed for later
const COMDLG_FILTERSPEC rgFExt[] = {{L"Audio Group Archive (*.*)", L"*.*"}};
// Create event handling
IFileDialogEvents* pfde = NULL;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook the event handler
DWORD dwCookie;
hr = pfd->Advise(pfde, &dwCookie);
if (SUCCEEDED(hr))
{
// Set options for the dialog
DWORD dwFlags;
// Get options first so we do not override
hr = pfd->GetOptions(&dwFlags);
if (SUCCEEDED(hr))
{
// Get shell items only
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
if (SUCCEEDED(hr))
{
// Types of files to display (not default)
hr = pfd->SetFileTypes(ARRAYSIZE(rgFExt), rgFExt);
if (SUCCEEDED(hr))
{
// Set default file type to display
// hr = pfd->SetDefaultExtension(L"sqlite");
// if (SUCCEEDED(hr))
//{
// Show dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
// Get the result once the user clicks on open
IShellItem* result;
hr = pfd->GetResult(&result);
if (SUCCEEDED(hr))
{
// Print out the file name
PWSTR fName = NULL;
hr = result->GetDisplayName(SIGDN_FILESYSPATH, &fName);
if (SUCCEEDED(hr))
{
ret.assign(fName);
CoTaskMemFree(fName);
}
result->Release();
}
}
//}
}
}
}
}
pfd->Unadvise(dwCookie);
}
pfde->Release();
}
pfd->Release();
return ret;
}
HRESULT CDialogEventHandler_CreateInstance(REFIID riid, void** ppv)
{
*ppv = NULL;
CDialogEventHandler* pDialogEventHandler = new (std::nothrow) CDialogEventHandler();
HRESULT hr = pDialogEventHandler ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pDialogEventHandler->QueryInterface(riid, ppv);
pDialogEventHandler->Release();
}
return hr;
}
HRESULT CDialogEventHandler::OnTypeChange(IFileDialog* pfd)
{
IFileSaveDialog* pfsd;
HRESULT hr = pfd->QueryInterface(&pfsd);
if (SUCCEEDED(hr))
{
UINT uIndex;
hr = pfsd->GetFileTypeIndex(&uIndex); // index of current file-type
if (SUCCEEDED(hr))
{
IPropertyDescriptionList* pdl = NULL;
}
pfsd->Release();
}
return hr;
}
// IFileDialogControlEvents
// This method gets called when an dialog control item selection happens (radio-button selection. etc).
// For sample sake, let's react to this event by changing the dialog title.
HRESULT CDialogEventHandler::OnItemSelected(IFileDialogCustomize* pfdc, DWORD dwIDCtl, DWORD dwIDItem)
{
IFileDialog* pfd = NULL;
HRESULT hr = pfdc->QueryInterface(&pfd);
if (SUCCEEDED(hr))
{
if (dwIDCtl == CONTROL_RADIOBUTTONLIST)
{
switch (dwIDItem)
{
case CONTROL_RADIOBUTTON1:
hr = pfd->SetTitle(L"Longhorn Dialog");
break;
case CONTROL_RADIOBUTTON2:
hr = pfd->SetTitle(L"Vista Dialog");
break;
}
}
pfd->Release();
}
return hr;
}

8
VST/FileOpenDialog.hpp Normal file
View File

@@ -0,0 +1,8 @@
#ifndef __AMUSE_FILEOPENDIALOG_HPP__
#define __AMUSE_FILEOPENDIALOG_HPP__
#include <string>
std::wstring openDB();
#endif // __AMUSE_FILEOPENDIALOG_HPP__

394
VST/VSTBackend.cpp Normal file
View File

@@ -0,0 +1,394 @@
#include "VSTBackend.hpp"
#include "audiodev/AudioVoiceEngine.hpp"
#include <Shlobj.h>
#include <logvisor/logvisor.hpp>
#undef min
#undef max
struct VSTVoiceEngine : boo::BaseAudioVoiceEngine
{
std::vector<float> m_interleavedBuf;
float** m_outputData = nullptr;
size_t m_renderFrames = 0;
size_t m_curBufFrame = 0;
boo::AudioChannelSet _getAvailableSet() { return boo::AudioChannelSet::Stereo; }
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const { return {}; }
boo::ReceiveFunctor* m_midiReceiver = nullptr;
struct MIDIIn : public boo::IMIDIIn
{
MIDIIn(bool virt, boo::ReceiveFunctor&& receiver) : IMIDIIn(virt, std::move(receiver)) {}
std::string description() const { return "VST MIDI"; }
};
std::unique_ptr<boo::IMIDIIn> newVirtualMIDIIn(boo::ReceiveFunctor&& receiver)
{
std::unique_ptr<boo::IMIDIIn> ret = std::make_unique<MIDIIn>(true, std::move(receiver));
m_midiReceiver = &ret->m_receiver;
return ret;
}
std::unique_ptr<boo::IMIDIOut> newVirtualMIDIOut() { return {}; }
std::unique_ptr<boo::IMIDIInOut> newVirtualMIDIInOut(boo::ReceiveFunctor&& receiver) { return {}; }
std::unique_ptr<boo::IMIDIIn> newRealMIDIIn(const char* name, boo::ReceiveFunctor&& receiver) { return {}; }
std::unique_ptr<boo::IMIDIOut> newRealMIDIOut(const char* name) { return {}; }
std::unique_ptr<boo::IMIDIInOut> newRealMIDIInOut(const char* name, boo::ReceiveFunctor&& receiver) { return {}; }
bool useMIDILock() const { return false; }
VSTVoiceEngine()
{
m_mixInfo.m_periodFrames = 1024;
m_mixInfo.m_sampleRate = 44100.0;
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
m_mixInfo.m_bitsPerSample = 32;
_buildAudioRenderClient();
}
void _buildAudioRenderClient()
{
m_mixInfo.m_channels = _getAvailableSet();
unsigned chCount = ChannelCount(m_mixInfo.m_channels);
m_5msFrames = m_mixInfo.m_sampleRate * 5 / 1000;
m_curBufFrame = m_5msFrames;
m_mixInfo.m_periodFrames = m_5msFrames;
m_interleavedBuf.resize(m_5msFrames * 2);
boo::ChannelMap& chMapOut = m_mixInfo.m_channelMap;
chMapOut.m_channelCount = 2;
chMapOut.m_channels[0] = boo::AudioChannel::FrontLeft;
chMapOut.m_channels[1] = boo::AudioChannel::FrontRight;
while (chMapOut.m_channelCount < chCount)
chMapOut.m_channels[chMapOut.m_channelCount++] = boo::AudioChannel::Unknown;
}
void _rebuildAudioRenderClient(double sampleRate, size_t periodFrames)
{
m_mixInfo.m_periodFrames = periodFrames;
m_mixInfo.m_sampleRate = sampleRate;
_buildAudioRenderClient();
for (boo::AudioVoice* vox : m_activeVoices)
vox->_resetSampleRate(vox->m_sampleRateIn);
for (boo::AudioSubmix* smx : m_activeSubmixes)
smx->_resetOutputSampleRate();
}
void pumpAndMixVoices()
{
for (size_t f = 0; f < m_renderFrames;)
{
if (m_curBufFrame == m_5msFrames)
{
_pumpAndMixVoices(m_5msFrames, m_interleavedBuf.data());
m_curBufFrame = 0;
}
size_t remRenderFrames = std::min(m_renderFrames - f, m_5msFrames - m_curBufFrame);
if (remRenderFrames)
{
for (size_t i = 0; i < 2; ++i)
{
float* bufOut = m_outputData[i];
for (size_t lf = 0; lf < remRenderFrames; ++lf)
bufOut[f + lf] = m_interleavedBuf[(m_curBufFrame + lf) * 2 + i];
}
m_curBufFrame += remRenderFrames;
f += remRenderFrames;
}
}
}
double getCurrentSampleRate() const { return m_mixInfo.m_sampleRate; }
};
namespace amuse
{
#define kBackendID CCONST('a', 'm', 'u', 's')
static logvisor::Module Log("amuse::AudioUnitBackend");
VSTBackend::VSTBackend(audioMasterCallback cb) : AudioEffectX(cb, 0, 0), m_filePresenter(*this), m_editor(*this)
{
isSynth();
setUniqueID(kBackendID);
setNumInputs(0);
setNumOutputs(2);
setEditor(&m_editor);
sizeWindow(600, 420);
programsAreChunks();
m_booBackend = std::make_unique<VSTVoiceEngine>();
m_voxAlloc.emplace(*m_booBackend);
m_engine.emplace(*m_voxAlloc);
WCHAR path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, path)))
{
m_userDir = std::wstring(path) + L"\\Amuse";
CreateDirectory(m_userDir.c_str(), nullptr);
}
m_filePresenter.update();
}
VSTBackend::~VSTBackend() { editor = nullptr; }
AEffEditor* VSTBackend::getEditor() { return &m_editor; }
VstInt32 VSTBackend::processEvents(VstEvents* events)
{
std::unique_lock<std::mutex> lk(m_lock);
VSTVoiceEngine& engine = static_cast<VSTVoiceEngine&>(*m_booBackend);
/* Handle group load request */
if (m_curGroup != m_reqGroup)
{
m_curGroup = m_reqGroup;
if (m_curSeq)
m_curSeq->kill();
m_curSeq = m_engine->seqPlay(m_reqGroup, -1, nullptr);
m_editor.reselectPage();
}
if (engine.m_midiReceiver)
{
for (VstInt32 i = 0; i < events->numEvents; ++i)
{
VstMidiEvent* evt = reinterpret_cast<VstMidiEvent*>(events->events[i]);
if (evt->type == kVstMidiType)
{
if (m_routeChannel != -1)
{
evt->midiData[0] &= ~0xf;
evt->midiData[0] |= m_routeChannel & 0xf;
}
(*engine.m_midiReceiver)(
std::vector<uint8_t>(std::cbegin(evt->midiData), std::cbegin(evt->midiData) + evt->byteSize),
(m_curFrame + evt->deltaFrames) / sampleRate);
}
}
}
return 1;
}
void VSTBackend::processReplacing(float**, float** outputs, VstInt32 sampleFrames)
{
std::unique_lock<std::mutex> lk(m_lock);
VSTVoiceEngine& engine = static_cast<VSTVoiceEngine&>(*m_booBackend);
/* Output buffers */
engine.m_renderFrames = sampleFrames;
engine.m_outputData = outputs;
m_engine->pumpEngine();
m_curFrame += sampleFrames;
}
VstInt32 VSTBackend::canDo(char* text)
{
VstInt32 returnCode = 0;
if (!strcmp(text, "receiveVstEvents"))
returnCode = 1;
else if (!strcmp(text, "receiveVstMidiEvent"))
returnCode = 1;
return returnCode;
}
VstPlugCategory VSTBackend::getPlugCategory() { return kPlugCategSynth; }
bool VSTBackend::getEffectName(char* text)
{
strcpy(text, "Amuse");
return true;
}
bool VSTBackend::getProductString(char* text)
{
strcpy(text, "Amuse");
return true;
}
bool VSTBackend::getVendorString(char* text)
{
strcpy(text, "AxioDL");
return true;
}
bool VSTBackend::getProgramNameIndexed(VstInt32 category, VstInt32 index, char* text)
{
strcpy(text, "Sampler");
return true;
}
bool VSTBackend::getOutputProperties(VstInt32 index, VstPinProperties* properties)
{
bool returnCode = false;
if (index == 0)
{
strcpy(properties->label, "Amuse Out");
properties->flags = kVstPinIsStereo | kVstPinIsActive;
properties->arrangementType = kSpeakerArrStereo;
returnCode = true;
}
return returnCode;
}
VstInt32 VSTBackend::getNumMidiInputChannels() { return 1; }
void VSTBackend::setSampleRate(float sampleRate)
{
AudioEffectX::setSampleRate(sampleRate);
VSTVoiceEngine& engine = static_cast<VSTVoiceEngine&>(*m_booBackend);
engine._rebuildAudioRenderClient(sampleRate, engine.mixInfo().m_periodFrames);
}
void VSTBackend::setBlockSize(VstInt32 blockSize)
{
AudioEffectX::setBlockSize(blockSize);
VSTVoiceEngine& engine = static_cast<VSTVoiceEngine&>(*m_booBackend);
engine._rebuildAudioRenderClient(engine.mixInfo().m_sampleRate, blockSize);
}
void VSTBackend::loadGroupFile(int collectionIdx, int fileIdx)
{
std::unique_lock<std::mutex> lk(m_lock);
if (m_curSeq)
{
m_curSeq->kill();
m_curSeq.reset();
m_curGroup = -1;
m_reqGroup = -1;
}
if (collectionIdx < m_filePresenter.m_iteratorVec.size())
{
AudioGroupFilePresenter::CollectionIterator& it = m_filePresenter.m_iteratorVec[collectionIdx];
if (fileIdx < it->second->m_iteratorVec.size())
{
AudioGroupCollection::GroupIterator& git = it->second->m_iteratorVec[fileIdx];
if (m_curData)
m_curData->removeFromEngine(*m_engine);
git->second->addToEngine(*m_engine);
m_curData = git->second.get();
}
}
}
void VSTBackend::setGroup(int groupIdx, bool immediate)
{
std::unique_lock<std::mutex> lk(m_lock);
if (!m_curData)
return;
if (groupIdx < m_curData->m_groupTokens.size())
{
const AudioGroupDataCollection::GroupToken& groupTok = m_curData->m_groupTokens[groupIdx];
m_reqGroup = groupTok.m_groupId;
if (immediate)
{
if (m_curSeq)
m_curSeq->kill();
m_curSeq = m_engine->seqPlay(groupTok.m_groupId, -1, nullptr);
}
}
}
void VSTBackend::_setNormalProgram(int programNo)
{
if (!m_curSeq)
return;
m_curSeq->setChanProgram(0, programNo);
m_routeChannel = 0;
}
void VSTBackend::setNormalProgram(int programNo)
{
std::unique_lock<std::mutex> lk(m_lock);
_setNormalProgram(programNo);
}
void VSTBackend::_setDrumProgram(int programNo)
{
if (!m_curSeq)
return;
m_curSeq->setChanProgram(9, programNo);
m_routeChannel = 9;
}
void VSTBackend::setDrumProgram(int programNo)
{
std::unique_lock<std::mutex> lk(m_lock);
_setDrumProgram(programNo);
}
VstInt32 VSTBackend::getChunk(void** data, bool)
{
size_t allocSz = 14;
if (m_curData)
allocSz += (m_curData->m_path.size() - m_userDir.size() - 1) * 2;
uint8_t* buf = new uint8_t[allocSz];
if (m_curData)
memmove(buf, m_curData->m_path.data() + m_userDir.size() + 1, allocSz - 12);
else
*reinterpret_cast<wchar_t*>(buf) = L'\0';
uint32_t* intVals = reinterpret_cast<uint32_t*>(buf + allocSz - 12);
intVals[0] = 0;
intVals[1] = m_editor.m_selGroupIdx;
intVals[2] = m_editor.m_selPageIdx;
*data = buf;
return allocSz;
}
VstInt32 VSTBackend::setChunk(void* data, VstInt32 byteSize, bool)
{
if (byteSize < 14)
return 0;
wchar_t* path = reinterpret_cast<wchar_t*>(data);
uint32_t* intVals = reinterpret_cast<uint32_t*>(path + wcslen(path) + 1);
std::wstring targetPath = m_userDir + L'\\' + path;
uint32_t groupIdx = intVals[1];
uint32_t pageIdx = intVals[2];
size_t colIdx = 0;
for (auto& collection : m_filePresenter.m_audioGroupCollections)
{
size_t fileIdx = 0;
for (auto& file : collection.second->m_groups)
{
if (!file.second->m_path.compare(targetPath))
{
m_editor.selectCollection(LPARAM(0x80000000 | (colIdx << 16) | fileIdx));
m_editor.selectGroup(groupIdx);
m_editor.selectPage(pageIdx);
m_editor._reselectColumns();
}
++fileIdx;
}
++colIdx;
}
return 1;
}
}
AudioEffect* createEffectInstance(audioMasterCallback audioMaster) { return new amuse::VSTBackend(audioMaster); }

77
VST/VSTBackend.hpp Normal file
View File

@@ -0,0 +1,77 @@
#ifndef __AMUSE_VSTBACKEND_HPP__
#define __AMUSE_VSTBACKEND_HPP__
#include "audioeffectx.h"
#include "VSTEditor.hpp"
#include <memory>
#include "optional.hpp"
#include "amuse/BooBackend.hpp"
#include "amuse/Engine.hpp"
#include "amuse/IBackendVoice.hpp"
#include "amuse/IBackendSubmix.hpp"
#include "amuse/IBackendVoiceAllocator.hpp"
#include "AudioGroupFilePresenter.hpp"
namespace amuse
{
class VSTBackend;
/** Backend voice allocator implementation for AudioUnit mixer */
class VSTBackendVoiceAllocator : public BooBackendVoiceAllocator
{
public:
VSTBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine) : BooBackendVoiceAllocator(booEngine) {}
};
/** Actual plugin implementation class */
class VSTBackend : public AudioEffectX
{
std::mutex m_lock;
std::unique_ptr<boo::IAudioVoiceEngine> m_booBackend;
std::experimental::optional<amuse::VSTBackendVoiceAllocator> m_voxAlloc;
std::experimental::optional<amuse::Engine> m_engine;
std::shared_ptr<amuse::Sequencer> m_curSeq;
int m_reqGroup = -1;
int m_curGroup = -1;
const AudioGroupDataCollection* m_curData = nullptr;
size_t m_curFrame = 0;
std::wstring m_userDir;
int m_routeChannel = -1;
AudioGroupFilePresenter m_filePresenter;
VSTEditor m_editor;
public:
VSTBackend(audioMasterCallback cb);
~VSTBackend();
AEffEditor* getEditor();
VstInt32 processEvents(VstEvents* events);
void processReplacing(float** inputs, float** outputs, VstInt32 sampleFrames);
VstInt32 canDo(char* text);
VstPlugCategory getPlugCategory();
bool getEffectName(char* text);
bool getProductString(char* text);
bool getVendorString(char* text);
bool getProgramNameIndexed(VstInt32 category, VstInt32 index, char* text);
bool getOutputProperties(VstInt32 index, VstPinProperties* properties);
VstInt32 getNumMidiInputChannels();
void setSampleRate(float sampleRate);
void setBlockSize(VstInt32 blockSize);
amuse::Engine& getAmuseEngine() { return *m_engine; }
const std::wstring& getUserDir() const { return m_userDir; }
AudioGroupFilePresenter& getFilePresenter() { return m_filePresenter; }
void loadGroupFile(int collectionIdx, int fileIdx);
void setGroup(int groupIdx, bool immediate);
void _setNormalProgram(int programNo);
void setNormalProgram(int programNo);
void _setDrumProgram(int programNo);
void setDrumProgram(int programNo);
VstInt32 getChunk(void** data, bool isPreset);
VstInt32 setChunk(void* data, VstInt32 byteSize, bool isPreset);
};
}
#endif // __AMUSE_VSTBACKEND_HPP__

393
VST/VSTEditor.cpp Normal file
View File

@@ -0,0 +1,393 @@
#include "VSTEditor.hpp"
#include "VSTBackend.hpp"
#include "FileOpenDialog.hpp"
#include <Windowsx.h>
#include <shellapi.h>
#include <algorithm>
#include <Shlwapi.h>
#undef min
#undef max
extern void* hInstance;
static WNDPROC OriginalListViewProc = 0;
static HBRUSH gGreyBorderBrush;
namespace amuse
{
VSTEditor::VSTEditor(VSTBackend& backend) : AEffEditor(&backend), m_backend(backend) {}
bool VSTEditor::getRect(ERect** rect)
{
*rect = &m_windowRect;
return true;
}
LRESULT CALLBACK VSTEditor::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
VSTEditor& editor = *reinterpret_cast<VSTEditor*>(GetWindowLongPtrW(hwnd, 0));
switch (uMsg)
{
case WM_NOTIFY:
{
NMHDR& itemAct = *reinterpret_cast<LPNMHDR>(lParam);
switch (itemAct.code)
{
case HDN_BEGINTRACK:
return TRUE;
case NM_CLICK:
{
NMITEMACTIVATE& itemAct = *reinterpret_cast<LPNMITEMACTIVATE>(lParam);
if (itemAct.hdr.hwndFrom == editor.m_groupListView)
editor.selectGroup(itemAct.iItem);
else if (itemAct.hdr.hwndFrom == editor.m_pageListView)
editor.selectPage(itemAct.iItem);
return 0;
}
case TVN_SELCHANGED:
{
if (editor.m_deferredCollectionSel)
return 0;
NMTREEVIEW& itemAct = *reinterpret_cast<LPNMTREEVIEW>(lParam);
if (itemAct.hdr.hwndFrom == editor.m_collectionTree)
editor.selectCollection(itemAct.itemNew.lParam);
return 0;
}
case TVN_GETDISPINFO:
{
NMTVDISPINFO& treeDispInfo = *reinterpret_cast<LPNMTVDISPINFO>(lParam);
if (treeDispInfo.item.mask & TVIF_CHILDREN)
{
}
return 0;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
case WM_COMMAND:
{
switch (HIWORD(wParam))
{
case BN_CLICKED:
{
HWND button = HWND(lParam);
if (button == editor.m_collectionAdd)
editor.addAction();
else if (button == editor.m_collectionRemove)
editor.removeAction();
return 0;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
case WM_ERASEBKGND:
{
RECT rect;
GetClientRect(hwnd, &rect);
FillRect(HDC(wParam), &rect, gGreyBorderBrush);
return 1;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
LRESULT CALLBACK VSTEditor::ColHeaderWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SETCURSOR:
return TRUE;
case WM_LBUTTONDBLCLK:
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC dc = BeginPaint(hwnd, &ps);
RECT rect;
GetClientRect(hwnd, &rect);
TRIVERTEX verts[] = {{rect.left, rect.top, 0x6000, 0x6000, 0x7000, 0xff00},
{rect.right, rect.bottom, 0x2000, 0x2000, 0x2800, 0xff00}};
GRADIENT_RECT grect = {0, 1};
GradientFill(dc, verts, 2, &grect, 1, GRADIENT_FILL_RECT_V);
SetTextColor(dc, RGB(255, 255, 255));
SetBkMode(dc, TRANSPARENT);
SelectObject(dc, GetStockObject(ANSI_VAR_FONT));
rect.left += 6;
LPWSTR str = LPWSTR(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
DrawText(dc, str, -1, &rect, DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
EndPaint(hwnd, &ps);
return 0;
}
}
return CallWindowProc(OriginalListViewProc, hwnd, uMsg, wParam, lParam);
}
void VSTEditor::_reselectColumns()
{
if (m_deferredCollectionSel)
{
TreeView_SelectItem(m_collectionTree, m_deferredCollectionSel);
m_deferredCollectionSel = 0;
}
if (m_selGroupIdx != -1)
ListView_SetItemState(m_groupListView, m_selGroupIdx, LVIS_FOCUSED | LVIS_SELECTED, 0xf);
if (m_selPageIdx != -1)
ListView_SetItemState(m_pageListView, m_selPageIdx, LVIS_FOCUSED | LVIS_SELECTED, 0xf);
}
bool VSTEditor::open(void* ptr)
{
AEffEditor::open(ptr);
HWND hostView = HWND(ptr);
gGreyBorderBrush = CreateSolidBrush(RGB(100, 100, 100));
WNDCLASSW notifyCls = {CS_HREDRAW | CS_VREDRAW,
WindowProc,
0,
8,
HINSTANCE(hInstance),
nullptr,
nullptr,
nullptr,
nullptr,
L"VSTNotify"};
RegisterClassW(&notifyCls);
m_rootView = CreateWindowW(L"VSTNotify", L"", WS_CHILD, 0, 0, m_windowRect.right, m_windowRect.bottom, hostView,
nullptr, HINSTANCE(hInstance), nullptr);
SetWindowLongPtrW(m_rootView, 0, LONG_PTR(this));
ShowWindow(m_rootView, SW_SHOW);
TVINSERTSTRUCT treeItem = {};
treeItem.hParent = TVI_ROOT;
treeItem.hInsertAfter = TVI_LAST;
treeItem.item.mask = TVIF_CHILDREN | TVIF_TEXT;
treeItem.item.cChildren = 1;
treeItem.item.pszText = L"Root A";
LVCOLUMN column = {};
column.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
column.fmt = LVCFMT_LEFT | LVCFMT_FIXED_WIDTH;
column.cx = 199;
m_collectionTree =
CreateWindowW(WC_TREEVIEW, L"",
WS_CHILD | WS_CLIPSIBLINGS | TVS_SHOWSELALWAYS | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS,
1, 25, 199, m_windowRect.bottom - m_windowRect.top - 26, m_rootView, nullptr, nullptr, nullptr);
TreeView_SetBkColor(m_collectionTree, RGB(64, 64, 64));
TreeView_SetTextColor(m_collectionTree, RGB(255, 255, 255));
HTREEITEM rootItemA = TreeView_InsertItem(m_collectionTree, &treeItem);
treeItem.item.pszText = L"Root B";
HTREEITEM rootItemB = TreeView_InsertItem(m_collectionTree, &treeItem);
treeItem.hParent = rootItemA;
treeItem.item.cChildren = 0;
treeItem.item.pszText = L"Child A";
TreeView_InsertItem(m_collectionTree, &treeItem);
treeItem.item.pszText = L"Child B";
TreeView_InsertItem(m_collectionTree, &treeItem);
treeItem.hParent = rootItemB;
treeItem.item.pszText = L"Child A";
TreeView_InsertItem(m_collectionTree, &treeItem);
treeItem.item.pszText = L"Child B";
TreeView_InsertItem(m_collectionTree, &treeItem);
ShowWindow(m_collectionTree, SW_SHOW);
HWND cHeader = CreateWindowW(WC_HEADER, L"", WS_CHILD, 1, 1, 199, 24, m_rootView, nullptr, nullptr, nullptr);
SetWindowLongPtrW(cHeader, GWLP_USERDATA, LONG_PTR(L"Collection"));
OriginalListViewProc = WNDPROC(SetWindowLongPtr(cHeader, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc)));
ShowWindow(cHeader, SW_SHOW);
HWND gHeader = CreateWindowW(WC_HEADER, L"", WS_CHILD, 201, 1, 199, 24, m_rootView, nullptr, nullptr, nullptr);
SetWindowLongPtrW(gHeader, GWLP_USERDATA, LONG_PTR(L"Group"));
OriginalListViewProc = WNDPROC(SetWindowLongPtr(gHeader, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc)));
ShowWindow(gHeader, SW_SHOW);
HWND pHeader = CreateWindowW(WC_HEADER, L"", WS_CHILD, 401, 1, 198, 24, m_rootView, nullptr, nullptr, nullptr);
SetWindowLongPtrW(pHeader, GWLP_USERDATA, LONG_PTR(L"Page"));
OriginalListViewProc = WNDPROC(SetWindowLongPtr(pHeader, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc)));
ShowWindow(pHeader, SW_SHOW);
m_collectionAdd =
CreateWindowW(WC_BUTTON, L"+", WS_CHILD | WS_CLIPSIBLINGS | BS_PUSHBUTTON, 1,
m_windowRect.bottom - m_windowRect.top - 25, 25, 24, m_rootView, nullptr, nullptr, nullptr);
SetWindowFont(m_collectionAdd, GetStockObject(ANSI_FIXED_FONT), FALSE);
Button_Enable(m_collectionAdd, TRUE);
SetWindowPos(m_collectionAdd, HWND_TOP, 1, m_windowRect.bottom - m_windowRect.top - 25, 25, 24, SWP_SHOWWINDOW);
m_collectionRemove =
CreateWindowW(WC_BUTTON, L"-", WS_CHILD | WS_CLIPSIBLINGS | BS_PUSHBUTTON, 26,
m_windowRect.bottom - m_windowRect.top - 25, 25, 24, m_rootView, nullptr, nullptr, nullptr);
SetWindowFont(m_collectionRemove, GetStockObject(ANSI_FIXED_FONT), FALSE);
Button_Enable(m_collectionRemove, FALSE);
SetWindowPos(m_collectionRemove, HWND_TOP, 26, m_windowRect.bottom - m_windowRect.top - 25, 25, 24, SWP_SHOWWINDOW);
m_groupListView =
CreateWindowW(WC_LISTVIEW, L"",
WS_CHILD | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER,
201, 25, 199, m_windowRect.bottom - m_windowRect.top - 26, m_rootView, nullptr, nullptr, nullptr);
column.pszText = L"Group";
HWND header = ListView_GetHeader(m_groupListView);
SetWindowLongPtrW(header, GWLP_USERDATA, LONG_PTR(column.pszText));
SetWindowLongPtr(header, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc));
ListView_SetBkColor(m_groupListView, RGB(64, 64, 64));
ListView_SetTextBkColor(m_groupListView, CLR_NONE);
ListView_SetTextColor(m_groupListView, RGB(255, 255, 255));
ListView_InsertColumn(m_groupListView, 0, &column);
ShowWindow(m_groupListView, SW_SHOW);
m_pageListView =
CreateWindowW(WC_LISTVIEW, L"",
WS_CHILD | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER,
401, 25, 198, m_windowRect.bottom - m_windowRect.top - 26, m_rootView, nullptr, nullptr, nullptr);
column.pszText = L"Page";
column.cx = 198 - GetSystemMetrics(SM_CXVSCROLL);
header = ListView_GetHeader(m_pageListView);
SetWindowLongPtrW(header, GWLP_USERDATA, LONG_PTR(column.pszText));
SetWindowLongPtr(header, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc));
ListView_SetBkColor(m_pageListView, RGB(64, 64, 64));
ListView_SetTextBkColor(m_pageListView, CLR_NONE);
ListView_SetTextColor(m_pageListView, RGB(255, 255, 255));
ListView_InsertColumn(m_pageListView, 0, &column);
ShowWindow(m_pageListView, SW_SHOW);
update();
return true;
}
void VSTEditor::close()
{
AEffEditor::close();
UnregisterClassW(L"VSTNotify", HINSTANCE(hInstance));
}
void VSTEditor::update()
{
m_backend.getFilePresenter().populateCollectionColumn(*this);
m_backend.loadGroupFile(m_selCollectionIdx, m_selFileIdx);
m_backend.getFilePresenter().populateGroupColumn(*this, m_selCollectionIdx, m_selFileIdx);
m_backend.setGroup(m_selGroupIdx, true);
m_backend.getFilePresenter().populatePageColumn(*this, m_selCollectionIdx, m_selFileIdx, m_selGroupIdx);
selectPage(m_selPageIdx);
_reselectColumns();
}
void VSTEditor::addAction()
{
std::wstring path = openDB();
if (path.size())
{
amuse::ContainerRegistry::Type containerType;
std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>> data =
amuse::ContainerRegistry::LoadContainer(path.c_str(), containerType);
if (data.empty())
{
wchar_t msg[512];
SNPrintf(msg, 512, L"Unable to load Audio Groups from %s", path.c_str());
MessageBoxW(nullptr, msg, L"Invalid Data File", MB_OK | MB_ICONERROR);
return;
}
SystemString name(amuse::ContainerRegistry::TypeToName(containerType));
if (containerType == amuse::ContainerRegistry::Type::Raw4)
{
size_t dotpos = path.rfind(L'.');
if (dotpos != std::string::npos)
name.assign(path.cbegin(), path.cbegin() + dotpos);
size_t slashpos = name.rfind(L'\\');
size_t fslashpos = name.rfind(L'/');
if (slashpos == std::string::npos)
slashpos = fslashpos;
else if (fslashpos != std::string::npos)
slashpos = std::max(slashpos, fslashpos);
if (slashpos != std::string::npos)
name.assign(name.cbegin() + slashpos + 1, name.cend());
}
m_backend.getFilePresenter().addCollection(name, std::move(data));
update();
}
}
void VSTEditor::removeAction()
{
if (m_selCollectionIdx == -1)
return;
m_backend.getFilePresenter().removeCollection(m_selCollectionIdx);
m_backend.getFilePresenter().populateCollectionColumn(*this);
m_selCollectionIdx = -1;
m_selFileIdx = -1;
m_selGroupIdx = -1;
m_backend.getFilePresenter().populateGroupColumn(*this, m_selCollectionIdx, m_selFileIdx);
m_backend.getFilePresenter().populatePageColumn(*this, m_selCollectionIdx, m_selFileIdx, m_selGroupIdx);
Button_Enable(m_collectionRemove, FALSE);
}
void VSTEditor::selectCollection(LPARAM idx)
{
if (0x80000000 & idx)
{
/* Sub-item */
int rootIdx = (idx >> 16) & 0x7fff;
int subIdx = idx & 0xffff;
Button_Enable(m_collectionRemove, FALSE);
m_selCollectionIdx = rootIdx;
m_selFileIdx = subIdx;
m_backend.loadGroupFile(m_selCollectionIdx, m_selFileIdx);
m_backend.getFilePresenter().populateGroupColumn(*this, rootIdx, subIdx);
}
else
{
/* Root-item */
int rootIdx = (idx >> 16) & 0x7fff;
m_selCollectionIdx = rootIdx;
Button_Enable(m_collectionRemove, TRUE);
}
}
void VSTEditor::selectGroup(int idx)
{
m_selGroupIdx = idx;
m_backend.setGroup(m_selGroupIdx, false);
m_backend.getFilePresenter().populatePageColumn(*this, m_selCollectionIdx, m_selFileIdx, m_selGroupIdx);
m_lastLParam = -1;
}
void VSTEditor::selectPage(int idx)
{
m_selPageIdx = idx;
LV_ITEM item = {};
item.mask = LVIF_PARAM;
item.iItem = idx;
ListView_GetItem(m_pageListView, &item);
m_lastLParam = item.lParam;
if (item.lParam & 0x80000000)
selectDrumPage(item.lParam & 0x7fffffff);
else
selectNormalPage(item.lParam & 0x7fffffff);
}
void VSTEditor::reselectPage()
{
if (m_lastLParam != -1)
{
if (m_lastLParam & 0x80000000)
m_backend._setDrumProgram(m_lastLParam & 0x7fffffff);
else
m_backend._setNormalProgram(m_lastLParam & 0x7fffffff);
}
}
void VSTEditor::selectNormalPage(int idx) { m_backend.setNormalProgram(idx); }
void VSTEditor::selectDrumPage(int idx) { m_backend.setDrumProgram(idx); }
}

64
VST/VSTEditor.hpp Normal file
View File

@@ -0,0 +1,64 @@
#ifndef __AMUSE_VSTEDITOR_HPP__
#define __AMUSE_VSTEDITOR_HPP__
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>
#include "aeffeditor.h"
namespace amuse
{
class VSTBackend;
/** Editor UI class */
class VSTEditor : public AEffEditor
{
friend class VSTBackend;
friend class AudioGroupFilePresenter;
friend struct AudioGroupCollection;
VSTBackend& m_backend;
ERect m_windowRect = {0, 0, 420, 600};
HWND m_rootView = 0;
HWND m_collectionTree = 0;
HWND m_collectionAdd = 0;
HWND m_collectionRemove = 0;
HWND m_groupListView = 0;
HWND m_pageListView = 0;
int m_selCollectionIdx = -1;
int m_selFileIdx = -1;
int m_selGroupIdx = -1;
int m_selPageIdx = -1;
int m_lastLParam = -1;
HTREEITEM m_deferredCollectionSel = 0;
static LRESULT CALLBACK WindowProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);
static LRESULT CALLBACK ColHeaderWindowProc(_In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam);
void _reselectColumns();
public:
VSTEditor(VSTBackend& backend);
bool getRect(ERect** rect);
bool open(void* ptr);
void close();
void update();
void addAction();
void removeAction();
void selectCollection(LPARAM idx);
void selectGroup(int idx);
void selectPage(int idx);
void reselectPage();
void selectNormalPage(int idx);
void selectDrumPage(int idx);
};
}
#endif // __AMUSE_VSTEDITOR_HPP__

215
driver/amuseconv.cpp Normal file
View File

@@ -0,0 +1,215 @@
#include "amuse/amuse.hpp"
#include "athena/FileReader.hpp"
#include "athena/DNAYaml.hpp"
#include "logvisor/logvisor.hpp"
#include <stdio.h>
#include <string.h>
static logvisor::Module Log("amuseconv");
enum ConvType
{
ConvN64,
ConvGCN,
ConvPC
};
static void ReportConvType(ConvType tp)
{
switch (tp)
{
case ConvN64:
Log.report(logvisor::Info, _S("using N64 format"));
break;
case ConvPC:
Log.report(logvisor::Info, _S("using PC format"));
break;
case ConvGCN:
default:
Log.report(logvisor::Info, _S("using GameCube format"));
break;
}
}
static bool BuildAudioGroup(const amuse::SystemString& groupBase, const amuse::SystemString& targetPath)
{
return true;
}
static bool ExtractAudioGroup(const amuse::SystemString& inPath, const amuse::SystemString& targetPath)
{
amuse::ContainerRegistry::Type type;
auto groups = amuse::ContainerRegistry::LoadContainer(inPath.c_str(), type);
if (groups.size())
{
Log.report(logvisor::Info, _S("Found '%s'"), amuse::ContainerRegistry::TypeToName(type));
amuse::Mkdir(targetPath.c_str(), 0755);
Log.report(logvisor::Info, _S("Established directory at %s"), targetPath.c_str());
for (auto& group : groups)
{
Log.report(logvisor::Info, _S("Extracting %s"), group.first.c_str());
}
}
auto songs = amuse::ContainerRegistry::LoadSongs(inPath.c_str());
amuse::SystemString songsDir = targetPath + _S("/midifiles");
bool madeDir = false;
for (auto& pair : songs)
{
if (!madeDir)
{
amuse::Mkdir(targetPath.c_str(), 0755);
amuse::Mkdir(songsDir.c_str(), 0755);
madeDir = true;
}
amuse::SystemString songPath = songsDir + _S('/') + pair.first + _S(".mid");
FILE* fp = amuse::FOpen(songPath.c_str(), _S("wb"));
if (fp)
{
Log.report(logvisor::Info, _S("Extracting %s"), pair.first.c_str());
int extractedVersion;
bool isBig;
std::vector<uint8_t> mid =
amuse::SongConverter::SongToMIDI(pair.second.m_data.get(), extractedVersion, isBig);
fwrite(mid.data(), 1, mid.size(), fp);
fclose(fp);
}
}
return true;
}
static bool BuildSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath, int version, bool big)
{
FILE* fp = amuse::FOpen(inPath.c_str(), _S("rb"));
if (!fp)
return false;
fseek(fp, 0, SEEK_END);
long sz = ftell(fp);
fseek(fp, 0, SEEK_SET);
std::vector<uint8_t> data(sz, 0);
fread(&data[0], 1, sz, fp);
fclose(fp);
std::vector<uint8_t> out = amuse::SongConverter::MIDIToSong(data, version, big);
if (out.empty())
return false;
fp = amuse::FOpen(targetPath.c_str(), _S("wb"));
fwrite(out.data(), 1, out.size(), fp);
fclose(fp);
return true;
}
static bool ExtractSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath)
{
FILE* fp = amuse::FOpen(inPath.c_str(), _S("rb"));
if (!fp)
return false;
fseek(fp, 0, SEEK_END);
long sz = ftell(fp);
fseek(fp, 0, SEEK_SET);
std::vector<uint8_t> data(sz, 0);
fread(&data[0], 1, sz, fp);
fclose(fp);
int extractedVersion;
bool isBig;
std::vector<uint8_t> out = amuse::SongConverter::SongToMIDI(data.data(), extractedVersion, isBig);
if (out.empty())
return false;
fp = amuse::FOpen(targetPath.c_str(), _S("wb"));
fwrite(out.data(), 1, out.size(), fp);
fclose(fp);
return true;
}
#if _WIN32
int wmain(int argc, const amuse::SystemChar** argv)
#else
int main(int argc, const amuse::SystemChar** argv)
#endif
{
logvisor::RegisterConsoleLogger();
if (argc < 3)
{
printf("Usage: amuseconv <in-file> <out-file> [n64|pc|gcn]\n");
return 0;
}
ConvType type = ConvGCN;
if (argc >= 4)
{
if (!amuse::CompareCaseInsensitive(argv[3], _S("n64")))
type = ConvN64;
else if (!amuse::CompareCaseInsensitive(argv[3], _S("gcn")))
type = ConvGCN;
else if (!amuse::CompareCaseInsensitive(argv[3], _S("pc")))
type = ConvPC;
else
{
Log.report(logvisor::Error, _S("unrecognized format: %s"), argv[3]);
return 1;
}
}
bool good = false;
FILE* fin = amuse::FOpen(argv[1], _S("rb"));
if (fin)
{
fclose(fin);
amuse::SystemString barePath(argv[1]);
size_t dotPos = barePath.rfind(_S('.'));
const amuse::SystemChar* dot = barePath.c_str() + dotPos;
if (dotPos != amuse::SystemString::npos)
{
if (!amuse::CompareCaseInsensitive(dot, _S(".mid")) || !amuse::CompareCaseInsensitive(dot, _S(".midi")))
{
ReportConvType(type);
good = BuildSNG(barePath, argv[2], 1, true);
}
else if (!amuse::CompareCaseInsensitive(dot, _S(".son")) || !amuse::CompareCaseInsensitive(dot, _S(".sng")))
{
good = ExtractSNG(argv[1], argv[2]);
}
else
{
good = ExtractAudioGroup(argv[1], argv[2]);
}
}
}
else
{
amuse::Sstat theStat;
if (!amuse::Stat(argv[1], &theStat) && S_ISDIR(theStat.st_mode))
{
amuse::SystemString projectPath(argv[1]);
projectPath += _S("/project.yaml");
fin = amuse::FOpen(projectPath.c_str(), _S("rb"));
if (fin)
{
fclose(fin);
ReportConvType(type);
good = BuildAudioGroup(argv[1], argv[2]);
}
}
}
if (!good)
{
Log.report(logvisor::Error, _S("unable to convert %s to %s"), argv[1], argv[2]);
return 1;
}
return 0;
}

View File

@@ -16,9 +16,10 @@
static logvisor::Module Log("amuseplay");
#if __GNUC__
__attribute__((__format__ (__printf__, 3, 4)))
__attribute__((__format__(__printf__, 3, 4)))
#endif
static inline void SNPrintf(boo::SystemChar* str, size_t maxlen, const boo::SystemChar* format, ...)
static inline void
SNPrintf(boo::SystemChar* str, size_t maxlen, const boo::SystemChar* format, ...)
{
va_list va;
va_start(va, format);
@@ -36,33 +37,33 @@ static inline void SNPrintf(boo::SystemChar* str, size_t maxlen, const boo::Syst
#include <signal.h>
static void abortHandler( int signum )
static void abortHandler(int signum)
{
unsigned int i;
void * stack[ 100 ];
unsigned int i;
void* stack[100];
unsigned short frames;
SYMBOL_INFO * symbol;
HANDLE process;
SYMBOL_INFO* symbol;
HANDLE process;
process = GetCurrentProcess();
SymInitialize( process, NULL, TRUE );
frames = CaptureStackBackTrace( 0, 100, stack, NULL );
symbol = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 );
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof( SYMBOL_INFO );
SymInitialize(process, NULL, TRUE);
frames = CaptureStackBackTrace(0, 100, stack, NULL);
symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
for( i = 0; i < frames; i++ )
for (i = 0; i < frames; i++)
{
SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, symbol );
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
printf( "%i: %s - 0x%0llX", frames - i - 1, symbol->Name, symbol->Address );
printf("%i: %s - 0x%0llX", frames - i - 1, symbol->Name, symbol->Address);
DWORD dwDisplacement;
DWORD dwDisplacement;
IMAGEHLP_LINE64 line;
SymSetOptions(SYMOPT_LOAD_LINES);
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
if (SymGetLineFromAddr64(process, ( DWORD64 )( stack[ i ] ), &dwDisplacement, &line))
if (SymGetLineFromAddr64(process, (DWORD64)(stack[i]), &dwDisplacement, &line))
{
// SymGetLineFromAddr64 returned success
printf(" LINE %d\n", line.LineNumber);
@@ -73,12 +74,12 @@ static void abortHandler( int signum )
}
}
free( symbol );
free(symbol);
// If you caught one of the above signals, it is likely you just
// want to quit your program right now.
system("PAUSE");
exit( signum );
exit(signum);
}
#endif
@@ -88,6 +89,7 @@ struct EventCallback : boo::IWindowCallback
{
AppCallback& m_app;
bool m_tracking = false;
public:
void charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat);
void charKeyUp(unsigned long charCode, boo::EModifierKey mods);
@@ -132,7 +134,7 @@ struct AppCallback : boo::IApplicationCallback
int8_t m_lastChanProg = -1;
/* Control state */
float m_volume = 0.5f;
float m_volume = 0.8f;
float m_modulation = 0.f;
float m_pitchBend = 0.f;
bool m_updateDisp = false;
@@ -151,9 +153,10 @@ struct AppCallback : boo::IApplicationCallback
voxCount = m_seq->getVoiceCount();
program = m_seq->getChanProgram(m_chanId);
}
printf("\r "
"\r %" PRISize " Setup %d, Chan %d, Prog %d, Octave: %d, Vel: %d, VOL: %d%%\r", voxCount,
m_setupId, m_chanId, program, m_octave, m_velocity, int(std::rint(m_volume * 100)));
printf(
"\r "
"\r %" PRISize " Setup %d, Chan %d, Prog %d, Octave: %d, Vel: %d, VOL: %d%%\r",
voxCount, m_setupId, m_chanId, program, m_octave, m_velocity, int(std::rint(m_volume * 100)));
fflush(stdout);
}
@@ -166,7 +169,7 @@ struct AppCallback : boo::IApplicationCallback
m_seq->kill();
}
m_seq = m_engine->seqPlay(m_groupId, setupId, nullptr);
m_seq->setVolume(m_volume);
m_engine->setVolume(m_volume);
if (m_arrData)
m_seq->playSong(m_arrData->m_data.get(), false);
@@ -176,19 +179,20 @@ struct AppCallback : boo::IApplicationCallback
void SongLoop(const amuse::SongGroupIndex& index)
{
printf("░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n"
"░░░ ████ ████ ┃ ████ ████ ████ ┃ ████ ████ ░░░\n"
"░░░ ████ ████ ┃ ████ ████ ████ ┃ ████ ████ ░░░\n"
"░░░ ▌W▐█ ▌E▐█ ┃ ▌T▐█ ▌Y▐█ ▌U▐█ ┃ ▌O▐█ ▌P▐█ ░░░\n"
"░░░ │ │ ┃ │ │ │ ┃ │ │ ░░░\n"
"░░░ ASDFGHJKL; ░░░\n"
"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n"
"<left/right>: cycle MIDI setup, <up/down>: volume, <space>: PANIC\n"
"<tab>: sustain pedal, <window-Y>: pitch wheel, <window-X>: mod wheel\n"
"<Z/X>: octave, <C/V>: velocity, <B/N>: channel, <,/.>: program, <Q>: quit\n");
printf(
"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n"
"░░░ ████ ████ ┃ ████ ████ ████ ┃ ████ ████ ░░░\n"
"░░░ ████ ████ ┃ ████ ████ ████ ┃ ████ ████ ░░░\n"
"░░░ ▌W▐█ ▌E▐█ ┃ ▌T▐█ ▌Y▐█ ▌U▐█ ┃ ▌O▐█ ▌P▐█ ░░░\n"
"░░░ ░░░\n"
"░░░ A │ S │ D ┃ F │ G │ H │ J ┃ K │ L │ ; ░░░\n"
"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n"
"<left/right>: cycle MIDI setup, <up/down>: volume, <space>: PANIC\n"
"<tab>: sustain pedal, <window-Y>: pitch wheel, <window-X>: mod wheel\n"
"<Z/X>: octave, <C/V>: velocity, <B/N>: channel, <,/.>: program, <Q>: quit\n");
std::map<int, const std::array<amuse::SongGroupIndex::MIDISetup, 16>*> sortEntries
(index.m_midiSetups.cbegin(), index.m_midiSetups.cend());
std::map<int, const std::array<amuse::SongGroupIndex::MIDISetup, 16>*> sortEntries(index.m_midiSetups.cbegin(),
index.m_midiSetups.cend());
auto setupIt = sortEntries.cbegin();
if (setupIt != sortEntries.cend())
{
@@ -287,9 +291,10 @@ struct AppCallback : boo::IApplicationCallback
void UpdateSFXDisplay()
{
bool playing = m_vox && m_vox->state() == amuse::VoiceState::Playing;
printf("\r "
"\r %c SFX %d, VOL: %d%%\r", playing ? '>' : ' ',
m_sfxId, int(std::rint(m_volume * 100)));
printf(
"\r "
"\r %c SFX %d, VOL: %d%%\r",
playing ? '>' : ' ', m_sfxId, int(std::rint(m_volume * 100)));
fflush(stdout);
}
@@ -311,8 +316,8 @@ struct AppCallback : boo::IApplicationCallback
{
printf("<space>: keyon/keyoff, <left/right>: cycle SFX, <up/down>: volume, <Q>: quit\n");
std::map<uint16_t, const amuse::SFXGroupIndex::SFXEntry*> sortEntries
(index.m_sfxEntries.cbegin(), index.m_sfxEntries.cend());
std::map<uint16_t, const amuse::SFXGroupIndex::SFXEntry*> sortEntries(index.m_sfxEntries.cbegin(),
index.m_sfxEntries.cend());
auto sfxIt = sortEntries.cbegin();
if (sfxIt != sortEntries.cend())
SelectSFX(sfxIt->first);
@@ -410,7 +415,8 @@ struct AppCallback : boo::IApplicationCallback
m_seq->nextChanProgram(m_chanId);
m_updateDisp = true;
break;
default: break;
default:
break;
}
}
}
@@ -434,7 +440,8 @@ struct AppCallback : boo::IApplicationCallback
else if (m_sfxId != -1)
m_vox = m_engine->fxStart(m_sfxId, m_volume, 0.f);
m_updateDisp = true;
default: break;
default:
break;
}
}
else if (m_seq && m_chanId != -1)
@@ -534,7 +541,8 @@ struct AppCallback : boo::IApplicationCallback
case ':':
m_seq->keyOn(m_chanId, (m_octave + 1) * 12 + 16, m_velocity);
break;
default: break;
default:
break;
}
if (!setPanic)
@@ -605,7 +613,8 @@ struct AppCallback : boo::IApplicationCallback
case ':':
m_seq->keyOff(m_chanId, (m_octave + 1) * 12 + 16, m_velocity);
break;
default: break;
default:
break;
}
}
}
@@ -620,12 +629,10 @@ struct AppCallback : boo::IApplicationCallback
m_win->showWindow();
boo::ITextureR* tex = nullptr;
boo::GraphicsDataToken gfxToken =
m_win->getMainContextDataFactory()->commitTransaction(
[&](boo::IGraphicsDataFactory::Context& ctx) -> bool
{
tex = ctx.newRenderTexture(100, 100, false, false);
return true;
});
m_win->getMainContextDataFactory()->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) -> bool {
tex = ctx.newRenderTexture(100, 100, false, false);
return true;
});
boo::IGraphicsCommandQueue* q = m_win->getCommandQueue();
q->setRenderTarget(tex);
q->clearTarget();
@@ -639,23 +646,16 @@ struct AppCallback : boo::IApplicationCallback
exit(1);
}
#if _WIN32
char utf8Path[1024];
WideCharToMultiByte(CP_UTF8, 0, m_argv[1], -1, utf8Path, 1024, nullptr, nullptr);
#else
const char* utf8Path = m_argv[1];
#endif
amuse::ContainerRegistry::Type cType = amuse::ContainerRegistry::DetectContainerType(utf8Path);
amuse::ContainerRegistry::Type cType = amuse::ContainerRegistry::DetectContainerType(m_argv[1]);
if (cType == amuse::ContainerRegistry::Type::Invalid)
{
Log.report(logvisor::Error, "invalid/no data at path argument");
exit(1);
}
Log.report(logvisor::Info, "Found '%s' Audio Group data", amuse::ContainerRegistry::TypeToName(cType));
Log.report(logvisor::Info, _S("Found '%s' Audio Group data"), amuse::ContainerRegistry::TypeToName(cType));
std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>> data =
amuse::ContainerRegistry::LoadContainer(utf8Path);
std::vector<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>> data =
amuse::ContainerRegistry::LoadContainer(m_argv[1]);
if (data.empty())
{
Log.report(logvisor::Error, "invalid/no data at path argument");
@@ -663,8 +663,12 @@ struct AppCallback : boo::IApplicationCallback
}
std::list<amuse::AudioGroupProject> m_projs;
std::map<int, std::pair<std::pair<std::string, amuse::IntrusiveAudioGroupData>*, const amuse::SongGroupIndex*>> allSongGroups;
std::map<int, std::pair<std::pair<std::string, amuse::IntrusiveAudioGroupData>*, const amuse::SFXGroupIndex*>> allSFXGroups;
std::map<int, std::pair<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>*,
const amuse::SongGroupIndex*>>
allSongGroups;
std::map<int, std::pair<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>*,
const amuse::SFXGroupIndex*>>
allSFXGroups;
size_t totalGroups = 0;
for (auto& grp : data)
@@ -674,10 +678,10 @@ struct AppCallback : boo::IApplicationCallback
amuse::AudioGroupProject& proj = m_projs.back();
totalGroups += proj.sfxGroups().size() + proj.songGroups().size();
for (auto it = proj.songGroups().begin() ; it != proj.songGroups().end() ; ++it)
for (auto it = proj.songGroups().begin(); it != proj.songGroups().end(); ++it)
allSongGroups[it->first] = std::make_pair(&grp, &it->second);
for (auto it = proj.sfxGroups().begin() ; it != proj.sfxGroups().end() ; ++it)
for (auto it = proj.sfxGroups().begin(); it != proj.sfxGroups().end(); ++it)
allSFXGroups[it->first] = std::make_pair(&grp, &it->second);
}
@@ -687,19 +691,11 @@ struct AppCallback : boo::IApplicationCallback
m_setupId = -1;
/* Attempt loading song */
std::vector<std::pair<std::string, amuse::ContainerRegistry::SongData>> songs;
std::vector<std::pair<amuse::SystemString, amuse::ContainerRegistry::SongData>> songs;
if (m_argc > 2)
{
#if _WIN32
char utf8Path[1024];
WideCharToMultiByte(CP_UTF8, 0, m_argv[2], -1, utf8Path, 1024, nullptr, nullptr);
#else
const char* utf8Path = m_argv[2];
#endif
songs = amuse::ContainerRegistry::LoadSongs(utf8Path);
}
songs = amuse::ContainerRegistry::LoadSongs(m_argv[2]);
else
songs = amuse::ContainerRegistry::LoadSongs(utf8Path);
songs = amuse::ContainerRegistry::LoadSongs(m_argv[1]);
if (songs.size())
{
@@ -739,8 +735,27 @@ struct AppCallback : boo::IApplicationCallback
int idx = 0;
for (const auto& pair : songs)
{
printf(" %d %s (Group %d, Setup %d)\n", idx++,
pair.first.c_str(), pair.second.m_groupId, pair.second.m_setupId);
const amuse::ContainerRegistry::SongData& sngData = pair.second;
int16_t grpId = sngData.m_groupId;
int16_t setupId = sngData.m_setupId;
if (sngData.m_groupId == -1 && sngData.m_setupId != -1)
{
for (const auto& pair : allSongGroups)
{
for (const auto& setup : pair.second.second->m_midiSetups)
{
if (setup.first == sngData.m_setupId)
{
grpId = pair.first;
break;
}
}
if (grpId != -1)
break;
}
}
amuse::Printf(_S(" %d %s (Group %d, Setup %d)\n"), idx++, pair.first.c_str(), grpId,
setupId);
}
int userSel = 0;
@@ -772,7 +787,25 @@ struct AppCallback : boo::IApplicationCallback
}
}
/* Get group selection from user */
/* Get group selection via setup search */
if (m_groupId == -1 && m_setupId != -1)
{
for (const auto& pair : allSongGroups)
{
for (const auto& setup : pair.second.second->m_midiSetups)
{
if (setup.first == m_setupId)
{
m_groupId = pair.first;
break;
}
}
if (m_groupId != -1)
break;
}
}
/* Get group selection via user */
if (m_groupId != -1)
{
if (allSongGroups.find(m_groupId) != allSongGroups.end())
@@ -791,15 +824,15 @@ struct AppCallback : boo::IApplicationCallback
printf("Multiple Audio Groups discovered:\n");
for (const auto& pair : allSFXGroups)
{
printf(" %d %s (SFXGroup) %" PRISize " sfx-entries\n",
pair.first, pair.second.first->first.c_str(),
pair.second.second->m_sfxEntries.size());
amuse::Printf(_S(" %d %s (SFXGroup) %" PRISize " sfx-entries\n"), pair.first,
pair.second.first->first.c_str(), pair.second.second->m_sfxEntries.size());
}
for (const auto& pair : allSongGroups)
{
printf(" %d %s (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages\n",
pair.first, pair.second.first->first.c_str(),
pair.second.second->m_normPages.size(), pair.second.second->m_drumPages.size());
amuse::Printf(_S(" %d %s (SongGroup) %" PRISize " normal-pages, %" PRISize
" drum-pages, %" PRISize " MIDI-setups\n"),
pair.first, pair.second.first->first.c_str(), pair.second.second->m_normPages.size(),
pair.second.second->m_drumPages.size(), pair.second.second->m_midiSetups.size());
}
int userSel = 0;
@@ -899,13 +932,12 @@ struct AppCallback : boo::IApplicationCallback
return 0;
}
void appQuitting(boo::IApplication*)
{
m_running = false;
}
void appQuitting(boo::IApplication*) { m_running = false; }
AppCallback(int argc, const boo::SystemChar** argv)
: m_argc(argc), m_argv(argv), m_eventRec(*this), m_events(m_eventRec) {}
: m_argc(argc), m_argv(argv), m_eventRec(*this), m_events(m_eventRec)
{
}
};
void EventCallback::charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat)
@@ -916,10 +948,7 @@ void EventCallback::charKeyDown(unsigned long charCode, boo::EModifierKey mods,
m_app.charKeyDown(charCode);
}
void EventCallback::charKeyUp(unsigned long charCode, boo::EModifierKey mods)
{
m_app.charKeyUp(charCode);
}
void EventCallback::charKeyUp(unsigned long charCode, boo::EModifierKey mods) { m_app.charKeyUp(charCode); }
void EventCallback::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat)
{
@@ -934,30 +963,23 @@ void EventCallback::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods,
case boo::ESpecialKey::Up:
if (m_app.m_volume < 1.f)
m_app.m_volume = amuse::clamp(0.f, m_app.m_volume + 0.05f, 1.f);
if (m_app.m_vox)
m_app.m_vox->setVolume(m_app.m_volume);
if (m_app.m_seq)
m_app.m_seq->setVolume(m_app.m_volume);
m_app.m_engine->setVolume(m_app.m_volume);
m_app.m_updateDisp = true;
break;
case boo::ESpecialKey::Down:
if (m_app.m_volume > 0.f)
m_app.m_volume = amuse::clamp(0.f, m_app.m_volume - 0.05f, 1.f);
if (m_app.m_vox)
m_app.m_vox->setVolume(m_app.m_volume);
if (m_app.m_seq)
m_app.m_seq->setVolume(m_app.m_volume);
m_app.m_engine->setVolume(m_app.m_volume);
m_app.m_updateDisp = true;
break;
case boo::ESpecialKey::Esc:
m_app.m_breakout = true;
default: break;
default:
break;
}
}
void EventCallback::specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods)
{
}
void EventCallback::specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) {}
void EventCallback::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton, boo::EModifierKey)
{
@@ -1005,8 +1027,8 @@ int main(int argc, const boo::SystemChar** argv)
{
logvisor::RegisterConsoleLogger();
AppCallback app(argc, argv);
int ret = boo::ApplicationRun(boo::IApplication::EPlatformType::Auto,
app, _S("amuseplay"), _S("Amuse Player"), argc, argv, false);
int ret = boo::ApplicationRun(boo::IApplication::EPlatformType::Auto, app, _S("amuseplay"), _S("Amuse Player"),
argc, argv, false);
printf("IM DYING!!\n");
return ret;
}
@@ -1014,10 +1036,10 @@ int main(int argc, const boo::SystemChar** argv)
#if _WIN32
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
{
signal( SIGABRT, abortHandler );
signal( SIGSEGV, abortHandler );
signal( SIGILL, abortHandler );
signal( SIGFPE, abortHandler );
signal(SIGABRT, abortHandler);
signal(SIGSEGV, abortHandler);
signal(SIGILL, abortHandler);
signal(SIGFPE, abortHandler);
int argc = 0;
const boo::SystemChar** argv = (const wchar_t**)(CommandLineToArgvW(lpCmdLine, &argc));
@@ -1025,11 +1047,11 @@ int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
GetModuleFileNameW(nullptr, selfPath, 1024);
static const boo::SystemChar* booArgv[32] = {};
booArgv[0] = selfPath;
for (int i=0 ; i<argc ; ++i)
booArgv[i+1] = argv[i];
for (int i = 0; i < argc; ++i)
booArgv[i + 1] = argv[i];
logvisor::CreateWin32Console();
SetConsoleOutputCP(65001);
return wmain(argc+1, booArgv);
return wmain(argc + 1, booArgv);
}
#endif

504
driver/amuserender.cpp Normal file
View File

@@ -0,0 +1,504 @@
#include "amuse/amuse.hpp"
#include "amuse/BooBackend.hpp"
#include "athena/FileReader.hpp"
#include "boo/boo.hpp"
#include "boo/audiodev/IAudioVoiceEngine.hpp"
#include "logvisor/logvisor.hpp"
#include "optional.hpp"
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <thread>
#include <map>
#include <set>
#include <vector>
#include <unordered_map>
#include <stdarg.h>
static logvisor::Module Log("amuserender");
#if __GNUC__
__attribute__((__format__(__printf__, 3, 4)))
#endif
static inline void
SNPrintf(boo::SystemChar* str, size_t maxlen, const boo::SystemChar* format, ...)
{
va_list va;
va_start(va, format);
#if _WIN32
_vsnwprintf(str, maxlen, format, va);
#else
vsnprintf(str, maxlen, format, va);
#endif
va_end(va);
}
#if _WIN32
#include <DbgHelp.h>
#pragma comment(lib, "Dbghelp.lib")
#include <signal.h>
static void abortHandler(int signum)
{
unsigned int i;
void* stack[100];
unsigned short frames;
SYMBOL_INFO* symbol;
HANDLE process;
process = GetCurrentProcess();
SymInitialize(process, NULL, TRUE);
frames = CaptureStackBackTrace(0, 100, stack, NULL);
symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1);
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
for (i = 0; i < frames; i++)
{
SymFromAddr(process, (DWORD64)(stack[i]), 0, symbol);
printf("%i: %s - 0x%0llX", frames - i - 1, symbol->Name, symbol->Address);
DWORD dwDisplacement;
IMAGEHLP_LINE64 line;
SymSetOptions(SYMOPT_LOAD_LINES);
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
if (SymGetLineFromAddr64(process, (DWORD64)(stack[i]), &dwDisplacement, &line))
{
// SymGetLineFromAddr64 returned success
printf(" LINE %d\n", line.LineNumber);
}
else
{
printf("\n");
}
}
free(symbol);
// If you caught one of the above signals, it is likely you just
// want to quit your program right now.
system("PAUSE");
exit(signum);
}
#endif
/* SIGINT will gracefully break write loop */
static bool g_BreakLoop = false;
static void SIGINTHandler(int sig) { g_BreakLoop = true; }
#if _WIN32
int wmain(int argc, const boo::SystemChar** argv)
#else
int main(int argc, const boo::SystemChar** argv)
#endif
{
logvisor::RegisterConsoleLogger();
std::vector<boo::SystemString> m_args;
m_args.reserve(argc);
double rate = 32000.0;
for (int i = 1; i < argc; ++i)
{
#if _WIN32
if (!wcsncmp(argv[i], L"-r", 2))
{
if (argv[i][2])
rate = wcstod(&argv[i][2], nullptr);
else if (argc > (i + 1))
{
rate = wcstod(argv[i + 1], nullptr);
++i;
}
}
else
m_args.push_back(argv[i]);
#else
if (!strncmp(argv[i], "-r", 2))
{
if (argv[i][2])
rate = strtod(&argv[i][2], nullptr);
else if (argc > (i + 1))
{
rate = strtod(argv[i + 1], nullptr);
++i;
}
}
else
m_args.push_back(argv[i]);
#endif
}
/* Load data */
if (m_args.size() < 1)
{
Log.report(logvisor::Error, "Usage: amuserender <group-file> [<songs-file>] [-r <sample-rate>]");
exit(1);
}
amuse::ContainerRegistry::Type cType = amuse::ContainerRegistry::DetectContainerType(m_args[0].c_str());
if (cType == amuse::ContainerRegistry::Type::Invalid)
{
Log.report(logvisor::Error, "invalid/no data at path argument");
exit(1);
}
Log.report(logvisor::Info, _S("Found '%s' Audio Group data"), amuse::ContainerRegistry::TypeToName(cType));
std::vector<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>> data =
amuse::ContainerRegistry::LoadContainer(m_args[0].c_str());
if (data.empty())
{
Log.report(logvisor::Error, "invalid/no data at path argument");
exit(1);
}
int m_groupId = -1;
int m_setupId = -1;
const amuse::SystemString* m_groupName = nullptr;
const amuse::SystemString* m_songName = nullptr;
amuse::ContainerRegistry::SongData* m_arrData = nullptr;
bool m_sfxGroup = false;
std::list<amuse::AudioGroupProject> m_projs;
std::map<int,
std::pair<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>*, const amuse::SongGroupIndex*>>
allSongGroups;
std::map<int,
std::pair<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>*, const amuse::SFXGroupIndex*>>
allSFXGroups;
size_t totalGroups = 0;
for (auto& grp : data)
{
/* Load project to assemble group list */
m_projs.push_back(amuse::AudioGroupProject::CreateAudioGroupProject(grp.second));
amuse::AudioGroupProject& proj = m_projs.back();
totalGroups += proj.sfxGroups().size() + proj.songGroups().size();
for (auto it = proj.songGroups().begin(); it != proj.songGroups().end(); ++it)
allSongGroups[it->first] = std::make_pair(&grp, &it->second);
for (auto it = proj.sfxGroups().begin(); it != proj.sfxGroups().end(); ++it)
allSFXGroups[it->first] = std::make_pair(&grp, &it->second);
}
/* Attempt loading song */
std::vector<std::pair<amuse::SystemString, amuse::ContainerRegistry::SongData>> songs;
if (m_args.size() > 1)
songs = amuse::ContainerRegistry::LoadSongs(m_args[1].c_str());
else
songs = amuse::ContainerRegistry::LoadSongs(m_args[0].c_str());
if (songs.size())
{
bool play = true;
if (m_args.size() <= 1)
{
bool prompt = true;
while (true)
{
if (prompt)
{
printf("Render Song? (Y/N): ");
prompt = false;
}
char userSel;
if (scanf("%c", &userSel) <= 0 || userSel == '\n')
continue;
userSel = tolower(userSel);
if (userSel == 'n')
play = false;
else if (userSel != 'y')
{
prompt = true;
continue;
}
break;
}
}
if (play)
{
/* Get song selection from user */
if (songs.size() > 1)
{
/* Ask user to specify which song */
printf("Multiple Songs discovered:\n");
int idx = 0;
for (const auto& pair : songs)
{
const amuse::ContainerRegistry::SongData& sngData = pair.second;
int16_t grpId = sngData.m_groupId;
int16_t setupId = sngData.m_setupId;
if (sngData.m_groupId == -1 && sngData.m_setupId != -1)
{
for (const auto& pair : allSongGroups)
{
for (const auto& setup : pair.second.second->m_midiSetups)
{
if (setup.first == sngData.m_setupId)
{
grpId = pair.first;
break;
}
}
if (grpId != -1)
break;
}
}
amuse::Printf(_S(" %d %s (Group %d, Setup %d)\n"), idx++, pair.first.c_str(), grpId, setupId);
}
int userSel = 0;
printf("Enter Song Number: ");
if (scanf("%d", &userSel) <= 0)
{
Log.report(logvisor::Error, "unable to parse prompt");
exit(1);
}
if (userSel < songs.size())
{
m_arrData = &songs[userSel].second;
m_groupId = m_arrData->m_groupId;
m_setupId = m_arrData->m_setupId;
m_songName = &songs[userSel].first;
}
else
{
Log.report(logvisor::Error, "unable to find Song %d", userSel);
exit(1);
}
}
else if (songs.size() == 1)
{
m_arrData = &songs[0].second;
m_groupId = m_arrData->m_groupId;
m_setupId = m_arrData->m_setupId;
m_songName = &songs[0].first;
}
}
}
/* Get group selection via setup search */
if (m_groupId == -1 && m_setupId != -1)
{
for (const auto& pair : allSongGroups)
{
for (const auto& setup : pair.second.second->m_midiSetups)
{
if (setup.first == m_setupId)
{
m_groupId = pair.first;
m_groupName = &pair.second.first->first;
break;
}
}
if (m_groupId != -1)
break;
}
}
/* Get group selection via user */
if (m_groupId != -1)
{
auto songSearch = allSongGroups.find(m_groupId);
auto sfxSearch = allSFXGroups.find(m_groupId);
if (songSearch != allSongGroups.end())
{
m_sfxGroup = false;
m_groupName = &songSearch->second.first->first;
}
else if (sfxSearch != allSFXGroups.end())
{
m_sfxGroup = true;
m_groupName = &sfxSearch->second.first->first;
}
else
{
Log.report(logvisor::Error, "unable to find Group %d", m_groupId);
exit(1);
}
}
else if (totalGroups > 1)
{
/* Ask user to specify which group in project */
printf("Multiple Audio Groups discovered:\n");
for (const auto& pair : allSFXGroups)
{
amuse::Printf(_S(" %d %s (SFXGroup) %" PRISize " sfx-entries\n"), pair.first,
pair.second.first->first.c_str(), pair.second.second->m_sfxEntries.size());
}
for (const auto& pair : allSongGroups)
{
amuse::Printf(_S(" %d %s (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages, %" PRISize
" MIDI-setups\n"),
pair.first, pair.second.first->first.c_str(), pair.second.second->m_normPages.size(),
pair.second.second->m_drumPages.size(), pair.second.second->m_midiSetups.size());
}
int userSel = 0;
printf("Enter Group Number: ");
if (scanf("%d", &userSel) <= 0)
{
Log.report(logvisor::Error, "unable to parse prompt");
exit(1);
}
auto songSearch = allSongGroups.find(userSel);
auto sfxSearch = allSFXGroups.find(userSel);
if (songSearch != allSongGroups.end())
{
m_groupId = userSel;
m_groupName = &songSearch->second.first->first;
m_sfxGroup = false;
}
else if (sfxSearch != allSFXGroups.end())
{
m_groupId = userSel;
m_groupName = &sfxSearch->second.first->first;
m_sfxGroup = true;
}
else
{
Log.report(logvisor::Error, "unable to find Group %d", userSel);
exit(1);
}
}
else if (totalGroups == 1)
{
/* Load one and only group */
if (allSongGroups.size())
{
const auto& pair = *allSongGroups.cbegin();
m_groupId = pair.first;
m_groupName = &pair.second.first->first;
m_sfxGroup = false;
}
else
{
const auto& pair = *allSFXGroups.cbegin();
m_groupId = pair.first;
m_groupName = &pair.second.first->first;
m_sfxGroup = true;
}
}
else
{
Log.report(logvisor::Error, "empty project");
exit(1);
}
/* Make final group selection */
amuse::IntrusiveAudioGroupData* selData = nullptr;
const amuse::SongGroupIndex* songIndex = nullptr;
const amuse::SFXGroupIndex* sfxIndex = nullptr;
auto songSearch = allSongGroups.find(m_groupId);
if (songSearch != allSongGroups.end())
{
selData = &songSearch->second.first->second;
songIndex = songSearch->second.second;
std::set<int> sortSetups;
for (auto& pair : songIndex->m_midiSetups)
sortSetups.insert(pair.first);
if (m_setupId == -1)
{
/* Ask user to specify which group in project */
printf("Multiple MIDI Setups:\n");
for (int setup : sortSetups)
printf(" %d\n", setup);
int userSel = 0;
printf("Enter Setup Number: ");
if (scanf("%d", &userSel) <= 0)
{
Log.report(logvisor::Error, "unable to parse prompt");
exit(1);
}
m_setupId = userSel;
}
if (sortSetups.find(m_setupId) == sortSetups.cend())
{
Log.report(logvisor::Error, "unable to find setup %d", m_setupId);
exit(1);
}
}
else
{
auto sfxSearch = allSFXGroups.find(m_groupId);
if (sfxSearch != allSFXGroups.end())
{
selData = &sfxSearch->second.first->second;
sfxIndex = sfxSearch->second.second;
}
}
if (!selData)
{
Log.report(logvisor::Error, "unable to select audio group data");
exit(1);
}
if (m_sfxGroup)
{
Log.report(logvisor::Error, "amuserender is currently only able to render SongGroups");
exit(1);
}
/* WAV out path */
amuse::SystemChar pathOut[1024];
SNPrintf(pathOut, 1024, _S("%s-%s.wav"), m_groupName->c_str(), m_songName->c_str());
Log.report(logvisor::Info, _S("Writing to %s"), pathOut);
/* Build voice engine */
std::unique_ptr<boo::IAudioVoiceEngine> voxEngine = boo::NewWAVAudioVoiceEngine(pathOut, rate);
amuse::BooBackendVoiceAllocator booBackend(*voxEngine);
amuse::Engine engine(booBackend, amuse::AmplitudeMode::PerSample);
/* Load group into engine */
const amuse::AudioGroup* group = engine.addAudioGroup(*selData);
if (!group)
{
Log.report(logvisor::Error, "unable to add audio group");
exit(1);
}
/* Enter playback loop */
std::shared_ptr<amuse::Sequencer> seq = engine.seqPlay(m_groupId, m_setupId, m_arrData->m_data.get());
size_t wroteFrames = 0;
signal(SIGINT, SIGINTHandler);
do
{
engine.pumpEngine();
wroteFrames += voxEngine->get5MsFrames();
printf("\rFrame %" PRISize, wroteFrames);
fflush(stdout);
} while (!g_BreakLoop && (seq->state() == amuse::SequencerState::Playing || seq->getVoiceCount() != 0));
printf("\n");
return 0;
}
#if _WIN32
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR lpCmdLine, int)
{
signal(SIGABRT, abortHandler);
signal(SIGSEGV, abortHandler);
signal(SIGILL, abortHandler);
signal(SIGFPE, abortHandler);
int argc = 0;
const boo::SystemChar** argv = (const wchar_t**)(CommandLineToArgvW(lpCmdLine, &argc));
static boo::SystemChar selfPath[1024];
GetModuleFileNameW(nullptr, selfPath, 1024);
static const boo::SystemChar* booArgv[32] = {};
booArgv[0] = selfPath;
for (int i = 0; i < argc; ++i)
booArgv[i + 1] = argv[i];
logvisor::CreateWin32Console();
SetConsoleOutputCP(65001);
return wmain(argc + 1, booArgv);
}
#endif

View File

@@ -9,8 +9,7 @@ namespace amuse
{
class AudioGroupData;
using Sample = std::pair<AudioGroupSampleDirectory::Entry,
AudioGroupSampleDirectory::ADPCMParms>;
using Sample = std::pair<AudioGroupSampleDirectory::Entry, AudioGroupSampleDirectory::ADPCMParms>;
/** Runtime audio group index container */
class AudioGroup
@@ -21,19 +20,20 @@ class AudioGroup
const unsigned char* m_samp;
DataFormat m_fmt;
bool m_valid;
public:
operator bool() const {return m_valid;}
operator bool() const { return m_valid; }
AudioGroup(const AudioGroupData& data, GCNDataTag);
AudioGroup(const AudioGroupData& data, bool absOffs, N64DataTag);
AudioGroup(const AudioGroupData& data, bool absOffs, PCDataTag);
const Sample* getSample(int sfxId) const;
const unsigned char* getSampleData(uint32_t offset) const;
const AudioGroupProject& getProj() const {return m_proj;}
const AudioGroupPool& getPool() const {return m_pool;}
DataFormat getDataFormat() const {return m_fmt;}
const AudioGroupProject& getProj() const { return m_proj; }
const AudioGroupPool& getPool() const { return m_pool; }
const AudioGroupSampleDirectory& getSdir() const { return m_sdir; }
DataFormat getDataFormat() const { return m_fmt; }
};
}
#endif // __AMUSE_AUDIOGROUP_HPP__

View File

@@ -10,62 +10,117 @@ namespace amuse
class AudioGroupData
{
friend class Engine;
protected:
unsigned char* m_proj;
size_t m_projSz;
unsigned char* m_pool;
size_t m_poolSz;
unsigned char* m_sdir;
size_t m_sdirSz;
unsigned char* m_samp;
size_t m_sampSz;
DataFormat m_fmt;
bool m_absOffs;
AudioGroupData(unsigned char* proj, unsigned char* pool,
unsigned char* sdir, unsigned char* samp,
DataFormat fmt, bool absOffs)
: m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp),
m_fmt(fmt), m_absOffs(absOffs) {}
public:
AudioGroupData(unsigned char* proj, unsigned char* pool,
unsigned char* sdir, unsigned char* samp, GCNDataTag)
: m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp),
m_fmt(DataFormat::GCN), m_absOffs(true) {}
AudioGroupData(unsigned char* proj, unsigned char* pool,
unsigned char* sdir, unsigned char* samp, bool absOffs, N64DataTag)
: m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp),
m_fmt(DataFormat::N64), m_absOffs(absOffs) {}
AudioGroupData(unsigned char* proj, unsigned char* pool,
unsigned char* sdir, unsigned char* samp, bool absOffs, PCDataTag)
: m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp),
m_fmt(DataFormat::PC), m_absOffs(absOffs) {}
const unsigned char* getProj() const {return m_proj;}
const unsigned char* getPool() const {return m_pool;}
const unsigned char* getSdir() const {return m_sdir;}
const unsigned char* getSamp() const {return m_samp;}
operator bool() const
AudioGroupData(unsigned char* proj, size_t projSz, unsigned char* pool, size_t poolSz, unsigned char* sdir,
size_t sdirSz, unsigned char* samp, size_t sampSz, DataFormat fmt, bool absOffs)
: m_proj(proj)
, m_projSz(projSz)
, m_pool(pool)
, m_poolSz(poolSz)
, m_sdir(sdir)
, m_sdirSz(sdirSz)
, m_samp(samp)
, m_sampSz(sampSz)
, m_fmt(fmt)
, m_absOffs(absOffs)
{
return m_proj != nullptr && m_pool != nullptr && m_sdir != nullptr && m_samp != nullptr;
}
DataFormat getDataFormat() const {return m_fmt;}
bool getAbsoluteProjOffsets() const {return m_absOffs;}
public:
AudioGroupData(unsigned char* proj, size_t projSz, unsigned char* pool, size_t poolSz, unsigned char* sdir,
size_t sdirSz, unsigned char* samp, size_t sampSz, GCNDataTag)
: m_proj(proj)
, m_projSz(projSz)
, m_pool(pool)
, m_poolSz(poolSz)
, m_sdir(sdir)
, m_sdirSz(sdirSz)
, m_samp(samp)
, m_sampSz(sampSz)
, m_fmt(DataFormat::GCN)
, m_absOffs(true)
{
}
AudioGroupData(unsigned char* proj, size_t projSz, unsigned char* pool, size_t poolSz, unsigned char* sdir,
size_t sdirSz, unsigned char* samp, size_t sampSz, bool absOffs, N64DataTag)
: m_proj(proj)
, m_projSz(projSz)
, m_pool(pool)
, m_poolSz(poolSz)
, m_sdir(sdir)
, m_sdirSz(sdirSz)
, m_samp(samp)
, m_sampSz(sampSz)
, m_fmt(DataFormat::N64)
, m_absOffs(absOffs)
{
}
AudioGroupData(unsigned char* proj, size_t projSz, unsigned char* pool, size_t poolSz, unsigned char* sdir,
size_t sdirSz, unsigned char* samp, size_t sampSz, bool absOffs, PCDataTag)
: m_proj(proj)
, m_projSz(projSz)
, m_pool(pool)
, m_poolSz(poolSz)
, m_sdir(sdir)
, m_sdirSz(sdirSz)
, m_samp(samp)
, m_sampSz(sampSz)
, m_fmt(DataFormat::PC)
, m_absOffs(absOffs)
{
}
const unsigned char* getProj() const { return m_proj; }
const unsigned char* getPool() const { return m_pool; }
const unsigned char* getSdir() const { return m_sdir; }
const unsigned char* getSamp() const { return m_samp; }
unsigned char* getProj() { return m_proj; }
unsigned char* getPool() { return m_pool; }
unsigned char* getSdir() { return m_sdir; }
unsigned char* getSamp() { return m_samp; }
size_t getProjSize() const { return m_projSz; }
size_t getPoolSize() const { return m_poolSz; }
size_t getSdirSize() const { return m_sdirSz; }
size_t getSampSize() const { return m_sampSz; }
operator bool() const { return m_proj != nullptr && m_pool != nullptr && m_sdir != nullptr && m_samp != nullptr; }
DataFormat getDataFormat() const { return m_fmt; }
bool getAbsoluteProjOffsets() const { return m_absOffs; }
};
/** A buffer-owning version of AudioGroupData */
class IntrusiveAudioGroupData : public AudioGroupData
{
bool m_owns = true;
public:
using AudioGroupData::AudioGroupData;
~IntrusiveAudioGroupData();
IntrusiveAudioGroupData(const IntrusiveAudioGroupData&)=delete;
IntrusiveAudioGroupData& operator=(const IntrusiveAudioGroupData&)=delete;
IntrusiveAudioGroupData(const IntrusiveAudioGroupData&) = delete;
IntrusiveAudioGroupData& operator=(const IntrusiveAudioGroupData&) = delete;
IntrusiveAudioGroupData(IntrusiveAudioGroupData&& other);
IntrusiveAudioGroupData& operator=(IntrusiveAudioGroupData&& other);
};
void dangleOwnership() { m_owns = false; }
};
}
#endif // __AMUSE_AUDIOGROUPDATA_HPP__

View File

@@ -22,35 +22,38 @@ static inline double TimeCentsToSeconds(int32_t tc)
/** Defines phase-based volume curve for macro volume control */
struct ADSR
{
uint8_t attackFine; /* 0-255ms */
uint8_t attackCoarse; /* 0-65280ms */
uint8_t decayFine; /* 0-255ms */
uint8_t decayCoarse; /* 0-65280ms */
uint8_t sustainFine; /* multiply by 0.0244 for percentage */
uint8_t attackFine; /* 0-255ms */
uint8_t attackCoarse; /* 0-65280ms */
uint8_t decayFine; /* 0-255ms */
uint8_t decayCoarse; /* 0-65280ms */
uint8_t sustainFine; /* multiply by 0.0244 for percentage */
uint8_t sustainCoarse; /* multiply by 6.25 for percentage */
uint8_t releaseFine; /* 0-255ms */
uint8_t releaseFine; /* 0-255ms */
uint8_t releaseCoarse; /* 0-65280ms */
double getAttack() const {return (attackCoarse * 255 + attackFine) / 1000.0;}
double getDecay() const {return decayCoarse == 128 ? 0.0 : ((decayCoarse * 255 + decayFine) / 1000.0);}
double getSustain() const {return decayCoarse == 128 ? 1.0 : ((sustainCoarse * 6.25 + sustainFine * 0.0244) / 100.0);}
double getRelease() const {return (releaseCoarse * 255 + releaseFine) / 1000.0;}
double getAttack() const { return (attackCoarse * 255 + attackFine) / 1000.0; }
double getDecay() const { return decayCoarse == 128 ? 0.0 : ((decayCoarse * 255 + decayFine) / 1000.0); }
double getSustain() const
{
return decayCoarse == 128 ? 1.0 : ((sustainCoarse * 6.25 + sustainFine * 0.0244) / 100.0);
}
double getRelease() const { return (releaseCoarse * 255 + releaseFine) / 1000.0; }
};
/** Defines phase-based volume curve for macro volume control (modified DLS standard) */
struct ADSRDLS
{
uint32_t attack; /* 16.16 Time-cents */
uint32_t decay; /* 16.16 Time-cents */
uint16_t sustain; /* 0x1000 == 100% */
uint16_t release; /* milliseconds */
uint32_t attack; /* 16.16 Time-cents */
uint32_t decay; /* 16.16 Time-cents */
uint16_t sustain; /* 0x1000 == 100% */
uint16_t release; /* milliseconds */
uint32_t velToAttack; /* 16.16, 1000.0 == 100%; attack = <attack> + (vel/128) * <velToAttack> */
uint32_t keyToDecay; /* 16.16, 1000.0 == 100%; decay = <decay> + (note/128) * <keyToDecay> */
uint32_t keyToDecay; /* 16.16, 1000.0 == 100%; decay = <decay> + (note/128) * <keyToDecay> */
double getAttack() const {return TimeCentsToSeconds(attack);}
double getDecay() const {return TimeCentsToSeconds(decay);}
double getSustain() const {return sustain / double(0x1000);}
double getRelease() const {return release / double(1000);}
double getAttack() const { return TimeCentsToSeconds(attack); }
double getDecay() const { return TimeCentsToSeconds(decay); }
double getSustain() const { return sustain / double(0x1000); }
double getRelease() const { return release / double(1000); }
double getVelToAttack(int8_t vel) const
{
if (velToAttack == 0x80000000)
@@ -96,6 +99,7 @@ class AudioGroupPool
std::unordered_map<ObjectId, const unsigned char*> m_tables;
std::unordered_map<ObjectId, const Keymap*> m_keymaps;
std::unordered_map<ObjectId, std::vector<const LayerMapping*>> m_layers;
public:
AudioGroupPool(const unsigned char* data);
AudioGroupPool(const unsigned char* data, PCDataTag);
@@ -103,12 +107,9 @@ public:
const Keymap* keymap(ObjectId id) const;
const std::vector<const LayerMapping*>* layer(ObjectId id) const;
const ADSR* tableAsAdsr(ObjectId id) const;
const ADSRDLS* tableAsAdsrDLS(ObjectId id) const
{return reinterpret_cast<const ADSRDLS*>(tableAsAdsr(id));}
const Curve* tableAsCurves(ObjectId id) const
{return reinterpret_cast<const Curve*>(tableAsAdsr(id));}
const ADSRDLS* tableAsAdsrDLS(ObjectId id) const { return reinterpret_cast<const ADSRDLS*>(tableAsAdsr(id)); }
const Curve* tableAsCurves(ObjectId id) const { return reinterpret_cast<const Curve*>(tableAsAdsr(id)); }
};
}
#endif // __AMUSE_AUDIOGROUPPOOL_HPP__

View File

@@ -76,8 +76,8 @@ class AudioGroupProject
std::unique_ptr<SongGroupIndex::PageEntry[]> m_convNormalPages;
std::unique_ptr<SongGroupIndex::PageEntry[]> m_convDrumPages;
std::unique_ptr<std::array<SongGroupIndex::MIDISetup, 16>[]> m_convMidiSetups;
void _allocateConvBuffers(const unsigned char* data, bool absOffs, N64DataTag);
void _allocateConvBuffers(const unsigned char* data, bool absOffs, PCDataTag);
void _allocateConvBuffers(const unsigned char* data, N64DataTag);
void _allocateConvBuffers(const unsigned char* data, PCDataTag);
public:
AudioGroupProject(const unsigned char* data, GCNDataTag);
@@ -88,10 +88,9 @@ public:
const SongGroupIndex* getSongGroupIndex(int groupId) const;
const SFXGroupIndex* getSFXGroupIndex(int groupId) const;
const std::unordered_map<int, SongGroupIndex>& songGroups() const {return m_songGroups;}
const std::unordered_map<int, SFXGroupIndex>& sfxGroups() const {return m_sfxGroups;}
const std::unordered_map<int, SongGroupIndex>& songGroups() const { return m_songGroups; }
const std::unordered_map<int, SFXGroupIndex>& sfxGroups() const { return m_sfxGroups; }
};
}
#endif // __AMUSE_AUDIOGROUPPROJECT_HPP__

View File

@@ -12,6 +12,7 @@ namespace amuse
class AudioGroupSampleDirectory
{
friend class AudioGroup;
public:
struct Entry
{
@@ -26,8 +27,7 @@ public:
uint32_t m_adpcmParmOffset;
void swapBig();
};
union ADPCMParms
{
union ADPCMParms {
struct DSPParms
{
uint16_t m_bytesPerFrame;
@@ -44,15 +44,17 @@ public:
void swapBigDSP();
void swapBigVADPCM();
};
private:
std::unordered_map<uint16_t, std::pair<Entry, ADPCMParms>> m_entries;
public:
AudioGroupSampleDirectory(const unsigned char* data, GCNDataTag);
AudioGroupSampleDirectory(const unsigned char* data, const unsigned char* sampData,
bool absOffs, N64DataTag);
AudioGroupSampleDirectory(const unsigned char* data, const unsigned char* sampData, bool absOffs, N64DataTag);
AudioGroupSampleDirectory(const unsigned char* data, bool absOffs, PCDataTag);
};
const std::unordered_map<uint16_t, std::pair<Entry, ADPCMParms>>& sampleEntries() const { return m_entries; }
};
}
#endif // __AMUSE_AUDIOGROUPSAMPLEDIR_HPP__

View File

@@ -10,7 +10,6 @@
#include "IBackendVoiceAllocator.hpp"
#include <mutex>
#include <list>
#include <chrono>
namespace amuse
{
@@ -23,18 +22,21 @@ class BooBackendVoice : public IBackendVoice
struct VoiceCallback : boo::IAudioVoiceCallback
{
BooBackendVoice& m_parent;
void preSupplyAudio(boo::IAudioVoice& voice, double dt);
size_t supplyAudio(boo::IAudioVoice& voice, size_t frames, int16_t* data);
void routeAudio(size_t frames, double dt, int busId, int16_t* in, int16_t* out);
void routeAudio(size_t frames, double dt, int busId, int32_t* in, int32_t* out);
void routeAudio(size_t frames, double dt, int busId, float* in, float* out);
VoiceCallback(BooBackendVoice& parent) : m_parent(parent) {}
} m_cb;
std::unique_ptr<boo::IAudioVoice> m_booVoice;
public:
BooBackendVoice(boo::IAudioVoiceEngine& engine, Voice& clientVox,
double sampleRate, bool dynamicPitch);
BooBackendVoice(boo::IAudioSubmix& submix, Voice& clientVox,
double sampleRate, bool dynamicPitch);
BooBackendVoice(boo::IAudioVoiceEngine& engine, Voice& clientVox, double sampleRate, bool dynamicPitch);
void resetSampleRate(double sampleRate);
void setMatrixCoefficients(const float coefs[8], bool slew);
void setSubmixMatrixCoefficients(const float coefs[8], bool slew);
void resetChannelLevels();
void setChannelLevels(IBackendSubmix* submix, const float coefs[8], bool slew);
void setPitchRatio(double ratio, bool slew);
void start();
void stop();
@@ -44,25 +46,23 @@ public:
class BooBackendSubmix : public IBackendSubmix
{
friend class BooBackendVoiceAllocator;
friend class BooBackendVoice;
Submix& m_clientSmx;
struct SubmixCallback : boo::IAudioSubmixCallback
{
BooBackendSubmix& m_parent;
bool canApplyEffect() const;
void applyEffect(int16_t* audio, size_t frameCount,
const boo::ChannelMap& chanMap, double sampleRate) const;
void applyEffect(int32_t* audio, size_t frameCount,
const boo::ChannelMap& chanMap, double sampleRate) const;
void applyEffect(float* audio, size_t frameCount,
const boo::ChannelMap& chanMap, double sampleRate) const;
void applyEffect(int16_t* audio, size_t frameCount, const boo::ChannelMap& chanMap, double sampleRate) const;
void applyEffect(int32_t* audio, size_t frameCount, const boo::ChannelMap& chanMap, double sampleRate) const;
void applyEffect(float* audio, size_t frameCount, const boo::ChannelMap& chanMap, double sampleRate) const;
void resetOutputSampleRate(double sampleRate);
SubmixCallback(BooBackendSubmix& parent) : m_parent(parent) {}
} m_cb;
std::unique_ptr<boo::IAudioSubmix> m_booSubmix;
public:
BooBackendSubmix(boo::IAudioVoiceEngine& engine, Submix& clientSmx);
BooBackendSubmix(boo::IAudioSubmix& parent, Submix& clientSmx);
void setChannelGains(const float gains[8]);
std::unique_ptr<IBackendVoice> allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch);
BooBackendSubmix(boo::IAudioVoiceEngine& engine, Submix& clientSmx, bool mainOut, int busId);
void setSendLevel(IBackendSubmix* submix, float level, bool slew);
double getSampleRate() const;
SubmixFormat getSampleFormat() const;
};
@@ -75,13 +75,14 @@ class BooBackendMIDIReader : public IMIDIReader, public boo::IMIDIReader
std::unique_ptr<boo::IMIDIIn> m_midiIn;
boo::MIDIDecoder m_decoder;
std::list<std::pair<std::chrono::steady_clock::time_point, std::vector<uint8_t>>> m_queue;
bool m_useLock;
std::list<std::pair<double, std::vector<uint8_t>>> m_queue;
std::mutex m_midiMutex;
void _MIDIReceive(std::vector<uint8_t>&& bytes);
void _MIDIReceive(std::vector<uint8_t>&& bytes, double time);
public:
~BooBackendMIDIReader();
BooBackendMIDIReader(Engine& engine, const char* name);
BooBackendMIDIReader(Engine& engine, const char* name, bool useLock);
std::string description();
void pumpReader(double dt);
@@ -119,17 +120,18 @@ class BooBackendVoiceAllocator : public IBackendVoiceAllocator
{
friend class BooBackendMIDIReader;
boo::IAudioVoiceEngine& m_booEngine;
public:
BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine);
std::unique_ptr<IBackendVoice> allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch);
std::unique_ptr<IBackendSubmix> allocateSubmix(Submix& clientSmx);
std::unique_ptr<IBackendSubmix> allocateSubmix(Submix& clientSmx, bool mainOut, int busId);
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices();
std::unique_ptr<IMIDIReader> allocateMIDIReader(Engine& engine, const char* name=nullptr);
std::unique_ptr<IMIDIReader> allocateMIDIReader(Engine& engine, const char* name = nullptr);
void register5MsCallback(std::function<void(double)>&& callback);
AudioChannelSet getAvailableSet();
void pumpAndMixVoices();
void setVolume(float vol);
};
}
#endif // __AMUSE_BOO_BACKEND_HPP__

View File

@@ -5,9 +5,13 @@
#include <limits.h>
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <string>
#include <cstring>
#ifndef _MSC_VER
#include <strings.h>
#include <sys/stat.h>
#endif
namespace amuse
@@ -21,20 +25,50 @@ namespace amuse
#endif
#endif
static inline int CompareCaseInsensitive(const char* a, const char* b)
#ifdef _WIN32
using SystemString = std::wstring;
using SystemChar = wchar_t;
#ifndef _S
#define _S(val) L##val
#endif
typedef struct _stat Sstat;
static inline int Mkdir(const wchar_t* path, int) { return _wmkdir(path); }
static inline int Stat(const wchar_t* path, Sstat* statout) { return _wstat(path, statout); }
#else
using SystemString = std::string;
using SystemChar = char;
#ifndef _S
#define _S(val) val
#endif
typedef struct stat Sstat;
static inline int Mkdir(const char* path, mode_t mode) { return mkdir(path, mode); }
static inline int Stat(const char* path, Sstat* statout) { return stat(path, statout); }
#endif
#if _WIN32
static inline int CompareCaseInsensitive(const char* a, const char* b) { return _stricmp(a, b); }
#endif
static inline int CompareCaseInsensitive(const SystemChar* a, const SystemChar* b)
{
#if _WIN32
return _stricmp(a, b);
return _wcsicmp(a, b);
#else
return strcasecmp(a, b);
#endif
}
template <typename T>
static inline T clamp(T a, T val, T b) {return std::max<T>(a, std::min<T>(b, val));}
static inline T clamp(T a, T val, T b)
{
return std::max<T>(a, std::min<T>(b, val));
}
template <typename T>
static inline T ClampFull(float in) {return in;}
static inline T ClampFull(float in)
{
return in;
}
template <>
inline int16_t ClampFull<int16_t>(float in)
@@ -59,10 +93,6 @@ inline int32_t ClampFull<int32_t>(float in)
template <>
inline float ClampFull<float>(float in)
{
if (in < -1.f)
return -1.f;
else if (in > 1.f)
return 1.f;
return in;
}
@@ -70,6 +100,56 @@ inline float ClampFull<float>(float in)
#define M_PIF 3.14159265358979323846f /* pi */
#endif
#if __GNUC__
__attribute__((__format__(__printf__, 1, 2)))
#endif
static inline void
Printf(const SystemChar* fmt, ...)
{
va_list args;
va_start(args, fmt);
#if _WIN32
vwprintf(fmt, args);
#else
vprintf(fmt, args);
#endif
va_end(args);
}
#if __GNUC__
__attribute__((__format__(__printf__, 3, 4)))
#endif
static inline void
SNPrintf(SystemChar* str, size_t maxlen, const SystemChar* format, ...)
{
va_list va;
va_start(va, format);
#if _WIN32
_vsnwprintf(str, maxlen, format, va);
#else
vsnprintf(str, maxlen, format, va);
#endif
va_end(va);
}
static inline const SystemChar* StrRChr(const SystemChar* str, SystemChar ch)
{
#if _WIN32
return wcsrchr(str, ch);
#else
return strrchr(str, ch);
#endif
}
static inline SystemChar* StrRChr(SystemChar* str, SystemChar ch)
{
#if _WIN32
return wcsrchr(str, ch);
#else
return strrchr(str, ch);
#endif
}
static inline int FSeek(FILE* fp, int64_t offset, int whence)
{
#if _WIN32
@@ -92,6 +172,20 @@ static inline int64_t FTell(FILE* fp)
#endif
}
static inline FILE* FOpen(const SystemChar* path, const SystemChar* mode)
{
#if _WIN32
FILE* fp = _wfopen(path, mode);
if (!fp)
return nullptr;
#else
FILE* fp = fopen(path, mode);
if (!fp)
return nullptr;
#endif
return fp;
}
#undef bswap16
#undef bswap32
#undef bswap64
@@ -131,24 +225,20 @@ static inline T bswap64(T val)
#elif _WIN32
return _byteswap_uint64(val);
#else
return ((val & 0xFF00000000000000ULL) >> 56) |
((val & 0x00FF000000000000ULL) >> 40) |
((val & 0x0000FF0000000000ULL) >> 24) |
((val & 0x000000FF00000000ULL) >> 8) |
((val & 0x00000000FF000000ULL) << 8) |
((val & 0x0000000000FF0000ULL) << 24) |
((val & 0x000000000000FF00ULL) << 40) |
((val & 0x00000000000000FFULL) << 56);
return ((val & 0xFF00000000000000ULL) >> 56) | ((val & 0x00FF000000000000ULL) >> 40) |
((val & 0x0000FF0000000000ULL) >> 24) | ((val & 0x000000FF00000000ULL) >> 8) |
((val & 0x00000000FF000000ULL) << 8) | ((val & 0x0000000000FF0000ULL) << 24) |
((val & 0x000000000000FF00ULL) << 40) | ((val & 0x00000000000000FFULL) << 56);
#endif
}
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
static inline int16_t SBig(int16_t val) {return bswap16(val);}
static inline uint16_t SBig(uint16_t val) {return bswap16(val);}
static inline int32_t SBig(int32_t val) {return bswap32(val);}
static inline uint32_t SBig(uint32_t val) {return bswap32(val);}
static inline int64_t SBig(int64_t val) {return bswap64(val);}
static inline uint64_t SBig(uint64_t val) {return bswap64(val);}
static inline int16_t SBig(int16_t val) { return bswap16(val); }
static inline uint16_t SBig(uint16_t val) { return bswap16(val); }
static inline int32_t SBig(int32_t val) { return bswap32(val); }
static inline uint32_t SBig(uint32_t val) { return bswap32(val); }
static inline int64_t SBig(int64_t val) { return bswap64(val); }
static inline uint64_t SBig(uint64_t val) { return bswap64(val); }
static inline float SBig(float val)
{
int32_t ival = bswap32(*((int32_t*)(&val)));
@@ -160,28 +250,27 @@ static inline double SBig(double val)
return *((double*)(&ival));
}
#ifndef SBIG
#define SBIG(q) ( ( (q) & 0x000000FF ) << 24 | ( (q) & 0x0000FF00 ) << 8 \
| ( (q) & 0x00FF0000 ) >> 8 | ( (q) & 0xFF000000 ) >> 24 )
#define SBIG(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24)
#endif
static inline int16_t SLittle(int16_t val) {return val;}
static inline uint16_t SLittle(uint16_t val) {return val;}
static inline int32_t SLittle(int32_t val) {return val;}
static inline uint32_t SLittle(uint32_t val) {return val;}
static inline int64_t SLittle(int64_t val) {return val;}
static inline uint64_t SLittle(uint64_t val) {return val;}
static inline float SLittle(float val) {return val;}
static inline double SLittle(double val) {return val;}
static inline int16_t SLittle(int16_t val) { return val; }
static inline uint16_t SLittle(uint16_t val) { return val; }
static inline int32_t SLittle(int32_t val) { return val; }
static inline uint32_t SLittle(uint32_t val) { return val; }
static inline int64_t SLittle(int64_t val) { return val; }
static inline uint64_t SLittle(uint64_t val) { return val; }
static inline float SLittle(float val) { return val; }
static inline double SLittle(double val) { return val; }
#ifndef SLITTLE
#define SLITTLE(q) (q)
#endif
#else
static inline int16_t SLittle(int16_t val) {return bswap16(val);}
static inline uint16_t SLittle(uint16_t val) {return bswap16(val);}
static inline int32_t SLittle(int32_t val) {return bswap32(val);}
static inline uint32_t SLittle(uint32_t val) {return bswap32(val);}
static inline int64_t SLittle(int64_t val) {return bswap64(val);}
static inline uint64_t SLittle(uint64_t val) {return bswap64(val);}
static inline int16_t SLittle(int16_t val) { return bswap16(val); }
static inline uint16_t SLittle(uint16_t val) { return bswap16(val); }
static inline int32_t SLittle(int32_t val) { return bswap32(val); }
static inline uint32_t SLittle(uint32_t val) { return bswap32(val); }
static inline int64_t SLittle(int64_t val) { return bswap64(val); }
static inline uint64_t SLittle(uint64_t val) { return bswap64(val); }
static inline float SLittle(float val)
{
int32_t ival = bswap32(*((int32_t*)(&val)));
@@ -193,18 +282,17 @@ static inline double SLittle(double val)
return *((double*)(&ival));
}
#ifndef SLITTLE
#define SLITTLE(q) ( ( (q) & 0x000000FF ) << 24 | ( (q) & 0x0000FF00 ) << 8 \
| ( (q) & 0x00FF0000 ) >> 8 | ( (q) & 0xFF000000 ) >> 24 )
#define SLITTLE(q) (((q)&0x000000FF) << 24 | ((q)&0x0000FF00) << 8 | ((q)&0x00FF0000) >> 8 | ((q)&0xFF000000) >> 24)
#endif
static inline int16_t SBig(int16_t val) {return val;}
static inline uint16_t SBig(uint16_t val) {return val;}
static inline int32_t SBig(int32_t val) {return val;}
static inline uint32_t SBig(uint32_t val) {return val;}
static inline int64_t SBig(int64_t val) {return val;}
static inline uint64_t SBig(uint64_t val) {return val;}
static inline float SBig(float val) {return val;}
static inline double SBig(double val) {return val;}
static inline int16_t SBig(int16_t val) { return val; }
static inline uint16_t SBig(uint16_t val) { return val; }
static inline int32_t SBig(int32_t val) { return val; }
static inline uint32_t SBig(uint32_t val) { return val; }
static inline int64_t SBig(int64_t val) { return val; }
static inline uint64_t SBig(uint64_t val) { return val; }
static inline float SBig(float val) { return val; }
static inline double SBig(double val) { return val; }
#ifndef SBIG
#define SBIG(q) (q)
#endif
@@ -219,14 +307,19 @@ enum class DataFormat
};
/** Meta-type for selecting GameCube (MusyX 2.0) data formats */
struct GCNDataTag {};
struct GCNDataTag
{
};
/** Meta-type for selecting N64 (MusyX 1.0) data formats */
struct N64DataTag {};
struct N64DataTag
{
};
/** Meta-type for selecting PC (MusyX 1.0) data formats */
struct PCDataTag {};
struct PCDataTag
{
};
}
#endif // __AMUSE_COMMON_HPP__

View File

@@ -2,6 +2,7 @@
#define __AMUSE_CONTAINERREGISTRY_HPP__
#include "AudioGroupData.hpp"
#include "Common.hpp"
#include <string>
#include <vector>
#include <memory>
@@ -20,8 +21,7 @@ public:
MetroidPrime2,
RogueSquadronPC,
RogueSquadronN64,
BattleForNabooPC,
BattleForNabooN64,
Factor5N64Rev,
RogueSquadron2,
RogueSquadron3
};
@@ -32,14 +32,17 @@ public:
int16_t m_groupId;
int16_t m_setupId;
SongData(std::unique_ptr<uint8_t[]>&& data, size_t size, int16_t groupId, int16_t setupId)
: m_data(std::move(data)), m_size(size), m_groupId(groupId), m_setupId(setupId) {}
: m_data(std::move(data)), m_size(size), m_groupId(groupId), m_setupId(setupId)
{
}
};
static const char* TypeToName(Type tp);
static Type DetectContainerType(const char* path);
static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadContainer(const char* path);
static std::vector<std::pair<std::string, SongData>> LoadSongs(const char* path);
static const SystemChar* TypeToName(Type tp);
static Type DetectContainerType(const SystemChar* path);
static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadContainer(const SystemChar* path);
static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadContainer(const SystemChar* path,
Type& typeOut);
static std::vector<std::pair<SystemString, SongData>> LoadSongs(const SystemChar* path);
};
}
#endif // __AMUSE_CONTAINERREGISTRY_HPP__

View File

@@ -0,0 +1,77 @@
#ifndef __AMUSE_DIRECTORY_ENUMERATOR__
#define __AMUSE_DIRECTORY_ENUMERATOR__
#include "Common.hpp"
#include <vector>
namespace amuse
{
struct CaseInsensitiveCompare
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
#if _WIN32
if (_stricmp(lhs.c_str(), rhs.c_str()) < 0)
#else
if (strcasecmp(lhs.c_str(), rhs.c_str()) < 0)
#endif
return true;
return false;
}
#if _WIN32
bool operator()(const std::wstring& lhs, const std::wstring& rhs) const
{
if (_wcsicmp(lhs.c_str(), rhs.c_str()) < 0)
return true;
return false;
}
#endif
};
class DirectoryEnumerator
{
public:
enum class Mode
{
Native,
DirsSorted,
FilesSorted,
DirsThenFilesSorted
};
struct Entry
{
SystemString m_path;
SystemString m_name;
size_t m_fileSz;
bool m_isDir;
private:
friend class DirectoryEnumerator;
Entry(SystemString&& path, const SystemChar* name, size_t sz, bool isDir)
: m_path(std::move(path)), m_name(name), m_fileSz(sz), m_isDir(isDir)
{
}
};
private:
std::vector<Entry> m_entries;
public:
DirectoryEnumerator(const SystemString& path, Mode mode = Mode::DirsThenFilesSorted, bool sizeSort = false,
bool reverse = false, bool noHidden = false)
: DirectoryEnumerator(path.c_str(), mode, sizeSort, reverse, noHidden)
{
}
DirectoryEnumerator(const SystemChar* path, Mode mode = Mode::DirsThenFilesSorted, bool sizeSort = false,
bool reverse = false, bool noHidden = false);
operator bool() const { return m_entries.size() != 0; }
size_t size() const { return m_entries.size(); }
std::vector<Entry>::const_iterator begin() const { return m_entries.cbegin(); }
std::vector<Entry>::const_iterator end() const { return m_entries.cend(); }
};
}
#endif // __AMUSE_DIRECTORY_ENUMERATOR__

View File

@@ -12,15 +12,15 @@ class EffectBaseTypeless
{
public:
virtual ~EffectBaseTypeless() = default;
virtual void resetOutputSampleRate(double sampleRate) = 0;
};
template <typename T>
class EffectBase : public EffectBaseTypeless
{
public:
virtual void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap)=0;
virtual void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap) = 0;
};
}
#endif // __AMUSE_EFFECTBASE_HPP__

View File

@@ -17,12 +17,13 @@ class EffectChorus
{
uint32_t x90_baseDelay; /**< [5, 15] minimum value (in ms) for computed delay */
uint32_t x94_variation; /**< [0, 5] time error (in ms) to set delay within */
uint32_t x98_period; /**< [500, 10000] time (in ms) of one delay-shift cycle */
bool m_dirty = true; /**< needs update of internal parameter data */
uint32_t x98_period; /**< [500, 10000] time (in ms) of one delay-shift cycle */
bool m_dirty = true; /**< needs update of internal parameter data */
template <typename T>
friend class EffectChorusImp;
EffectChorus(uint32_t baseDelay, uint32_t variation, uint32_t period);
public:
template <typename T>
using ImpType = EffectChorusImp<T>;
@@ -53,45 +54,46 @@ public:
template <typename T>
class EffectChorusImp : public EffectBase<T>, public EffectChorus
{
T* x0_lastChans[8][AMUSE_CHORUS_NUM_BLOCKS]; /**< Evenly-allocated pointer-table for each channel's delay */
T* x0_lastChans[8][AMUSE_CHORUS_NUM_BLOCKS] = {}; /**< Evenly-allocated pointer-table for each channel's delay */
uint8_t x24_currentLast = 1; /**< Last 5ms block-idx to be processed */
T x28_oldChans[8][4] = {}; /**< Unprocessed history of previous 4 samples */
T x28_oldChans[8][4] = {}; /**< Unprocessed history of previous 4 samples */
uint32_t x58_currentPosLo = 0; /**< 16.7 fixed-point low-part of sample index */
uint32_t x5c_currentPosHi = 0; /**< 16.7 fixed-point high-part of sample index */
int32_t x60_pitchOffset; /**< packed 16.16 fixed-point value of pitchHi and pitchLo quantities */
int32_t x60_pitchOffset; /**< packed 16.16 fixed-point value of pitchHi and pitchLo quantities */
uint32_t x64_pitchOffsetPeriodCount; /**< trigger value for flipping SRC state */
uint32_t x68_pitchOffsetPeriod; /**< intermediate block window quantity for calculating SRC state */
uint32_t x68_pitchOffsetPeriod; /**< intermediate block window quantity for calculating SRC state */
struct SrcInfo
{
T* x6c_dest; /**< selected channel's live buffer */
T* x70_smpBase; /**< selected channel's delay buffer */
T* x74_old; /**< selected channel's 4-sample history buffer */
uint32_t x78_posLo; /**< 16.7 fixed-point low-part of sample index */
uint32_t x7c_posHi; /**< 16.7 fixed-point high-part of sample index */
uint32_t x80_pitchLo; /**< 16.7 fixed-point low-part of sample-rate conversion differential */
uint32_t x84_pitchHi; /**< 16.7 fixed-point low-part of sample-rate conversion differential */
uint32_t x88_trigger; /**< total count of samples per channel across all blocks */
T* x6c_dest; /**< selected channel's live buffer */
T* x70_smpBase; /**< selected channel's delay buffer */
T* x74_old; /**< selected channel's 4-sample history buffer */
uint32_t x78_posLo; /**< 16.7 fixed-point low-part of sample index */
uint32_t x7c_posHi; /**< 16.7 fixed-point high-part of sample index */
uint32_t x80_pitchLo; /**< 16.7 fixed-point low-part of sample-rate conversion differential */
uint32_t x84_pitchHi; /**< 16.7 fixed-point low-part of sample-rate conversion differential */
uint32_t x88_trigger; /**< total count of samples per channel across all blocks */
uint32_t x8c_target = 0; /**< value to reset to when trigger hit */
void doSrc1(size_t blockSamples, size_t chanCount);
void doSrc2(size_t blockSamples, size_t chanCount);
} x6c_src;
uint32_t m_sampsPerMs; /**< canonical count of samples per ms for the current backend */
uint32_t m_sampsPerMs; /**< canonical count of samples per ms for the current backend */
uint32_t m_blockSamples; /**< count of samples in a 5ms block */
void _setup(double sampleRate);
void _update();
public:
~EffectChorusImp();
EffectChorusImp(uint32_t baseDelay, uint32_t variation, uint32_t period, double sampleRate);
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
void resetOutputSampleRate(double sampleRate) { _setup(sampleRate); }
};
}
#endif // __AMUSE_EFFECTCHORUS_HPP__

View File

@@ -15,10 +15,10 @@ class EffectDelayImp;
class EffectDelay
{
protected:
uint32_t x3c_delay[8]; /**< [10, 5000] time in ms of each channel's delay */
uint32_t x3c_delay[8]; /**< [10, 5000] time in ms of each channel's delay */
uint32_t x48_feedback[8]; /**< [0, 100] percent to mix delayed signal with input signal */
uint32_t x54_output[8]; /**< [0, 100] total output percent */
bool m_dirty = true; /**< needs update of internal parameter data */
uint32_t x54_output[8]; /**< [0, 100] total output percent */
bool m_dirty = true; /**< needs update of internal parameter data */
public:
template <typename T>
using ImpType = EffectDelayImp<T>;
@@ -26,7 +26,7 @@ public:
void setDelay(uint32_t delay)
{
delay = clamp(10u, delay, 5000u);
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
x3c_delay[i] = delay;
m_dirty = true;
}
@@ -40,7 +40,7 @@ public:
void setFeedback(uint32_t feedback)
{
feedback = clamp(0u, feedback, 100u);
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
x48_feedback[i] = feedback;
m_dirty = true;
}
@@ -55,7 +55,7 @@ public:
void setOutput(uint32_t output)
{
output = clamp(0u, output, 100u);
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
x54_output[i] = output;
m_dirty = true;
}
@@ -71,21 +71,23 @@ public:
template <typename T>
class EffectDelayImp : public EffectBase<T>, public EffectDelay
{
uint32_t x0_currentSize[8]; /**< per-channel delay-line buffer sizes */
uint32_t xc_currentPos[8]; /**< per-channel block-index */
uint32_t x0_currentSize[8]; /**< per-channel delay-line buffer sizes */
uint32_t xc_currentPos[8]; /**< per-channel block-index */
uint32_t x18_currentFeedback[8]; /**< [0, 128] feedback attenuator */
uint32_t x24_currentOutput[8]; /**< [0, 128] total attenuator */
uint32_t x24_currentOutput[8]; /**< [0, 128] total attenuator */
std::unique_ptr<T[]> x30_chanLines[8]; /**< delay-line buffers for each channel */
uint32_t m_sampsPerMs; /**< canonical count of samples per ms for the current backend */
uint32_t m_sampsPerMs; /**< canonical count of samples per ms for the current backend */
uint32_t m_blockSamples; /**< count of samples in a 5ms block */
void _setup(double sampleRate);
void _update();
public:
EffectDelayImp(uint32_t initDelay, uint32_t initFeedback, uint32_t initOutput, double sampleRate);
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
void resetOutputSampleRate(double sampleRate) { _setup(sampleRate); }
};
}
#endif // __AMUSE_EFFECTDELAY_HPP__

View File

@@ -31,19 +31,20 @@ class EffectReverbHiImp;
class EffectReverbStd
{
protected:
float x140_x1c8_coloration; /**< [0.0, 1.0] influences filter coefficients to define surface characteristics of a room */
float x144_x1cc_mix; /**< [0.0, 1.0] dry/wet mix factor of reverb effect */
float x148_x1d0_time; /**< [0.01, 10.0] time in seconds for reflection decay */
float x14c_x1d4_damping; /**< [0.0, 1.0] damping factor influencing low-pass filter of reflections */
float x150_x1d8_preDelay; /**< [0.0, 0.1] time in seconds before initial reflection heard */
bool m_dirty = true; /**< needs update of internal parameter data */
float x140_x1c8_coloration; /**< [0.0, 1.0] influences filter coefficients to define surface characteristics of a
room */
float x144_x1cc_mix; /**< [0.0, 1.0] dry/wet mix factor of reverb effect */
float x148_x1d0_time; /**< [0.01, 10.0] time in seconds for reflection decay */
float x14c_x1d4_damping; /**< [0.0, 1.0] damping factor influencing low-pass filter of reflections */
float x150_x1d8_preDelay; /**< [0.0, 0.1] time in seconds before initial reflection heard */
bool m_dirty = true; /**< needs update of internal parameter data */
template <typename T>
friend class EffectReverbStdImp;
template <typename T>
friend class EffectReverbHiImp;
EffectReverbStd(float coloration, float mix, float time,
float damping, float preDelay);
EffectReverbStd(float coloration, float mix, float time, float damping, float preDelay);
public:
template <typename T>
using ImpType = EffectReverbStdImp<T>;
@@ -86,8 +87,8 @@ class EffectReverbHi : public EffectReverbStd
template <typename T>
friend class EffectReverbHiImp;
EffectReverbHi(float coloration, float mix, float time,
float damping, float preDelay, float crosstalk);
EffectReverbHi(float coloration, float mix, float time, float damping, float preDelay, float crosstalk);
public:
template <typename T>
using ImpType = EffectReverbHiImp<T>;
@@ -103,52 +104,56 @@ public:
template <typename T>
class EffectReverbStdImp : public EffectBase<T>, public EffectReverbStd
{
ReverbDelayLine x0_AP[8][2] = {}; /**< All-pass delay lines */
ReverbDelayLine x78_C[8][2] = {}; /**< Comb delay lines */
float xf0_allPassCoef = 0.f; /**< All-pass mix coefficient */
float xf4_combCoef[8][2] = {}; /**< Comb mix coefficients */
float x10c_lpLastout[8] = {}; /**< Last low-pass results */
float x118_level = 0.f; /**< Internal wet/dry mix factor */
float x11c_damping = 0.f; /**< Low-pass damping */
int32_t x120_preDelayTime = 0; /**< Sample count of pre-delay */
ReverbDelayLine x0_AP[8][2] = {}; /**< All-pass delay lines */
ReverbDelayLine x78_C[8][2] = {}; /**< Comb delay lines */
float xf0_allPassCoef = 0.f; /**< All-pass mix coefficient */
float xf4_combCoef[8][2] = {}; /**< Comb mix coefficients */
float x10c_lpLastout[8] = {}; /**< Last low-pass results */
float x118_level = 0.f; /**< Internal wet/dry mix factor */
float x11c_damping = 0.f; /**< Low-pass damping */
int32_t x120_preDelayTime = 0; /**< Sample count of pre-delay */
std::unique_ptr<float[]> x124_preDelayLine[8]; /**< Dedicated pre-delay buffers */
float* x130_preDelayPtr[8] = {}; /**< Current pre-delay pointers */
float* x130_preDelayPtr[8] = {}; /**< Current pre-delay pointers */
double m_sampleRate; /**< copy of sample rate */
void _setup(double sampleRate);
void _update();
public:
EffectReverbStdImp(float coloration, float mix, float time,
float damping, float preDelay, double sampleRate);
EffectReverbStdImp(float coloration, float mix, float time, float damping, float preDelay, double sampleRate);
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
void resetOutputSampleRate(double sampleRate) { _setup(sampleRate); }
};
/** High-quality 3-stage reverb with per-channel low-pass and crosstalk */
template <typename T>
class EffectReverbHiImp : public EffectBase<T>, public EffectReverbHi
{
ReverbDelayLine x0_AP[8][2] = {}; /**< All-pass delay lines */
ReverbDelayLine x78_LP[8] = {}; /**< Per-channel low-pass delay-lines */
ReverbDelayLine xb4_C[8][3] = {}; /**< Comb delay lines */
float x168_allPassCoef = 0.f; /**< All-pass mix coefficient */
float x16c_combCoef[8][3] = {}; /**< Comb mix coefficients */
float x190_lpLastout[8] = {}; /**< Last low-pass results */
float x19c_level = 0.f; /**< Internal wet/dry mix factor */
float x1a0_damping = 0.f; /**< Low-pass damping */
int32_t x1a4_preDelayTime = 0; /**< Sample count of pre-delay */
ReverbDelayLine x0_AP[8][2] = {}; /**< All-pass delay lines */
ReverbDelayLine x78_LP[8] = {}; /**< Per-channel low-pass delay-lines */
ReverbDelayLine xb4_C[8][3] = {}; /**< Comb delay lines */
float x168_allPassCoef = 0.f; /**< All-pass mix coefficient */
float x16c_combCoef[8][3] = {}; /**< Comb mix coefficients */
float x190_lpLastout[8] = {}; /**< Last low-pass results */
float x19c_level = 0.f; /**< Internal wet/dry mix factor */
float x1a0_damping = 0.f; /**< Low-pass damping */
int32_t x1a4_preDelayTime = 0; /**< Sample count of pre-delay */
std::unique_ptr<float[]> x1ac_preDelayLine[8]; /**< Dedicated pre-delay buffers */
float* x1b8_preDelayPtr[8] = {}; /**< Current pre-delay pointers */
float* x1b8_preDelayPtr[8] = {}; /**< Current pre-delay pointers */
float x1a8_internalCrosstalk = 0.f;
double m_sampleRate; /**< copy of sample rate */
void _setup(double sampleRate);
void _update();
void _handleReverb(T* audio, int chanIdx, int chanCount, int sampleCount);
void _doCrosstalk(T* audio, float wet, float dry, int chanCount, int sampleCount);
public:
EffectReverbHiImp(float coloration, float mix, float time,
float damping, float preDelay, float crosstalk, double sampleRate);
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
};
public:
EffectReverbHiImp(float coloration, float mix, float time, float damping, float preDelay, float crosstalk,
double sampleRate);
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
void resetOutputSampleRate(double sampleRate) { _setup(sampleRate); }
};
}
#endif // __AMUSE_EFFECTREVERB_HPP__

View File

@@ -17,6 +17,7 @@ class Emitter : public Entity
friend class Engine;
void _destroy();
public:
~Emitter();
Emitter(Engine& engine, const AudioGroup& group, std::shared_ptr<Voice>&& vox);
@@ -28,9 +29,8 @@ public:
void setMinVol(float minVol);
void setFalloff(float falloff);
std::shared_ptr<Voice>& getVoice() {return m_vox;}
std::shared_ptr<Voice>& getVoice() { return m_vox; }
};
}
#endif // __AMUSE_EMITTER_HPP__

View File

@@ -9,6 +9,7 @@
#include "Emitter.hpp"
#include "AudioGroupSampleDirectory.hpp"
#include "Sequencer.hpp"
#include "Studio.hpp"
namespace amuse
{
@@ -22,7 +23,7 @@ class IMIDIReader;
enum class AmplitudeMode
{
PerSample, /**< Per-sample amplitude evaluation (dt = 1.0 / sampleRate, rather CPU demanding) */
PerSample, /**< Per-sample amplitude evaluation (dt = 1.0 / sampleRate, rather CPU demanding) */
BlockLinearized /**< Per-block lerp amplitude evaluation (dt = 160.0 / sampleRate) */
};
@@ -32,6 +33,7 @@ class Engine
friend class Voice;
friend class Emitter;
friend class Sequencer;
friend class Studio;
friend struct Sequencer::ChannelState;
IBackendVoiceAllocator& m_backend;
@@ -41,7 +43,9 @@ class Engine
std::list<std::shared_ptr<Voice>> m_activeVoices;
std::list<std::shared_ptr<Emitter>> m_activeEmitters;
std::list<std::shared_ptr<Sequencer>> m_activeSequencers;
std::list<Submix> m_activeSubmixes;
std::list<std::weak_ptr<Studio>> m_activeStudios; /* lifetime dependent on contributing audio entities */
bool m_defaultStudioReady = false;
std::shared_ptr<Studio> m_defaultStudio;
std::unordered_map<uint16_t, std::tuple<AudioGroup*, int, const SFXGroupIndex::SFXEntry*>> m_sfxLookup;
std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random;
int m_nextVid = 0;
@@ -50,25 +54,24 @@ class Engine
std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(int groupId) const;
std::pair<AudioGroup*, const SFXGroupIndex*> _findSFXGroup(int groupId) const;
std::list<std::shared_ptr<Voice>>::iterator
_allocateVoice(const AudioGroup& group, int groupId, double sampleRate,
bool dynamicPitch, bool emitter, Submix* smx);
std::list<std::shared_ptr<Sequencer>>::iterator
_allocateSequencer(const AudioGroup& group, int groupId,
int setupId, Submix* smx);
std::list<Submix>::iterator _allocateSubmix(Submix* smx);
std::list<std::shared_ptr<Voice>>::iterator _allocateVoice(const AudioGroup& group, int groupId, double sampleRate,
bool dynamicPitch, bool emitter,
std::weak_ptr<Studio> studio);
std::list<std::shared_ptr<Sequencer>>::iterator _allocateSequencer(const AudioGroup& group, int groupId,
int setupId, std::weak_ptr<Studio> studio);
std::shared_ptr<Studio> _allocateStudio(bool mainOut);
std::list<std::shared_ptr<Voice>>::iterator _destroyVoice(std::list<std::shared_ptr<Voice>>::iterator it);
std::list<std::shared_ptr<Sequencer>>::iterator _destroySequencer(std::list<std::shared_ptr<Sequencer>>::iterator it);
std::list<Submix>::iterator _destroySubmix(std::list<Submix>::iterator it);
std::list<Submix>::iterator _removeSubmix(std::list<Submix>::iterator it);
std::list<std::shared_ptr<Sequencer>>::iterator
_destroySequencer(std::list<std::shared_ptr<Sequencer>>::iterator it);
void _bringOutYourDead();
void _5MsCallback(double dt);
public:
~Engine();
Engine(IBackendVoiceAllocator& backend, AmplitudeMode ampMode=AmplitudeMode::PerSample);
Engine(IBackendVoiceAllocator& backend, AmplitudeMode ampMode = AmplitudeMode::PerSample);
/** Access voice backend of engine */
IBackendVoiceAllocator& getBackend() {return m_backend;}
IBackendVoiceAllocator& getBackend() { return m_backend; }
/** Update all active audio entities and fill OS audio buffers as needed */
void pumpEngine();
@@ -79,23 +82,33 @@ public:
/** Remove audio group from engine */
void removeAudioGroup(const AudioGroupData& data);
/** Create new Submix (a.k.a 'Studio') within root mix engine */
Submix* addSubmix(Submix* parent=nullptr);
/** Access engine's default studio */
std::shared_ptr<Studio> getDefaultStudio() { return m_defaultStudio; }
/** Remove Submix and deallocate */
void removeSubmix(Submix* smx);
/** Create new Studio within engine */
std::shared_ptr<Studio> addStudio(bool mainOut);
/** Start soundFX playing from loaded audio groups */
std::shared_ptr<Voice> fxStart(int sfxId, float vol, float pan, Submix* smx=nullptr);
std::shared_ptr<Voice> fxStart(int sfxId, float vol, float pan, std::weak_ptr<Studio> smx);
std::shared_ptr<Voice> fxStart(int sfxId, float vol, float pan)
{
return fxStart(sfxId, vol, pan, m_defaultStudio);
}
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
std::shared_ptr<Emitter> addEmitter(const Vector3f& pos, const Vector3f& dir, float maxDist,
float falloff, int sfxId, float minVol, float maxVol,
Submix* smx=nullptr);
std::shared_ptr<Emitter> addEmitter(const Vector3f& pos, const Vector3f& dir, float maxDist, float falloff,
int sfxId, float minVol, float maxVol, std::weak_ptr<Studio> smx);
/** Start song playing from loaded audio groups */
std::shared_ptr<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData,
Submix* smx=nullptr);
std::weak_ptr<Studio> smx);
std::shared_ptr<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData)
{
return seqPlay(groupId, songId, arrData, m_defaultStudio);
}
/** Set total volume of engine */
void setVolume(float vol);
/** Find voice from VoiceId */
std::shared_ptr<Voice> findVoice(int vid);
@@ -107,12 +120,11 @@ public:
void sendMacroMessage(ObjectId macroId, int32_t val);
/** Obtain next random number from engine's PRNG */
uint32_t nextRandom() {return m_random();}
uint32_t nextRandom() { return m_random(); }
/** Obtain list of active sequencers */
std::list<std::shared_ptr<Sequencer>>& getActiveSequencers() {return m_activeSequencers;}
std::list<std::shared_ptr<Sequencer>>& getActiveSequencers() { return m_activeSequencers; }
};
}
#endif // __AMUSE_ENGINE_HPP__

View File

@@ -21,6 +21,7 @@ class Entity
* but shared_ptrs are issued to the client so it can safely track state */
friend class Engine;
friend class SoundMacroState;
protected:
bool m_destroyed = false;
void _destroy()
@@ -35,8 +36,10 @@ protected:
int m_groupId;
ObjectId m_objectId = 0xffff; /* if applicable */
public:
Entity(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid=ObjectId())
: m_engine(engine), m_audioGroup(group), m_groupId(groupId), m_objectId(oid) {}
Entity(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid = ObjectId())
: m_engine(engine), m_audioGroup(group), m_groupId(groupId), m_objectId(oid)
{
}
~Entity()
{
#ifndef NDEBUG
@@ -45,16 +48,15 @@ public:
#endif
}
Engine& getEngine() {return m_engine;}
const AudioGroup& getAudioGroup() const {return m_audioGroup;}
int getGroupId() const {return m_groupId;}
ObjectId getObjectId() const {return m_objectId;}
Engine& getEngine() { return m_engine; }
const AudioGroup& getAudioGroup() const { return m_audioGroup; }
int getGroupId() const { return m_groupId; }
ObjectId getObjectId() const { return m_objectId; }
};
/** Curves for mapping velocity to volume and other functional mappings
* (defined here for visibility)*/
using Curve = uint8_t[128];
}
#endif // __AMUSE_ENTITY_HPP__

View File

@@ -5,6 +5,7 @@
namespace amuse
{
class Voice;
/** Per-sample state tracker for ADSR envelope data */
class Envelope
@@ -18,22 +19,27 @@ public:
Release,
Complete
};
private:
State m_phase = State::Attack; /**< Current envelope state */
double m_attackTime = 0.02; /**< Time of attack in seconds */
double m_decayTime = 0.0; /**< Time of decay in seconds */
double m_sustainFactor = 1.0; /**< Evaluated sustain percentage */
double m_releaseTime = 0.02; /**< Time of release in seconds */
State m_phase = State::Attack; /**< Current envelope state */
double m_attackTime = 0.01; /**< Time of attack in seconds */
double m_decayTime = 0.0; /**< Time of decay in seconds */
double m_sustainFactor = 1.0; /**< Evaluated sustain percentage */
double m_releaseTime = 0.01; /**< Time of release in seconds */
double m_releaseStartFactor = 0.0; /**< Level at whenever release event occurs */
double m_curTime = 0.0; /**< Current time of envelope stage in seconds */
double m_curTime = 0.0; /**< Current time of envelope stage in seconds */
bool m_adsrSet = false;
public:
void reset(const ADSR* adsr);
void reset(const ADSRDLS* adsr, int8_t note, int8_t vel);
void keyOff(const Voice& vox);
void keyOff();
float advance(double dt, const Voice& vox);
float advance(double dt);
bool isComplete() const {return m_phase == State::Complete;}
bool isComplete() const { return m_phase == State::Complete; }
bool isAdsrSet() const { return m_adsrSet; }
};
}
#endif // __AMUSE_ENVELOPE_HPP__

View File

@@ -21,19 +21,15 @@ class IBackendSubmix
public:
virtual ~IBackendSubmix() = default;
/** Set channel-gains for submix (AudioChannel enum for array index) */
virtual void setChannelGains(const float gains[8])=0;
/** Amuse obtains a new voice from the platform outputting to this submix */
virtual std::unique_ptr<IBackendVoice> allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch)=0;
/** Set send level for submix (AudioChannel enum for array index) */
virtual void setSendLevel(IBackendSubmix* submix, float level, bool slew) = 0;
/** Amuse gets fixed sample rate of submix this way */
virtual double getSampleRate() const=0;
virtual double getSampleRate() const = 0;
/** Amuse gets fixed sample format of submix this way */
virtual SubmixFormat getSampleFormat() const=0;
virtual SubmixFormat getSampleFormat() const = 0;
};
}
#endif // __AMUSE_IBACKENDSUBMIX_HPP__

View File

@@ -3,6 +3,7 @@
namespace amuse
{
class IBackendSubmix;
/** Same channel enums from boo, used for matrix coefficient table index */
enum class AudioChannel
@@ -32,24 +33,23 @@ public:
virtual ~IBackendVoice() = default;
/** Set new sample rate into platform voice (may result in artifacts while playing) */
virtual void resetSampleRate(double sampleRate)=0;
virtual void resetSampleRate(double sampleRate) = 0;
/** Reset channel-gains to silence and unbind all submixes */
virtual void resetChannelLevels() = 0;
/** Set channel-gains for audio source (AudioChannel enum for array index) */
virtual void setMatrixCoefficients(const float coefs[8], bool slew)=0;
/** Set submix-channel-gains for audio source (AudioChannel enum for array index) */
virtual void setSubmixMatrixCoefficients(const float coefs[8], bool slew)=0;
virtual void setChannelLevels(IBackendSubmix* submix, const float coefs[8], bool slew) = 0;
/** Called by client to dynamically adjust the pitch of voices with dynamic pitch enabled */
virtual void setPitchRatio(double ratio, bool slew)=0;
virtual void setPitchRatio(double ratio, bool slew) = 0;
/** Instructs platform to begin consuming sample data; invoking callback as needed */
virtual void start()=0;
virtual void start() = 0;
/** Instructs platform to stop consuming sample data */
virtual void stop()=0;
virtual void stop() = 0;
};
}
#endif // __AMUSE_IBACKENDVOICE_HPP__

View File

@@ -27,9 +27,9 @@ enum class AudioChannelSet
class IMIDIReader
{
public:
virtual ~IMIDIReader()=default;
virtual std::string description()=0;
virtual void pumpReader(double dt)=0;
virtual ~IMIDIReader() = default;
virtual std::string description() = 0;
virtual void pumpReader(double dt) = 0;
};
/** Client-implemented voice allocator */
@@ -39,29 +39,29 @@ public:
virtual ~IBackendVoiceAllocator() = default;
/** Amuse obtains a new voice from the platform this way */
virtual std::unique_ptr<IBackendVoice> allocateVoice(Voice& clientVox,
double sampleRate,
bool dynamicPitch)=0;
virtual std::unique_ptr<IBackendVoice> allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch) = 0;
/** Amuse obtains a new submix from the platform this way */
virtual std::unique_ptr<IBackendSubmix> allocateSubmix(Submix& clientSmx)=0;
virtual std::unique_ptr<IBackendSubmix> allocateSubmix(Submix& clientSmx, bool mainOut, int busId) = 0;
/** Amuse obtains a list of all MIDI devices this way */
virtual std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices()=0;
virtual std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() = 0;
/** Amuse obtains an interactive MIDI-in connection from the OS this way */
virtual std::unique_ptr<IMIDIReader> allocateMIDIReader(Engine& engine, const char* name=nullptr)=0;
virtual std::unique_ptr<IMIDIReader> allocateMIDIReader(Engine& engine, const char* name = nullptr) = 0;
/** Amuse obtains speaker-configuration from the platform this way */
virtual AudioChannelSet getAvailableSet()=0;
virtual AudioChannelSet getAvailableSet() = 0;
/** Amuse flushes voice samples to the backend this way */
virtual void pumpAndMixVoices()=0;
virtual void pumpAndMixVoices() = 0;
/** Set volume of main mix out */
virtual void setVolume(float vol) = 0;
/** Amuse may request callbacks 200-updates-per-second virtually */
virtual void register5MsCallback(std::function<void(double dt)>&& callback)=0;
virtual void register5MsCallback(std::function<void(double dt)>&& callback) = 0;
};
}
#endif // __AMUSE_IBACKENDVOICEALLOCATOR_HPP__

View File

@@ -9,7 +9,6 @@ namespace amuse
class Listener : public Entity
{
};
}
#endif // __AMUSE_LISTENER_HPP__

View File

@@ -12,28 +12,30 @@
namespace amuse
{
class Submix;
class Studio;
class Voice;
/** State of sequencer over lifetime */
enum class SequencerState
{
Playing, /**< Sequencer actively playing arrangement */
Playing, /**< Sequencer actively playing arrangement */
Interactive, /**< Interactive sequencer for live MIDI message processing, will not automatically die */
Dead /**< Set when arrangement complete and `dieOnEnd` was set, or manually with die() */
Dead /**< Set when arrangement complete and `dieOnEnd` was set, or manually with die() */
};
/** Multi-voice lifetime manager and polyphonic parameter tracking */
class Sequencer : public Entity
{
friend class Engine;
const SongGroupIndex& m_songGroup; /**< Quick access to song group project index */
const SongGroupIndex::MIDISetup* m_midiSetup = nullptr; /**< Selected MIDI setup */
Submix* m_submix = nullptr; /**< Submix this sequencer outputs to (or NULL for the main output mix) */
const SongGroupIndex* m_songGroup = nullptr; /**< Quick access to song group project index */
const SongGroupIndex::MIDISetup* m_midiSetup = nullptr; /**< Selected MIDI setup (may be null) */
const SFXGroupIndex* m_sfxGroup = nullptr; /**< SFX Groups are alternatively referenced here */
std::vector<const SFXGroupIndex::SFXEntry*> m_sfxMappings; /**< SFX entries are mapped to MIDI keys this via this */
std::shared_ptr<Studio> m_studio; /**< Studio this sequencer outputs to */
const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */
SongState m_songState; /**< State of current arrangement playback */
double m_ticksPerSec = 1000.0; /**< Current ticks per second (tempo) for arrangement data */
const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */
SongState m_songState; /**< State of current arrangement playback */
double m_ticksPerSec = 1000.0; /**< Current ticks per second (tempo) for arrangement data */
SequencerState m_state = SequencerState::Interactive; /**< Current high-level state of sequencer */
bool m_dieOnEnd = false; /**< Sequencer will be killed when current arrangement completes */
@@ -44,7 +46,7 @@ class Sequencer : public Entity
{
Sequencer& m_parent;
uint8_t m_chanId;
const SongGroupIndex::MIDISetup& m_setup;
const SongGroupIndex::MIDISetup* m_setup = nullptr; /* Channel defaults to program 0 if null */
const SongGroupIndex::PageEntry* m_page = nullptr;
~ChannelState();
ChannelState(Sequencer& parent, uint8_t chanId);
@@ -52,11 +54,12 @@ class Sequencer : public Entity
/** Voices corresponding to currently-pressed keys in channel */
std::unordered_map<uint8_t, std::shared_ptr<Voice>> m_chanVoxs;
std::unordered_set<std::shared_ptr<Voice>> m_keyoffVoxs;
std::weak_ptr<Voice> m_lastVoice;
int8_t m_ctrlVals[128] = {}; /**< MIDI controller values */
float m_curPitchWheel = 0.f; /**< MIDI pitch-wheel */
int8_t m_curProgram = 0; /**< MIDI program number */
float m_curVol = 1.f; /**< Current volume of channel */
float m_curPan = 0.f; /**< Current panning of channel */
int8_t m_curProgram = 0; /**< MIDI program number */
float m_curVol = 1.f; /**< Current volume of channel */
float m_curPan = 0.f; /**< Current panning of channel */
void _bringOutYourDead();
size_t getVoiceCount() const;
@@ -78,19 +81,22 @@ class Sequencer : public Entity
void _bringOutYourDead();
void _destroy();
public:
~Sequencer();
Sequencer(Engine& engine, const AudioGroup& group, int groupId,
const SongGroupIndex& songGroup, int setupId, Submix* smx);
Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SongGroupIndex* songGroup, int setupId,
std::weak_ptr<Studio> studio);
Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SFXGroupIndex* sfxGroup,
std::weak_ptr<Studio> studio);
/** Advance current song data (if any) */
void advance(double dt);
/** Obtain pointer to Sequencer's Submix */
Submix* getSubmix() {return m_submix;}
std::shared_ptr<Studio> getStudio() { return m_studio; }
/** Get current state of sequencer */
SequencerState state() const {return m_state;}
SequencerState state() const { return m_state; }
/** Get number of active voices */
size_t getVoiceCount() const;
@@ -108,10 +114,10 @@ public:
void setPitchWheel(uint8_t chan, float pitchWheel);
/** Send keyoffs to all active notes, silence immediately if `now` set */
void allOff(bool now=false);
void allOff(bool now = false);
/** Send keyoffs to all active notes on specified channel, silence immediately if `now` set */
void allOff(uint8_t chan, bool now=false);
void allOff(uint8_t chan, bool now = false);
/** Stop all voices in `kg`, stops immediately (no KeyOff) when `now` set */
void killKeygroup(uint8_t kg, bool now);
@@ -126,10 +132,10 @@ public:
void setTempo(double ticksPerSec);
/** Play MIDI arrangement */
void playSong(const unsigned char* arrData, bool dieOnEnd=true);
void playSong(const unsigned char* arrData, bool dieOnEnd = true);
/** Stop current MIDI arrangement */
void stopSong(bool now=false);
void stopSong(bool now = false);
/** Set total volume of sequencer */
void setVolume(float vol);
@@ -147,9 +153,8 @@ public:
void prevChanProgram(int8_t chanId);
/** Manually kill sequencer for deferred release from engine */
void kill() {m_state = SequencerState::Dead;}
void kill() { m_state = SequencerState::Dead; }
};
}
#endif // __AMUSE_SEQUENCER_HPP__

View File

@@ -0,0 +1,18 @@
#ifndef __AMUSE_SONGCONVERTER_HPP__
#define __AMUSE_SONGCONVERTER_HPP__
#include <vector>
#include <stdint.h>
namespace amuse
{
class SongConverter
{
public:
static std::vector<uint8_t> SongToMIDI(const unsigned char* data, int& versionOut, bool& isBig);
static std::vector<uint8_t> MIDIToSong(const std::vector<uint8_t>& data, int version, bool big);
};
}
#endif // __AMUSE_SONGCONVERTER_HPP__

View File

@@ -22,46 +22,46 @@ enum class SongPlayState
class SongState
{
friend class Voice;
friend class SongConverter;
/** Song header */
struct Header
{
uint32_t m_version;
uint32_t m_chanIdxOff;
uint32_t m_trackIdxOff;
uint32_t m_regionIdxOff;
uint32_t m_chanMapOff;
uint32_t m_tempoTableOff;
uint32_t m_initialTempo;
uint32_t m_unkOff;
uint32_t m_chanOffs[64];
void swapBig();
} m_header;
/** Channel header */
struct ChanHeader
/** Track region ('clip' in an NLA representation) */
struct TrackRegion
{
uint32_t m_startTick;
uint16_t m_unk1;
uint8_t m_progNum;
uint8_t m_unk1;
uint16_t m_unk2;
uint16_t m_dataIndex;
uint16_t m_unk3;
uint32_t m_startTick2;
uint16_t m_unk4;
uint16_t m_unk5;
uint16_t m_unk6;
uint16_t m_unk7;
void swapBig();
int16_t m_regionIndex;
int16_t m_unk3;
bool indexValid(bool bigEndian) const;
};
/** Tempo change entry */
struct TempoChange
{
uint32_t m_tick; /**< Relative song ticks from previous tempo change */
uint32_t m_tick; /**< Relative song ticks from previous tempo change */
uint32_t m_tempo; /**< Tempo value in beats-per-minute (at 384 ticks per quarter-note) */
void swapBig();
};
/** State of a single channel within arrangement */
struct Channel
const unsigned char* m_songData = nullptr; /**< Base pointer to active song */
int m_sngVersion; /**< Detected song revision, 1 has RLE-compressed delta-times */
bool m_bigEndian; /**< True if loaded song is big-endian data */
/** State of a single track within arrangement */
struct Track
{
struct Header
{
@@ -72,45 +72,56 @@ class SongState
};
SongState& m_parent;
uint8_t m_midiChan; /**< MIDI channel number of song channel */
uint32_t m_startTick; /**< Tick to start execution of channel commands */
uint8_t m_midiChan; /**< MIDI channel number of song channel */
const TrackRegion* m_curRegion; /**< Pointer to currently-playing track region */
const TrackRegion* m_nextRegion; /**< Pointer to next-queued track region */
const unsigned char* m_dataBase; /**< Base pointer to command data */
const unsigned char* m_data; /**< Pointer to upcoming command data */
const unsigned char* m_data = nullptr; /**< Pointer to upcoming command data */
const unsigned char* m_pitchWheelData = nullptr; /**< Pointer to upcoming pitch data */
const unsigned char* m_modWheelData = nullptr; /**< Pointer to upcoming modulation data */
uint32_t m_lastPitchTick = 0; /**< Last position of pitch wheel change */
int32_t m_lastPitchVal = 0; /**< Last value of pitch */
uint32_t m_lastModTick = 0; /**< Last position of mod wheel change */
int32_t m_lastModVal = 0; /**< Last value of mod */
std::array<uint16_t, 128> m_remNoteLengths = {}; /**< Remaining ticks per note */
const unsigned char* m_modWheelData = nullptr; /**< Pointer to upcoming modulation data */
uint32_t m_lastPitchTick = 0; /**< Last position of pitch wheel change */
int32_t m_lastPitchVal = 0; /**< Last value of pitch */
uint32_t m_lastModTick = 0; /**< Last position of mod wheel change */
int32_t m_lastModVal = 0; /**< Last value of mod */
std::array<int, 128> m_remNoteLengths; /**< Remaining ticks per note */
int32_t m_waitCountdown = 0; /**< Current wait in ticks */
int32_t m_eventWaitCountdown = 0; /**< Current wait in ticks */
int32_t m_lastN64EventTick =
0; /**< Last command time on this channel (for computing delta times from absolute times in N64 songs) */
Channel(SongState& parent, uint8_t midiChan, uint32_t startTick,
const unsigned char* song, const unsigned char* chan);
Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions);
void setRegion(Sequencer* seq, const TrackRegion* region);
void advanceRegion(Sequencer* seq);
bool advance(Sequencer& seq, int32_t ticks);
};
std::array<std::experimental::optional<Channel>, 64> m_channels;
std::array<std::experimental::optional<Track>, 64> m_tracks;
const uint32_t* m_regionIdx; /**< Table of offsets to song-region data */
/** Current pointer to tempo control, iterated over playback */
const TempoChange* m_tempoPtr = nullptr;
uint32_t m_tempo = 120; /**< Current tempo (beats per minute) */
uint32_t m_curTick = 0; /**< Current playback position for all channels */
uint32_t m_curTick = 0; /**< Current playback position for all channels */
SongPlayState m_songState = SongPlayState::Playing; /**< High-level state of Song playback */
double m_curDt = 0.f; /**< Cumulative dt value for time-remainder tracking */
double m_curDt = 0.f; /**< Cumulative dt value for time-remainder tracking */
public:
/** Determine SNG version
* @param isBig returns true if big-endian SNG
* @return 0 for initial version, 1 for delta-time revision, -1 for non-SNG */
static int DetectVersion(const unsigned char* ptr, bool& isBig);
/** initialize state for Song data at `ptr` */
void initialize(const unsigned char* ptr);
bool initialize(const unsigned char* ptr);
/** advances `dt` seconds worth of commands in the Song
* @return `true` if END reached
*/
bool advance(Sequencer& seq, double dt);
};
/** Get current song tempo in BPM */
uint32_t getTempo() const { return m_tempo; }
};
}
#endif // __AMUSE_SONGSTATE_HPP__

View File

@@ -14,6 +14,7 @@ class Voice;
class SoundMacroState
{
friend class Voice;
friend class Envelope;
/** SoundMacro header */
struct Header
@@ -71,33 +72,33 @@ class SoundMacroState
SendMessage,
GetMessage,
GetVid,
AddAgeCount = 0x30,
SetAgeCount,
SendFlag,
AddAgeCount = 0x30, /* unimplemented */
SetAgeCount, /* unimplemented */
SendFlag, /* unimplemented */
PitchWheelR,
SetPriority,
AddPriority,
AgeCntSpeed,
AgeCntVel,
SetPriority, /* unimplemented */
AddPriority, /* unimplemented */
AgeCntSpeed, /* unimplemented */
AgeCntVel, /* unimplemented */
VolSelect = 0x40,
PanSelect,
PitchWheelSelect,
ModWheelSelect,
PedalSelect,
PortamentoSelect,
ReverbSelect,
ReverbSelect, /* serves as PostASelect */
SpanSelect,
DopplerSelect,
TremoloSelect,
PreASelect,
PreBSelect,
PostBSelect,
AuxAFXSelect,
AuxBFXSelect,
AuxAFXSelect, /* unimplemented */
AuxBFXSelect, /* unimplemented */
SetupLFO = 0x50,
ModeSelect = 0x58,
SetKeygroup,
SRCmodeSelect,
SRCmodeSelect, /* unimplemented */
AddVars = 0x60,
SubVars,
MulVars,
@@ -119,35 +120,35 @@ class SoundMacroState
std::vector<std::pair<const unsigned char*, int>> m_pc;
double m_ticksPerSec; /**< ratio for resolving ticks in commands that use them */
uint8_t m_initVel; /**< Velocity played for this macro invocation */
uint8_t m_initMod; /**< Modulation played for this macro invocation */
uint8_t m_initKey; /**< Key played for this macro invocation */
uint8_t m_curVel; /**< Current velocity played for this macro invocation */
uint8_t m_curMod; /**< Current modulation played for this macro invocation */
uint32_t m_curPitch; /**< Current key played for this macro invocation (in cents) */
uint8_t m_initVel; /**< Velocity played for this macro invocation */
uint8_t m_initMod; /**< Modulation played for this macro invocation */
uint8_t m_initKey; /**< Key played for this macro invocation */
uint8_t m_curVel; /**< Current velocity played for this macro invocation */
uint8_t m_curMod; /**< Current modulation played for this macro invocation */
uint32_t m_curPitch; /**< Current key played for this macro invocation (in cents) */
double m_execTime; /**< time in seconds of SoundMacro execution (per-update resolution) */
bool m_keyoff; /**< keyoff message has been received */
bool m_sampleEnd; /**< sample has finished playback */
bool m_keyoff; /**< keyoff message has been received */
bool m_sampleEnd; /**< sample has finished playback */
bool m_inWait = false; /**< set when timer/keyoff/sampleend wait active */
bool m_inWait = false; /**< set when timer/keyoff/sampleend wait active */
bool m_indefiniteWait = false; /**< set when timer wait is indefinite (keyoff/sampleend only) */
bool m_keyoffWait = false; /**< set when active wait is a keyoff wait */
bool m_sampleEndWait = false; /**< set when active wait is a sampleend wait */
double m_waitCountdown; /**< countdown timer for active wait */
bool m_keyoffWait = false; /**< set when active wait is a keyoff wait */
bool m_sampleEndWait = false; /**< set when active wait is a sampleend wait */
double m_waitCountdown; /**< countdown timer for active wait */
int m_loopCountdown = -1; /**< countdown for current loop */
int m_loopCountdown = -1; /**< countdown for current loop */
int m_lastPlayMacroVid = -1; /**< VoiceId from last PlayMacro command */
bool m_useAdsrControllers; /**< when set, use the following controllers for envelope times */
uint8_t m_midiAttack; /**< Attack MIDI controller */
uint8_t m_midiDecay; /**< Decay MIDI controller */
uint8_t m_midiSustain; /**< Sustain MIDI controller */
uint8_t m_midiRelease; /**< Release MIDI controller */
uint8_t m_midiAttack; /**< Attack MIDI controller */
uint8_t m_midiDecay; /**< Decay MIDI controller */
uint8_t m_midiSustain; /**< Sustain MIDI controller */
uint8_t m_midiRelease; /**< Release MIDI controller */
uint8_t m_portamentoMode; /**< (0: Off, 1: On, 2: MIDI specified) */
uint8_t m_portamentoType; /**< (0: New key pressed while old key pressed, 1: Always) */
float m_portamentoTime; /**< portamento transition time, 0.f will perform legato */
uint8_t m_portamentoMode = 2; /**< (0: Off, 1: On, 2: MIDI specified) */
uint8_t m_portamentoType = 0; /**< (0: New key pressed while old key pressed, 1: Always) */
float m_portamentoTime = 0.5f; /**< portamento transition time, 0.f will perform legato */
/** Used to build a multi-component formula for overriding controllers */
struct Evaluator
@@ -173,19 +174,20 @@ class SoundMacroState
VarType m_varType;
Component(uint8_t midiCtrl, float scale, Combine combine, VarType varType)
: m_midiCtrl(midiCtrl), m_scale(scale), m_combine(combine), m_varType(varType) {}
: m_midiCtrl(midiCtrl), m_scale(scale), m_combine(combine), m_varType(varType)
{
}
};
std::vector<Component> m_comps; /**< Components built up by the macro */
/** Combine additional component(s) to formula */
void addComponent(uint8_t midiCtrl, float scale,
Combine combine, VarType varType);
void addComponent(uint8_t midiCtrl, float scale, Combine combine, VarType varType);
/** Calculate value */
float evaluate(const Voice& vox, const SoundMacroState& st) const;
float evaluate(double time, const Voice& vox, const SoundMacroState& st) const;
/** Determine if able to use */
operator bool() const {return m_comps.size() != 0;}
operator bool() const { return m_comps.size() != 0; }
};
Evaluator m_volumeSel;
@@ -216,8 +218,8 @@ class SoundMacroState
public:
/** initialize state for SoundMacro data at `ptr` */
void initialize(const unsigned char* ptr, int step, bool swapData);
void initialize(const unsigned char* ptr, int step, double ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool swapData);
void initialize(const unsigned char* ptr, int step, double ticksPerSec, uint8_t midiKey, uint8_t midiVel,
uint8_t midiMod, bool swapData);
/** advances `dt` seconds worth of commands in the SoundMacro
* @return `true` if END reached
@@ -230,7 +232,6 @@ public:
/** sample end event */
void sampleEndNotify(Voice& vox);
};
}
#endif // __AMUSE_SOUNDMACROSTATE_HPP__

54
include/amuse/Studio.hpp Normal file
View File

@@ -0,0 +1,54 @@
#ifndef __AMUSE_STUDIO_HPP__
#define __AMUSE_STUDIO_HPP__
#include <memory>
#include <list>
#include "Entity.hpp"
#include "Voice.hpp"
#include "Submix.hpp"
#include <type_traits>
namespace amuse
{
class Studio
{
friend class Engine;
Engine& m_engine;
Submix m_master;
Submix m_auxA;
Submix m_auxB;
struct StudioSend
{
std::shared_ptr<Studio> m_targetStudio;
float m_dryLevel;
float m_auxALevel;
float m_auxBLevel;
StudioSend(std::weak_ptr<Studio> studio, float dry, float auxA, float auxB)
: m_targetStudio(studio), m_dryLevel(dry), m_auxALevel(auxA), m_auxBLevel(auxB)
{
}
};
std::list<StudioSend> m_studiosOut;
#ifndef NDEBUG
bool _cyclicCheck(Studio* leaf);
#endif
public:
Studio(Engine& engine, bool mainOut);
/** Register a target Studio to send this Studio's mixing busses */
void addStudioSend(std::weak_ptr<Studio> studio, float dry, float auxA, float auxB);
/** Advise submixes of changing sample rate */
void resetOutputSampleRate(double sampleRate);
Submix& getMaster() { return m_master; }
Submix& getAuxA() { return m_auxA; }
Submix& getAuxB() { return m_auxB; }
Engine& getEngine() { return m_engine; }
};
}
#endif // __AMUSE_STUDIO_HPP__

View File

@@ -23,27 +23,14 @@ class Submix
friend class Voice;
friend class Sequencer;
Engine& m_root;
Submix* m_submix = nullptr; /**< Parent submix of this submix (or NULL if mixing to main output) */
std::unique_ptr<IBackendSubmix> m_backendSubmix; /**< Handle to client-implemented backend submix */
std::unique_ptr<IBackendSubmix> m_backendSubmix; /**< Handle to client-implemented backend submix */
std::vector<std::unique_ptr<EffectBaseTypeless>> m_effectStack; /**< Ordered list of effects to apply to submix */
bool m_destroyed = false;
void _destroy();
public:
Submix(Engine& engine, Submix* smx);
~Submix()
{
#ifndef NDEBUG
/* Ensure proper destruction procedure followed */
assert(m_destroyed);
#endif
}
/** Obtain pointer to Submix's parent Submix */
Submix* getParentSubmix() {return m_submix;}
Submix(Engine& engine);
/** Add new effect to effect stack and assume ownership */
template <class T, class ...Args>
template <class T, class... Args>
T& makeEffect(Args... args)
{
switch (m_backendSubmix->getSampleFormat())
@@ -77,18 +64,17 @@ public:
EffectDelay& makeDelay(uint32_t initDelay, uint32_t initFeedback, uint32_t initOutput);
/** Add new standard-quality reverb effect to effect stack and assume ownership */
EffectReverbStd& makeReverbStd(float coloration, float mix, float time,
float damping, float preDelay);
EffectReverbStd& makeReverbStd(float coloration, float mix, float time, float damping, float preDelay);
/** Add new high-quality reverb effect to effect stack and assume ownership */
EffectReverbHi& makeReverbHi(float coloration, float mix, float time,
float damping, float preDelay, float crosstalk);
EffectReverbHi& makeReverbHi(float coloration, float mix, float time, float damping, float preDelay,
float crosstalk);
/** Remove and deallocate all effects from effect stack */
void clearEffects() {m_effectStack.clear();}
void clearEffects() { m_effectStack.clear(); }
/** Returns true when an effect callback is bound */
bool canApplyEffect() const {return m_effectStack.size() != 0;}
bool canApplyEffect() const { return m_effectStack.size() != 0; }
/** in/out transformation entry for audio effect */
void applyEffect(int16_t* audio, size_t frameCount, const ChannelMap& chanMap) const;
@@ -99,9 +85,11 @@ public:
/** in/out transformation entry for audio effect */
void applyEffect(float* audio, size_t frameCount, const ChannelMap& chanMap) const;
Engine& getEngine() {return m_root;}
};
/** advice effects of changing sample rate */
void resetOutputSampleRate(double sampleRate);
Engine& getEngine() { return m_root; }
};
}
#endif // __AMUSE_SUBMIX_HPP__

View File

@@ -12,14 +12,13 @@ struct ReferenceVector;
/** Support class for attenuating channel audio based on speaker 'positions' */
class SurroundProfiles
{
static void SetupRefs(float matOut[8], const ChannelMap& map,
const Vector3f& listenEmit, const ReferenceVector refs[]);
public:
static void SetupMatrix(float matOut[8], const ChannelMap& map, AudioChannelSet set,
const Vector3f& emitPos, const Vector3f& listenPos,
const Vector3f& listenDir, const Vector3f& listenUp);
};
static void SetupRefs(float matOut[8], const ChannelMap& map, const Vector3f& listenEmit,
const ReferenceVector refs[]);
public:
static void SetupMatrix(float matOut[8], const ChannelMap& map, AudioChannelSet set, const Vector3f& emitPos,
const Vector3f& listenPos, const Vector3f& listenDir, const Vector3f& listenUp);
};
}
#endif // __AMUSE_SURROUNDPROFILES_HPP__

View File

@@ -14,7 +14,7 @@
namespace amuse
{
class IBackendVoice;
class Submix;
class Studio;
struct Keymap;
struct LayerMapping;
@@ -22,8 +22,8 @@ struct LayerMapping;
enum class VoiceState
{
Playing, /**< SoundMacro actively executing, not in KeyOff */
KeyOff, /**< KeyOff event issued, macro beginning fade-out */
Dead /**< Default state, causes Engine to remove voice at end of pump cycle */
KeyOff, /**< KeyOff event issued, macro beginning fade-out */
Dead /**< Default state, causes Engine to remove voice at end of pump cycle */
};
/** Individual source of audio */
@@ -32,108 +32,121 @@ class Voice : public Entity
friend class Engine;
friend class Sequencer;
friend class SoundMacroState;
int m_vid; /**< VoiceID of this voice instance */
bool m_emitter; /**< Voice is part of an Emitter */
Submix* m_submix = nullptr; /**< Submix this voice outputs to (or NULL for the main output mix) */
friend class Envelope;
int m_vid; /**< VoiceID of this voice instance */
bool m_emitter; /**< Voice is part of an Emitter */
std::shared_ptr<Studio> m_studio; /**< Studio this voice outputs to */
std::unique_ptr<IBackendVoice> m_backendVoice; /**< Handle to client-implemented backend voice */
SoundMacroState m_state; /**< State container for SoundMacro playback */
SoundMacroState::EventTrap m_keyoffTrap; /**< Trap for keyoff (SoundMacro overrides default envelope behavior) */
SoundMacroState m_state; /**< State container for SoundMacro playback */
SoundMacroState::EventTrap m_keyoffTrap; /**< Trap for keyoff (SoundMacro overrides default envelope behavior) */
SoundMacroState::EventTrap m_sampleEndTrap; /**< Trap for sampleend (SoundMacro overrides voice removal) */
SoundMacroState::EventTrap m_messageTrap; /**< Trap for messages sent from other SoundMacros */
std::list<int32_t> m_messageQueue; /**< Messages pending processing for SoundMacros in this voice */
SoundMacroState::EventTrap m_messageTrap; /**< Trap for messages sent from other SoundMacros */
std::list<int32_t> m_messageQueue; /**< Messages pending processing for SoundMacros in this voice */
std::list<std::shared_ptr<Voice>> m_childVoices; /**< Child voices for PLAYMACRO usage */
uint8_t m_keygroup = 0; /**< Keygroup voice is a member of */
uint8_t m_keygroup = 0; /**< Keygroup voice is a member of */
enum class SampleFormat : uint8_t
{
DSP, /**< GCN DSP-ucode ADPCM (very common for GameCube games) */
DSP_DRUM, /**< GCN DSP-ucode ADPCM (seems to be set into drum samples for expanding their amplitude appropriately) */
DSP_DRUM, /**< GCN DSP-ucode ADPCM (seems to be set into drum samples for expanding their amplitude
appropriately) */
PCM, /**< Big-endian PCM found in MusyX2 demo GM instruments */
N64, /**< 2-stage VADPCM coding with SAMP-embedded codebooks */
PCM_PC /**< Little-endian PCM found in PC Rogue Squadron (actually enum 0 which conflicts with DSP-ADPCM) */
};
const Sample* m_curSample = nullptr; /**< Current sample entry playing */
const Sample* m_curSample = nullptr; /**< Current sample entry playing */
const unsigned char* m_curSampleData = nullptr; /**< Current sample data playing */
SampleFormat m_curFormat; /**< Current sample format playing */
uint32_t m_curSamplePos = 0; /**< Current sample position */
uint32_t m_lastSamplePos = 0; /**< Last sample position (or last loop sample) */
int16_t m_prev1 = 0; /**< DSPADPCM prev sample */
int16_t m_prev2 = 0; /**< DSPADPCM prev-prev sample */
SampleFormat m_curFormat; /**< Current sample format playing */
uint32_t m_curSamplePos = 0; /**< Current sample position */
uint32_t m_lastSamplePos = 0; /**< Last sample position (or last loop sample) */
int16_t m_prev1 = 0; /**< DSPADPCM prev sample */
int16_t m_prev2 = 0; /**< DSPADPCM prev-prev sample */
double m_sampleRate = 32000.0; /**< Current sample rate computed from relative sample key or SETPITCH */
double m_voiceTime = 0.0; /**< Current seconds of voice playback (per-sample resolution) */
uint64_t m_voiceSamples = 0; /**< Count of samples processed over voice's lifetime */
float m_lastLevel = 0.f; /**< Last computed level ([0,1] mapped to [-10,0] clamped decibels) */
float m_nextLevel = 0.f; /**< Next computed level used for lerp-mode amplitude */
double m_voiceTime = 0.0; /**< Current seconds of voice playback (per-sample resolution) */
uint64_t m_voiceSamples = 0; /**< Count of samples processed over voice's lifetime */
float m_lastLevel = 0.f; /**< Last computed level ([0,1] mapped to [-10,0] clamped decibels) */
float m_nextLevel = 0.f; /**< Next computed level used for lerp-mode amplitude */
VoiceState m_voxState = VoiceState::Dead; /**< Current high-level state of voice */
bool m_sustained = false; /**< Sustain pedal pressed for this voice */
bool m_sustainKeyOff = false; /**< Keyoff event occured while sustained */
uint8_t m_curAftertouch = 0; /**< Aftertouch value (key pressure when 'bottoming out') */
bool m_sustained = false; /**< Sustain pedal pressed for this voice */
bool m_sustainKeyOff = false; /**< Keyoff event occured while sustained */
uint8_t m_curAftertouch = 0; /**< Aftertouch value (key pressure when 'bottoming out') */
float m_userVol = 1.f; /**< User volume of voice */
float m_curVol = 1.f; /**< Current volume of voice */
float m_curReverbVol = 0.f; /**< Current reverb volume of voice */
float m_userPan = 0.f; /**< User pan of voice */
float m_curPan = 0.f; /**< Current pan of voice */
float m_userSpan = 0.f; /**< User span of voice */
float m_curSpan = 0.f; /**< Current surround pan of voice */
float m_curPitchWheel = 0.f; /**< Current normalized wheel value for control */
int32_t m_pitchWheelUp; /**< Up range for pitchwheel control in cents */
int32_t m_pitchWheelDown; /**< Down range for pitchwheel control in cents */
int32_t m_pitchWheelVal; /**< Current resolved pitchwheel delta for control */
int32_t m_curPitch; /**< Current base pitch in cents */
bool m_pitchDirty; /**< m_curPitch has been updated and needs sending to voice */
float m_targetUserVol = 1.f; /**< Target user volume of voice (slewed to prevent audible aliasing) */
float m_curUserVol = 1.f; /**< Current user volume of voice */
float m_curVol = 1.f; /**< Current volume of voice */
float m_curReverbVol = 0.f; /**< Current reverb volume of voice */
float m_curAuxBVol = 0.f; /**< Current AuxB volume of voice */
float m_userPan = 0.f; /**< User pan of voice */
float m_curPan = 0.f; /**< Current pan of voice */
float m_userSpan = 0.f; /**< User span of voice */
float m_curSpan = 0.f; /**< Current surround pan of voice */
float m_curPitchWheel = 0.f; /**< Current normalized wheel value for control */
int32_t m_pitchWheelUp = 600; /**< Up range for pitchwheel control in cents */
int32_t m_pitchWheelDown = 600; /**< Down range for pitchwheel control in cents */
int32_t m_pitchWheelVal = 0; /**< Current resolved pitchwheel delta for control */
int32_t m_curPitch; /**< Current base pitch in cents */
bool m_pitchDirty = true; /**< m_curPitch has been updated and needs sending to voice */
bool m_needsSlew = false; /**< next _setTotalPitch will be slewed */
Envelope m_volAdsr; /**< Volume envelope */
double m_envelopeTime; /**< time since last ENVELOPE command, -1 for no active volume-sweep */
double m_envelopeDur; /**< requested duration of last ENVELOPE command */
float m_envelopeStart; /**< initial value for last ENVELOPE command */
float m_envelopeEnd; /**< final value for last ENVELOPE command */
Envelope m_volAdsr; /**< Volume envelope */
double m_envelopeTime = -1.f; /**< time since last ENVELOPE command, -1 for no active volume-sweep */
double m_envelopeDur; /**< requested duration of last ENVELOPE command */
float m_envelopeStart; /**< initial value for last ENVELOPE command */
float m_envelopeEnd; /**< final value for last ENVELOPE command */
const Curve* m_envelopeCurve; /**< curve to use for ENVELOPE command */
bool m_pitchEnv = false; /**< Pitch envelope activated */
Envelope m_pitchAdsr; /**< Pitch envelope for SETPITCHADSR */
Envelope m_pitchAdsr; /**< Pitch envelope for SETPITCHADSR */
int32_t m_pitchEnvRange; /**< Pitch delta for SETPITCHADSR (in cents) */
uint32_t m_pitchSweep1; /**< Current value of PITCHSWEEP1 controller (in cents) */
uint32_t m_pitchSweep2; /**< Current value of PITCHSWEEP2 controller (in cents) */
int16_t m_pitchSweep1Add; /**< Value to add to PITCHSWEEP1 controller each cycle */
int16_t m_pitchSweep2Add; /**< Value to add to PITCHSWEEP2 controller each cycle */
uint8_t m_pitchSweep1Times; /**< Remaining times to advance PITCHSWEEP1 controller */
uint8_t m_pitchSweep2Times; /**< Remaining times to advance PITCHSWEEP2 controller */
uint8_t m_pitchSweep1It; /**< Current iteration of PITCHSWEEP1 controller */
uint8_t m_pitchSweep2It; /**< Current iteration of PITCHSWEEP2 controller */
float m_portamentoTime = -1.f; /**< time since last portamento invocation, -1 for no active portamento-glide */
int32_t m_portamentoTarget; /**< destination pitch for latest portamento invocation */
float m_panningTime; /**< time since last PANNING command, -1 for no active pan-sweep */
float m_panningDur; /**< requested duration of last PANNING command */
uint8_t m_panPos; /**< initial pan value of last PANNING command */
int8_t m_panWidth; /**< delta pan value to target of last PANNING command */
uint32_t m_pitchSweep1 = 0; /**< Current value of PITCHSWEEP1 controller (in cents) */
uint32_t m_pitchSweep2 = 0; /**< Current value of PITCHSWEEP2 controller (in cents) */
int16_t m_pitchSweep1Add = 0; /**< Value to add to PITCHSWEEP1 controller each cycle */
int16_t m_pitchSweep2Add = 0; /**< Value to add to PITCHSWEEP2 controller each cycle */
uint8_t m_pitchSweep1Times = 0; /**< Remaining times to advance PITCHSWEEP1 controller */
uint8_t m_pitchSweep2Times = 0; /**< Remaining times to advance PITCHSWEEP2 controller */
uint8_t m_pitchSweep1It = 0; /**< Current iteration of PITCHSWEEP1 controller */
uint8_t m_pitchSweep2It = 0; /**< Current iteration of PITCHSWEEP2 controller */
float m_spanningTime; /**< time since last SPANNING command, -1 for no active span-sweep */
float m_spanningDur; /**< requested duration of last SPANNING command */
uint8_t m_spanPos; /**< initial pan value of last SPANNING command */
int8_t m_spanWidth; /**< delta pan value to target of last SPANNING command */
float m_panningTime = -1.f; /**< time since last PANNING command, -1 for no active pan-sweep */
float m_panningDur; /**< requested duration of last PANNING command */
uint8_t m_panPos; /**< initial pan value of last PANNING command */
int8_t m_panWidth; /**< delta pan value to target of last PANNING command */
int32_t m_vibratoLevel; /**< scale of vibrato effect (in cents) */
int32_t m_vibratoModLevel; /**< scale of vibrato mod-wheel influence (in cents) */
float m_vibratoPeriod; /**< vibrato wave period-time, 0.f will disable vibrato */
bool m_vibratoModWheel; /**< vibrato scaled with mod-wheel if set */
float m_spanningTime = -1.f; /**< time since last SPANNING command, -1 for no active span-sweep */
float m_spanningDur; /**< requested duration of last SPANNING command */
uint8_t m_spanPos; /**< initial pan value of last SPANNING command */
int8_t m_spanWidth; /**< delta pan value to target of last SPANNING command */
float m_tremoloScale; /**< minimum volume factor produced via LFO */
float m_tremoloModScale; /**< minimum volume factor produced via LFO, scaled via mod wheel */
int32_t m_vibratoLevel = 0; /**< scale of vibrato effect (in cents) */
int32_t m_vibratoModLevel = 0; /**< scale of vibrato mod-wheel influence (in cents) */
float m_vibratoPeriod = 0.f; /**< vibrato wave period-time, 0.f will disable vibrato */
bool m_vibratoModWheel = false; /**< vibrato scaled with mod-wheel if set */
float m_lfoPeriods[2]; /**< time-periods for LFO1 and LFO2 */
float m_tremoloScale = 0.f; /**< minimum volume factor produced via LFO */
float m_tremoloModScale = 0.f; /**< minimum volume factor produced via LFO, scaled via mod wheel */
float m_lfoPeriods[2] = {}; /**< time-periods for LFO1 and LFO2 */
std::unique_ptr<int8_t[]> m_ctrlValsSelf; /**< Self-owned MIDI Controller values */
int8_t* m_extCtrlVals = nullptr; /**< MIDI Controller values (external storage) */
int8_t* m_extCtrlVals = nullptr; /**< MIDI Controller values (external storage) */
void _destroy();
void _reset();
bool _checkSamplePos(bool& looped);
void _doKeyOff();
void _macroKeyOff();
void _macroSampleEnd();
bool _advanceSample(int16_t& samp, int32_t& curPitch);
void _procSamplePre(int16_t& samp);
template <typename T>
T _procSampleMaster(double time, T samp);
template <typename T>
T _procSampleAuxA(double time, T samp);
template <typename T>
T _procSampleAuxB(double time, T samp);
void _setTotalPitch(int32_t cents, bool slew);
bool _isRecursivelyDead();
void _bringOutYourDead();
@@ -144,35 +157,48 @@ class Voice : public Entity
std::list<std::shared_ptr<Voice>>::iterator _allocateVoice(double sampleRate, bool dynamicPitch);
std::list<std::shared_ptr<Voice>>::iterator _destroyVoice(std::list<std::shared_ptr<Voice>>::iterator it);
bool _loadSoundMacro(const unsigned char* macroData, int macroStep, double ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool pushPc=false);
bool _loadKeymap(const Keymap* keymap, int macroStep, double ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool pushPc=false);
bool _loadLayer(const std::vector<const LayerMapping*>& layer, int macroStep, double ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool pushPc=false);
std::shared_ptr<Voice> _startChildMacro(ObjectId macroId, int macroStep, double ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool pushPc=false);
bool _loadSoundMacro(const unsigned char* macroData, int macroStep, double ticksPerSec, uint8_t midiKey,
uint8_t midiVel, uint8_t midiMod, bool pushPc = false);
bool _loadKeymap(const Keymap* keymap, int macroStep, double ticksPerSec, uint8_t midiKey, uint8_t midiVel,
uint8_t midiMod, bool pushPc = false);
bool _loadLayer(const std::vector<const LayerMapping*>& layer, int macroStep, double ticksPerSec, uint8_t midiKey,
uint8_t midiVel, uint8_t midiMod, bool pushPc = false);
std::shared_ptr<Voice> _startChildMacro(ObjectId macroId, int macroStep, double ticksPerSec, uint8_t midiKey,
uint8_t midiVel, uint8_t midiMod, bool pushPc = false);
void _setPan(float pan);
void _setSurroundPan(float span);
void _setPitchWheel(float pitchWheel);
void _notifyCtrlChange(uint8_t ctrl, int8_t val);
public:
~Voice();
Voice(Engine& engine, const AudioGroup& group, int groupId, int vid, bool emitter, Submix* smx);
Voice(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid, int vid, bool emitter, Submix* smx);
Voice(Engine& engine, const AudioGroup& group, int groupId, int vid, bool emitter, std::weak_ptr<Studio> studio);
Voice(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid, int vid, bool emitter,
std::weak_ptr<Studio> studio);
/** Called before each supplyAudio invocation to prepare voice
* backend for possible parameter updates */
void preSupplyAudio(double dt);
/** Request specified count of audio frames (samples) from voice,
* internally advancing the voice stream */
size_t supplyAudio(size_t frames, int16_t* data);
/** Obtain pointer to Voice's Submix */
Submix* getSubmix() {return m_submix;}
/** Called three times after resampling supplyAudio output, voice should
* perform volume processing / send routing for each aux bus and master */
void routeAudio(size_t frames, double dt, int busId, int16_t* in, int16_t* out);
void routeAudio(size_t frames, double dt, int busId, int32_t* in, int32_t* out);
void routeAudio(size_t frames, double dt, int busId, float* in, float* out);
/** Obtain pointer to Voice's Studio */
std::shared_ptr<Studio> getStudio() { return m_studio; }
/** Get current state of voice */
VoiceState state() const {return m_voxState;}
VoiceState state() const { return m_voxState; }
/** Get VoiceId of this voice (unique to all currently-playing voices) */
int vid() const {return m_vid;}
int vid() const { return m_vid; }
/** Get max VoiceId of this voice and any contained children */
int maxVid() const;
@@ -181,9 +207,8 @@ public:
std::shared_ptr<Voice> startChildMacro(int8_t addNote, ObjectId macroId, int macroStep);
/** Load specified Sound Object from within group into voice */
bool loadSoundObject(ObjectId objectId, int macroStep, double ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod,
bool pushPc=false);
bool loadSoundObject(ObjectId objectId, int macroStep, double ticksPerSec, uint8_t midiKey, uint8_t midiVel,
uint8_t midiMod, bool pushPc = false);
/** Signals voice to begin fade-out (or defer if sustained), eventually reaching silence */
void keyOff();
@@ -237,10 +262,10 @@ public:
void setTremolo(float tremoloScale, float tremoloModScale);
/** Setup LFO1 for voice */
void setLFO1Period(float period) {m_lfoPeriods[0] = period;}
void setLFO1Period(float period) { m_lfoPeriods[0] = period; }
/** Setup LFO2 for voice */
void setLFO2Period(float period) {m_lfoPeriods[1] = period;}
void setLFO2Period(float period) { m_lfoPeriods[1] = period; }
/** Setup pitch sweep controller 1 */
void setPitchSweep1(uint8_t times, int16_t add);
@@ -251,6 +276,9 @@ public:
/** Set reverb mix for voice */
void setReverbVol(float rvol);
/** Set AuxB volume for voice */
void setAuxBVol(float bvol);
/** Set envelope for voice */
void setAdsr(ObjectId adsrId, bool dls);
@@ -270,10 +298,13 @@ public:
void setAftertouch(uint8_t aftertouch);
/** Assign voice to keygroup for coordinated mass-silencing */
void setKeygroup(uint8_t kg) {m_keygroup = kg;}
void setKeygroup(uint8_t kg) { m_keygroup = kg; }
/** Get note played on voice */
uint8_t getLastNote() const {return m_state.m_initKey;}
uint8_t getLastNote() const { return m_state.m_initKey; }
/** Do portamento glide; returns `false` if portamento disabled */
bool doPortamento(uint8_t newNote);
/** Get MIDI Controller value on voice */
int8_t getCtrlValue(uint8_t ctrl) const
@@ -300,9 +331,6 @@ public:
_notifyCtrlChange(ctrl, val);
}
/** Get ModWheel value on voice */
int8_t getModWheel() const {return getCtrlValue(1);}
/** 'install' external MIDI controller storage */
void installCtrlValues(int8_t* cvs)
{
@@ -311,19 +339,17 @@ public:
}
/** Get MIDI pitch wheel value on voice */
int8_t getPitchWheel() const {return m_curPitchWheel * 127;}
float getPitchWheel() const { return m_curPitchWheel; }
/** Get MIDI aftertouch value on voice */
int8_t getAftertouch() const {return m_curAftertouch;}
int8_t getAftertouch() const { return m_curAftertouch; }
/** Get count of all voices in hierarchy, including this one */
size_t getTotalVoices() const;
/** Recursively mark voice as dead for Engine to deallocate on next cycle */
void kill();
};
}
#endif // __AMUSE_VOICE_HPP__

View File

@@ -16,6 +16,8 @@
#include "Listener.hpp"
#include "Sequencer.hpp"
#include "SoundMacroState.hpp"
#include "SongConverter.hpp"
#include "SongState.hpp"
#include "Submix.hpp"
#include "Voice.hpp"

View File

@@ -5,28 +5,31 @@ namespace amuse
{
AudioGroup::AudioGroup(const AudioGroupData& data, GCNDataTag)
: m_proj(data.getProj(), GCNDataTag{}),
m_pool(data.getPool()),
m_sdir(data.getSdir(), GCNDataTag{}),
m_samp(data.getSamp()),
m_fmt(DataFormat::GCN)
{}
: m_proj(data.getProj(), GCNDataTag{})
, m_pool(data.getPool())
, m_sdir(data.getSdir(), GCNDataTag{})
, m_samp(data.getSamp())
, m_fmt(DataFormat::GCN)
{
}
AudioGroup::AudioGroup(const AudioGroupData& data, bool absOffs, N64DataTag)
: m_proj(data.getProj(), absOffs, N64DataTag{}),
m_pool(data.getPool()),
m_sdir(data.getSdir(), data.getSamp(), absOffs, N64DataTag{}),
m_samp(data.getSamp()),
m_fmt(DataFormat::N64)
{}
: m_proj(data.getProj(), absOffs, N64DataTag{})
, m_pool(data.getPool())
, m_sdir(data.getSdir(), data.getSamp(), absOffs, N64DataTag{})
, m_samp(data.getSamp())
, m_fmt(DataFormat::N64)
{
}
AudioGroup::AudioGroup(const AudioGroupData& data, bool absOffs, PCDataTag)
: m_proj(data.getProj(), absOffs, PCDataTag{}),
m_pool(data.getPool(), PCDataTag{}),
m_sdir(data.getSdir(), absOffs, PCDataTag{}),
m_samp(data.getSamp()),
m_fmt(DataFormat::PC)
{}
: m_proj(data.getProj(), absOffs, PCDataTag{})
, m_pool(data.getPool(), PCDataTag{})
, m_sdir(data.getSdir(), absOffs, PCDataTag{})
, m_samp(data.getSamp())
, m_fmt(DataFormat::PC)
{
}
const Sample* AudioGroup::getSample(int sfxId) const
{
@@ -36,9 +39,5 @@ const Sample* AudioGroup::getSample(int sfxId) const
return &search->second;
}
const unsigned char* AudioGroup::getSampleData(uint32_t offset) const
{
return m_samp + offset;
}
const unsigned char* AudioGroup::getSampleData(uint32_t offset) const { return m_samp + offset; }
}

View File

@@ -7,15 +7,16 @@ IntrusiveAudioGroupData::~IntrusiveAudioGroupData()
{
if (m_owns)
{
delete m_pool;
delete m_proj;
delete m_sdir;
delete m_samp;
delete[] m_pool;
delete[] m_proj;
delete[] m_sdir;
delete[] m_samp;
}
}
IntrusiveAudioGroupData::IntrusiveAudioGroupData(IntrusiveAudioGroupData&& other)
: AudioGroupData(other.m_proj, other.m_pool, other.m_sdir, other.m_samp, other.m_fmt, other.m_absOffs)
: AudioGroupData(other.m_proj, other.m_projSz, other.m_pool, other.m_poolSz, other.m_sdir, other.m_sdirSz, other.m_samp,
other.m_sampSz, other.m_fmt, other.m_absOffs)
{
m_owns = other.m_owns;
other.m_owns = false;
@@ -25,10 +26,10 @@ IntrusiveAudioGroupData& IntrusiveAudioGroupData::operator=(IntrusiveAudioGroupD
{
if (m_owns)
{
delete m_pool;
delete m_proj;
delete m_sdir;
delete m_samp;
delete[] m_pool;
delete[] m_proj;
delete[] m_sdir;
delete[] m_samp;
}
m_owns = other.m_owns;
@@ -43,5 +44,4 @@ IntrusiveAudioGroupData& IntrusiveAudioGroupData::operator=(IntrusiveAudioGroupD
return *this;
}
}

View File

@@ -70,10 +70,10 @@ AudioGroupPool::AudioGroupPool(const unsigned char* data)
ObjectId id = SBig(*reinterpret_cast<const ObjectId*>(cur + 4));
std::vector<const LayerMapping*>& mappingsOut = m_layers[id];
uint32_t count = SBig(*reinterpret_cast<const uint32_t*>(cur+8));
uint32_t count = SBig(*reinterpret_cast<const uint32_t*>(cur + 8));
mappingsOut.reserve(count);
const unsigned char* subcur = cur + 12;
for (int i=0 ; i<count ; ++i)
for (int i = 0; i < count; ++i)
mappingsOut.push_back(reinterpret_cast<const LayerMapping*>(subcur + i * 12));
cur += size;
@@ -130,10 +130,10 @@ AudioGroupPool::AudioGroupPool(const unsigned char* data, PCDataTag)
ObjectId id = *reinterpret_cast<const ObjectId*>(cur + 4);
std::vector<const LayerMapping*>& mappingsOut = m_layers[id];
uint32_t count = *reinterpret_cast<const uint32_t*>(cur+8);
uint32_t count = *reinterpret_cast<const uint32_t*>(cur + 8);
mappingsOut.reserve(count);
const unsigned char* subcur = cur + 12;
for (int i=0 ; i<count ; ++i)
for (int i = 0; i < count; ++i)
mappingsOut.push_back(reinterpret_cast<const LayerMapping*>(subcur + i * 12));
cur += size;
@@ -172,5 +172,4 @@ const ADSR* AudioGroupPool::tableAsAdsr(ObjectId id) const
return nullptr;
return reinterpret_cast<const ADSR*>(search->second);
}
}

View File

@@ -96,7 +96,7 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data, GCNDataTag)
idx.m_sfxEntries.reserve(count);
const SFXGroupIndex::SFXEntry* entries =
reinterpret_cast<const SFXGroupIndex::SFXEntry*>(data + header.pageTableOff + 4);
for (int i=0 ; i<count ; ++i)
for (int i = 0; i < count; ++i)
{
idx.m_sfxEntries[SBig(entries->defineId)] = entries;
++entries;
@@ -152,7 +152,7 @@ struct MusyX1MIDISetup
}
};
void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool absOffs, N64DataTag)
void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, N64DataTag)
{
size_t normPageCount = 0;
size_t drumPageCount = 0;
@@ -161,7 +161,7 @@ void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool abs
const GroupHeader* group = reinterpret_cast<const GroupHeader*>(data);
while (group->groupEndOff != 0xffffffff)
{
const unsigned char* subData = absOffs ? data : data + 8;
const unsigned char* subData = data + 8;
GroupHeader header = *group;
header.swapBig();
@@ -187,20 +187,16 @@ void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool abs
/* MIDI setups */
const uint8_t* setupData = subData + header.midiSetupsOff;
while (*reinterpret_cast<const uint32_t*>(setupData) != 0xffffffff)
const uint8_t* setupEnd = subData + header.groupEndOff;
while (setupData < setupEnd)
{
++midiSetupCount;
setupData += 8 * 16 + 4;
}
}
if (absOffs)
group = reinterpret_cast<const GroupHeader*>(data + header.groupEndOff);
else
{
data += header.groupEndOff;
group = reinterpret_cast<const GroupHeader*>(data);
}
data += header.groupEndOff;
group = reinterpret_cast<const GroupHeader*>(data);
}
if (normPageCount)
@@ -208,12 +204,13 @@ void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool abs
if (drumPageCount)
m_convDrumPages.reset(new SongGroupIndex::PageEntry[drumPageCount]);
if (midiSetupCount)
m_convMidiSetups.reset(new std::array<SongGroupIndex::MIDISetup, 16>[midiSetupCount]);
m_convMidiSetups.reset(new std::array<SongGroupIndex::MIDISetup, 16>[ midiSetupCount ]);
}
AudioGroupProject::AudioGroupProject(const unsigned char* data, bool absOffs, N64DataTag)
{
_allocateConvBuffers(data, absOffs, N64DataTag{});
if (!absOffs)
_allocateConvBuffers(data, N64DataTag{});
SongGroupIndex::PageEntry* normPagesBuf = m_convNormalPages.get();
SongGroupIndex::PageEntry* drumPagesBuf = m_convDrumPages.get();
std::array<SongGroupIndex::MIDISetup, 16>* midiSetupsBuf = m_convMidiSetups.get();
@@ -232,42 +229,77 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data, bool absOffs, N6
SongGroupIndex& idx = m_songGroups[header.groupId];
bIdx = &idx;
/* Normal pages */
const MusyX1PageEntry* normEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + header.pageTableOff);
while (normEntries->objId != 0xffff)
if (absOffs)
{
normEntries->setIntoMusyX2(*normPagesBuf);
idx.m_normPages[normEntries->programNo] = normPagesBuf;
++normEntries;
++normPagesBuf;
/* Normal pages */
const SongGroupIndex::PageEntry* normEntries =
reinterpret_cast<const SongGroupIndex::PageEntry*>(data + header.pageTableOff);
while (normEntries->objId != 0xffff)
{
idx.m_normPages[normEntries->programNo] = normEntries;
++normEntries;
}
/* Drum pages */
const SongGroupIndex::PageEntry* drumEntries =
reinterpret_cast<const SongGroupIndex::PageEntry*>(data + header.drumTableOff);
while (drumEntries->objId != 0xffff)
{
idx.m_drumPages[drumEntries->programNo] = drumEntries;
++drumEntries;
}
/* MIDI setups */
const uint8_t* setupData = data + header.midiSetupsOff;
const uint8_t* setupEnd = data + header.groupEndOff;
while (setupData < setupEnd)
{
uint16_t songId = SBig(*reinterpret_cast<const uint16_t*>(setupData));
idx.m_midiSetups[songId] =
reinterpret_cast<const std::array<SongGroupIndex::MIDISetup, 16>*>(setupData + 4);
setupData += 5 * 16 + 4;
}
}
/* Drum pages */
const MusyX1PageEntry* drumEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + header.drumTableOff);
while (drumEntries->objId != 0xffff)
else
{
drumEntries->setIntoMusyX2(*drumPagesBuf);
idx.m_drumPages[drumEntries->programNo] = drumPagesBuf;
++drumEntries;
++drumPagesBuf;
}
/* Normal pages */
const MusyX1PageEntry* normEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + header.pageTableOff);
while (normEntries->objId != 0xffff)
{
normEntries->setIntoMusyX2(*normPagesBuf);
idx.m_normPages[normEntries->programNo] = normPagesBuf;
++normEntries;
++normPagesBuf;
}
/* MIDI setups */
const uint8_t* setupData = subData + header.midiSetupsOff;
while (*reinterpret_cast<const uint32_t*>(setupData) != 0xffffffff)
{
uint16_t songId = SBig(*reinterpret_cast<const uint16_t*>(setupData));
const std::array<MusyX1MIDISetup, 16>* midiSetups =
reinterpret_cast<const std::array<MusyX1MIDISetup, 16>*>(setupData + 4);
/* Drum pages */
const MusyX1PageEntry* drumEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + header.drumTableOff);
while (drumEntries->objId != 0xffff)
{
drumEntries->setIntoMusyX2(*drumPagesBuf);
idx.m_drumPages[drumEntries->programNo] = drumPagesBuf;
++drumEntries;
++drumPagesBuf;
}
for (int i=0 ; i<16 ; ++i)
(*midiSetups)[i].setIntoMusyX2((*midiSetupsBuf)[i]);
/* MIDI setups */
const uint8_t* setupData = subData + header.midiSetupsOff;
const uint8_t* setupEnd = subData + header.groupEndOff;
while (setupData < setupEnd)
{
uint16_t songId = SBig(*reinterpret_cast<const uint16_t*>(setupData));
const std::array<MusyX1MIDISetup, 16>* midiSetups =
reinterpret_cast<const std::array<MusyX1MIDISetup, 16>*>(setupData + 4);
idx.m_midiSetups[songId] = midiSetupsBuf;
setupData += 8 * 16 + 4;
++midiSetupsBuf;
for (int i = 0; i < 16; ++i)
(*midiSetups)[i].setIntoMusyX2((*midiSetupsBuf)[i]);
idx.m_midiSetups[songId] = midiSetupsBuf;
setupData += 8 * 16 + 4;
++midiSetupsBuf;
}
}
}
else if (header.type == GroupType::SFX)
@@ -278,7 +310,7 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data, bool absOffs, N6
/* SFX entries */
uint16_t count = SBig(*reinterpret_cast<const uint16_t*>(subData + header.pageTableOff));
idx.m_sfxEntries.reserve(count);
for (int i=0 ; i<count ; ++i)
for (int i = 0; i < count; ++i)
{
const SFXGroupIndex::SFXEntry* entries =
reinterpret_cast<const SFXGroupIndex::SFXEntry*>(subData + header.pageTableOff + 4 + i * 12);
@@ -304,7 +336,7 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data, bool absOffs, N6
}
}
void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool absOffs, PCDataTag)
void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, PCDataTag)
{
size_t normPageCount = 0;
size_t drumPageCount = 0;
@@ -313,13 +345,13 @@ void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool abs
const GroupHeader* group = reinterpret_cast<const GroupHeader*>(data);
while (group->groupEndOff != 0xffffffff)
{
const unsigned char* subData = absOffs ? data : data + 8;
const unsigned char* subData = data + 8;
if (group->type == GroupType::Song)
{
/* Normal pages */
const MusyX1PageEntry* normEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + group->pageTableOff);
reinterpret_cast<const MusyX1PageEntry*>(subData + group->pageTableOff);
while (normEntries->objId != 0xffff)
{
++normPageCount;
@@ -328,7 +360,7 @@ void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool abs
/* Drum pages */
const MusyX1PageEntry* drumEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + group->drumTableOff);
reinterpret_cast<const MusyX1PageEntry*>(subData + group->drumTableOff);
while (drumEntries->objId != 0xffff)
{
++drumPageCount;
@@ -337,20 +369,16 @@ void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool abs
/* MIDI setups */
const uint8_t* setupData = subData + group->midiSetupsOff;
while (*reinterpret_cast<const uint32_t*>(setupData) != 0xffffffff)
const uint8_t* setupEnd = subData + group->groupEndOff;
while (setupData < setupEnd)
{
++midiSetupCount;
setupData += 8 * 16 + 4;
}
}
if (absOffs)
group = reinterpret_cast<const GroupHeader*>(data + group->groupEndOff);
else
{
data += group->groupEndOff;
group = reinterpret_cast<const GroupHeader*>(data);
}
data += group->groupEndOff;
group = reinterpret_cast<const GroupHeader*>(data);
}
if (normPageCount)
@@ -358,12 +386,13 @@ void AudioGroupProject::_allocateConvBuffers(const unsigned char* data, bool abs
if (drumPageCount)
m_convDrumPages.reset(new SongGroupIndex::PageEntry[drumPageCount]);
if (midiSetupCount)
m_convMidiSetups.reset(new std::array<SongGroupIndex::MIDISetup, 16>[midiSetupCount]);
m_convMidiSetups.reset(new std::array<SongGroupIndex::MIDISetup, 16>[ midiSetupCount ]);
}
AudioGroupProject::AudioGroupProject(const unsigned char* data, bool absOffs, PCDataTag)
{
_allocateConvBuffers(data, absOffs, PCDataTag{});
if (!absOffs)
_allocateConvBuffers(data, PCDataTag{});
SongGroupIndex::PageEntry* normPagesBuf = m_convNormalPages.get();
SongGroupIndex::PageEntry* drumPagesBuf = m_convDrumPages.get();
std::array<SongGroupIndex::MIDISetup, 16>* midiSetupsBuf = m_convMidiSetups.get();
@@ -380,42 +409,77 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data, bool absOffs, PC
SongGroupIndex& idx = m_songGroups[group->groupId];
bIdx = &idx;
/* Normal pages */
const MusyX1PageEntry* normEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + group->pageTableOff);
while (normEntries->objId != 0xffff)
if (absOffs)
{
normEntries->setIntoMusyX2(*normPagesBuf);
idx.m_normPages[normEntries->programNo] = normPagesBuf;
++normEntries;
++normPagesBuf;
/* Normal pages */
const SongGroupIndex::PageEntry* normEntries =
reinterpret_cast<const SongGroupIndex::PageEntry*>(data + group->pageTableOff);
while (normEntries->objId != 0xffff)
{
idx.m_normPages[normEntries->programNo] = normEntries;
++normEntries;
}
/* Drum pages */
const SongGroupIndex::PageEntry* drumEntries =
reinterpret_cast<const SongGroupIndex::PageEntry*>(data + group->drumTableOff);
while (drumEntries->objId != 0xffff)
{
idx.m_drumPages[drumEntries->programNo] = drumEntries;
++drumEntries;
}
/* MIDI setups */
const uint8_t* setupData = data + group->midiSetupsOff;
const uint8_t* setupEnd = data + group->groupEndOff;
while (setupData < setupEnd)
{
uint16_t songId = *reinterpret_cast<const uint16_t*>(setupData);
idx.m_midiSetups[songId] =
reinterpret_cast<const std::array<SongGroupIndex::MIDISetup, 16>*>(setupData + 4);
setupData += 5 * 16 + 4;
}
}
/* Drum pages */
const MusyX1PageEntry* drumEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + group->drumTableOff);
while (drumEntries->objId != 0xffff)
else
{
drumEntries->setIntoMusyX2(*drumPagesBuf);
idx.m_drumPages[drumEntries->programNo] = drumPagesBuf;
++drumEntries;
++drumPagesBuf;
}
/* Normal pages */
const MusyX1PageEntry* normEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + group->pageTableOff);
while (normEntries->objId != 0xffff)
{
normEntries->setIntoMusyX2(*normPagesBuf);
idx.m_normPages[normEntries->programNo] = normPagesBuf;
++normEntries;
++normPagesBuf;
}
/* MIDI setups */
const uint8_t* setupData = subData + group->midiSetupsOff;
while (*reinterpret_cast<const uint32_t*>(setupData) != 0xffffffff)
{
uint16_t songId = *reinterpret_cast<const uint16_t*>(setupData);
const std::array<MusyX1MIDISetup, 16>* midiSetups =
reinterpret_cast<const std::array<MusyX1MIDISetup, 16>*>(setupData + 4);
/* Drum pages */
const MusyX1PageEntry* drumEntries =
reinterpret_cast<const MusyX1PageEntry*>(subData + group->drumTableOff);
while (drumEntries->objId != 0xffff)
{
drumEntries->setIntoMusyX2(*drumPagesBuf);
idx.m_drumPages[drumEntries->programNo] = drumPagesBuf;
++drumEntries;
++drumPagesBuf;
}
for (int i=0 ; i<16 ; ++i)
(*midiSetups)[i].setIntoMusyX2((*midiSetupsBuf)[i]);
/* MIDI setups */
const uint8_t* setupData = subData + group->midiSetupsOff;
const uint8_t* setupEnd = subData + group->groupEndOff;
while (setupData < setupEnd)
{
uint16_t songId = *reinterpret_cast<const uint16_t*>(setupData);
const std::array<MusyX1MIDISetup, 16>* midiSetups =
reinterpret_cast<const std::array<MusyX1MIDISetup, 16>*>(setupData + 4);
idx.m_midiSetups[songId] = midiSetupsBuf;
setupData += 8 * 16 + 4;
++midiSetupsBuf;
for (int i = 0; i < 16; ++i)
(*midiSetups)[i].setIntoMusyX2((*midiSetupsBuf)[i]);
idx.m_midiSetups[songId] = midiSetupsBuf;
setupData += 8 * 16 + 4;
++midiSetupsBuf;
}
}
}
else if (group->type == GroupType::SFX)
@@ -426,10 +490,10 @@ AudioGroupProject::AudioGroupProject(const unsigned char* data, bool absOffs, PC
/* SFX entries */
uint16_t count = *reinterpret_cast<const uint16_t*>(subData + group->pageTableOff);
idx.m_sfxEntries.reserve(count);
for (int i=0 ; i<count ; ++i)
for (int i = 0; i < count; ++i)
{
const SFXGroupIndex::SFXEntry* entries =
reinterpret_cast<const SFXGroupIndex::SFXEntry*>(subData + group->pageTableOff + 4 + i * 12);
reinterpret_cast<const SFXGroupIndex::SFXEntry*>(subData + group->pageTableOff + 4 + i * 12);
idx.m_sfxEntries[entries->defineId] = entries;
}
}
@@ -481,5 +545,4 @@ const SFXGroupIndex* AudioGroupProject::getSFXGroupIndex(int groupId) const
return nullptr;
return &search->second;
}
}

View File

@@ -22,7 +22,7 @@ void AudioGroupSampleDirectory::ADPCMParms::swapBigDSP()
dsp.m_bytesPerFrame = SBig(dsp.m_bytesPerFrame);
dsp.m_hist2 = SBig(dsp.m_hist2);
dsp.m_hist1 = SBig(dsp.m_hist1);
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
{
dsp.m_coefs[i][0] = SBig(dsp.m_coefs[i][0]);
dsp.m_coefs[i][1] = SBig(dsp.m_coefs[i][1]);
@@ -32,7 +32,7 @@ void AudioGroupSampleDirectory::ADPCMParms::swapBigDSP()
void AudioGroupSampleDirectory::ADPCMParms::swapBigVADPCM()
{
int16_t* allCoefs = reinterpret_cast<int16_t*>(vadpcm.m_coefs[0][0]);
for (int i=0 ; i<128 ; ++i)
for (int i = 0; i < 128; ++i)
allCoefs[i] = SBig(allCoefs[i]);
}
@@ -41,8 +41,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data,
const unsigned char* cur = data;
while (*reinterpret_cast<const uint32_t*>(cur) != 0xffffffff)
{
const AudioGroupSampleDirectory::Entry* ent =
reinterpret_cast<const AudioGroupSampleDirectory::Entry*>(cur);
const AudioGroupSampleDirectory::Entry* ent = reinterpret_cast<const AudioGroupSampleDirectory::Entry*>(cur);
std::pair<Entry, ADPCMParms>& store = m_entries[SBig(ent->m_sfxId)];
store.first = *ent;
@@ -51,8 +50,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data,
if (store.first.m_adpcmParmOffset)
{
const AudioGroupSampleDirectory::ADPCMParms* adpcm =
reinterpret_cast<const AudioGroupSampleDirectory::ADPCMParms*>(data +
store.first.m_adpcmParmOffset);
reinterpret_cast<const AudioGroupSampleDirectory::ADPCMParms*>(data + store.first.m_adpcmParmOffset);
store.second.dsp = adpcm->dsp;
store.second.swapBigDSP();
}
@@ -129,8 +127,7 @@ struct MusyX1AbsSdirEntry
}
};
AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data,
const unsigned char* sampData,
AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data, const unsigned char* sampData,
bool absOffs, N64DataTag)
{
const unsigned char* cur = data;
@@ -145,7 +142,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data,
std::pair<Entry, ADPCMParms>& store = m_entries[ent.m_sfxId];
ent.setIntoMusyX2(store.first);
memcpy(&store.second.vadpcm.m_coefs, sampData + ent.m_sampleOff, 256);
memmove(&store.second.vadpcm.m_coefs, sampData + ent.m_sampleOff, 256);
store.second.swapBigVADPCM();
cur += 28;
@@ -161,7 +158,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data,
std::pair<Entry, ADPCMParms>& store = m_entries[ent.m_sfxId];
ent.setIntoMusyX2(store.first);
memcpy(&store.second.vadpcm.m_coefs, sampData + ent.m_sampleOff, 256);
memmove(&store.second.vadpcm.m_coefs, sampData + ent.m_sampleOff, 256);
store.second.swapBigVADPCM();
cur += 24;
@@ -169,8 +166,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data,
}
}
AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data,
bool absOffs, PCDataTag)
AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data, bool absOffs, PCDataTag)
{
const unsigned char* cur = data;
@@ -199,5 +195,4 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data,
}
}
}
}

View File

@@ -6,116 +6,98 @@
namespace amuse
{
size_t BooBackendVoice::VoiceCallback::supplyAudio(boo::IAudioVoice&,
size_t frames, int16_t* data)
void BooBackendVoice::VoiceCallback::preSupplyAudio(boo::IAudioVoice&, double dt)
{
m_parent.m_clientVox.preSupplyAudio(dt);
}
size_t BooBackendVoice::VoiceCallback::supplyAudio(boo::IAudioVoice&, size_t frames, int16_t* data)
{
return m_parent.m_clientVox.supplyAudio(frames, data);
}
BooBackendVoice::BooBackendVoice(boo::IAudioVoiceEngine& engine, Voice& clientVox,
double sampleRate, bool dynamicPitch)
: m_clientVox(clientVox), m_cb(*this),
m_booVoice(engine.allocateNewMonoVoice(sampleRate, &m_cb, dynamicPitch))
{}
BooBackendVoice::BooBackendVoice(boo::IAudioSubmix& submix, Voice& clientVox,
double sampleRate, bool dynamicPitch)
: m_clientVox(clientVox), m_cb(*this),
m_booVoice(submix.allocateNewMonoVoice(sampleRate, &m_cb, dynamicPitch))
{}
void BooBackendVoice::resetSampleRate(double sampleRate)
void BooBackendVoice::VoiceCallback::routeAudio(size_t frames, double dt, int busId, int16_t* in, int16_t* out)
{
m_booVoice->resetSampleRate(sampleRate);
m_parent.m_clientVox.routeAudio(frames, dt, busId, in, out);
}
void BooBackendVoice::setMatrixCoefficients(const float coefs[8], bool slew)
void BooBackendVoice::VoiceCallback::routeAudio(size_t frames, double dt, int busId, int32_t* in, int32_t* out)
{
m_booVoice->setMonoMatrixCoefficients(coefs, slew);
m_parent.m_clientVox.routeAudio(frames, dt, busId, in, out);
}
void BooBackendVoice::setSubmixMatrixCoefficients(const float coefs[8], bool slew)
void BooBackendVoice::VoiceCallback::routeAudio(size_t frames, double dt, int busId, float* in, float* out)
{
m_booVoice->setMonoSubmixMatrixCoefficients(coefs, slew);
m_parent.m_clientVox.routeAudio(frames, dt, busId, in, out);
}
void BooBackendVoice::setPitchRatio(double ratio, bool slew)
BooBackendVoice::BooBackendVoice(boo::IAudioVoiceEngine& engine, Voice& clientVox, double sampleRate, bool dynamicPitch)
: m_clientVox(clientVox), m_cb(*this), m_booVoice(engine.allocateNewMonoVoice(sampleRate, &m_cb, dynamicPitch))
{
m_booVoice->setPitchRatio(ratio, slew);
}
void BooBackendVoice::start()
void BooBackendVoice::resetSampleRate(double sampleRate) { m_booVoice->resetSampleRate(sampleRate); }
void BooBackendVoice::resetChannelLevels() { m_booVoice->resetChannelLevels(); }
void BooBackendVoice::setChannelLevels(IBackendSubmix* submix, const float coefs[8], bool slew)
{
m_booVoice->start();
BooBackendSubmix& smx = *reinterpret_cast<BooBackendSubmix*>(submix);
m_booVoice->setMonoChannelLevels(smx.m_booSubmix.get(), coefs, slew);
}
void BooBackendVoice::stop()
{
m_booVoice->stop();
}
void BooBackendVoice::setPitchRatio(double ratio, bool slew) { m_booVoice->setPitchRatio(ratio, slew); }
bool BooBackendSubmix::SubmixCallback::canApplyEffect() const
{
return m_parent.m_clientSmx.canApplyEffect();
}
void BooBackendVoice::start() { m_booVoice->start(); }
void BooBackendSubmix::SubmixCallback::applyEffect(int16_t* audio, size_t frameCount,
const boo::ChannelMap& chanMap, double) const
void BooBackendVoice::stop() { m_booVoice->stop(); }
bool BooBackendSubmix::SubmixCallback::canApplyEffect() const { return m_parent.m_clientSmx.canApplyEffect(); }
void BooBackendSubmix::SubmixCallback::applyEffect(int16_t* audio, size_t frameCount, const boo::ChannelMap& chanMap,
double) const
{
return m_parent.m_clientSmx.applyEffect(audio, frameCount, reinterpret_cast<const ChannelMap&>(chanMap));
}
void BooBackendSubmix::SubmixCallback::applyEffect(int32_t* audio, size_t frameCount,
const boo::ChannelMap& chanMap, double) const
void BooBackendSubmix::SubmixCallback::applyEffect(int32_t* audio, size_t frameCount, const boo::ChannelMap& chanMap,
double) const
{
return m_parent.m_clientSmx.applyEffect(audio, frameCount, reinterpret_cast<const ChannelMap&>(chanMap));
}
void BooBackendSubmix::SubmixCallback::applyEffect(float* audio, size_t frameCount,
const boo::ChannelMap& chanMap, double) const
void BooBackendSubmix::SubmixCallback::applyEffect(float* audio, size_t frameCount, const boo::ChannelMap& chanMap,
double) const
{
return m_parent.m_clientSmx.applyEffect(audio, frameCount, reinterpret_cast<const ChannelMap&>(chanMap));
}
BooBackendSubmix::BooBackendSubmix(boo::IAudioVoiceEngine& engine, Submix& clientSmx)
: m_clientSmx(clientSmx), m_cb(*this), m_booSubmix(engine.allocateNewSubmix(&m_cb))
{}
BooBackendSubmix::BooBackendSubmix(boo::IAudioSubmix& parent, Submix& clientSmx)
: m_clientSmx(clientSmx), m_cb(*this), m_booSubmix(parent.allocateNewSubmix(&m_cb))
{}
void BooBackendSubmix::setChannelGains(const float gains[8])
void BooBackendSubmix::SubmixCallback::resetOutputSampleRate(double sampleRate)
{
m_booSubmix->setChannelGains(gains);
m_parent.m_clientSmx.resetOutputSampleRate(sampleRate);
}
std::unique_ptr<IBackendVoice>
BooBackendSubmix::allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch)
BooBackendSubmix::BooBackendSubmix(boo::IAudioVoiceEngine& engine, Submix& clientSmx, bool mainOut, int busId)
: m_clientSmx(clientSmx), m_cb(*this), m_booSubmix(engine.allocateNewSubmix(mainOut, &m_cb, busId))
{
return std::make_unique<BooBackendVoice>(*m_booSubmix, clientVox, sampleRate, dynamicPitch);
}
double BooBackendSubmix::getSampleRate() const
void BooBackendSubmix::setSendLevel(IBackendSubmix* submix, float level, bool slew)
{
return m_booSubmix->getSampleRate();
BooBackendSubmix& smx = *reinterpret_cast<BooBackendSubmix*>(submix);
m_booSubmix->setSendLevel(smx.m_booSubmix.get(), level, slew);
}
SubmixFormat BooBackendSubmix::getSampleFormat() const
{
return SubmixFormat(m_booSubmix->getSampleFormat());
}
double BooBackendSubmix::getSampleRate() const { return m_booSubmix->getSampleRate(); }
SubmixFormat BooBackendSubmix::getSampleFormat() const { return SubmixFormat(m_booSubmix->getSampleFormat()); }
std::string BooBackendMIDIReader::description()
{
return m_midiIn->description();
}
std::string BooBackendMIDIReader::description() { return m_midiIn->description(); }
BooBackendMIDIReader::~BooBackendMIDIReader() {}
BooBackendMIDIReader::BooBackendMIDIReader(Engine& engine, const char* name)
: m_engine(engine), m_decoder(*this)
BooBackendMIDIReader::BooBackendMIDIReader(Engine& engine, const char* name, bool useLock)
: m_engine(engine), m_decoder(*this), m_useLock(useLock)
{
BooBackendVoiceAllocator& voxAlloc = static_cast<BooBackendVoiceAllocator&>(engine.getBackend());
if (!name)
@@ -123,42 +105,49 @@ BooBackendMIDIReader::BooBackendMIDIReader(Engine& engine, const char* name)
auto devices = voxAlloc.m_booEngine.enumerateMIDIDevices();
for (const auto& dev : devices)
{
m_midiIn = voxAlloc.m_booEngine.newRealMIDIIn(dev.first.c_str(),
std::bind(&BooBackendMIDIReader::_MIDIReceive, this,
std::placeholders::_1));
m_midiIn = voxAlloc.m_booEngine.newRealMIDIIn(
dev.first.c_str(),
std::bind(&BooBackendMIDIReader::_MIDIReceive, this, std::placeholders::_1, std::placeholders::_2));
if (m_midiIn)
return;
}
m_midiIn = voxAlloc.m_booEngine.newVirtualMIDIIn(std::bind(&BooBackendMIDIReader::_MIDIReceive, this,
std::placeholders::_1));
m_midiIn = voxAlloc.m_booEngine.newVirtualMIDIIn(
std::bind(&BooBackendMIDIReader::_MIDIReceive, this, std::placeholders::_1, std::placeholders::_2));
}
else
m_midiIn = voxAlloc.m_booEngine.newRealMIDIIn(name,
std::bind(&BooBackendMIDIReader::_MIDIReceive, this,
std::placeholders::_1));
m_midiIn = voxAlloc.m_booEngine.newRealMIDIIn(
name, std::bind(&BooBackendMIDIReader::_MIDIReceive, this, std::placeholders::_1, std::placeholders::_2));
}
void BooBackendMIDIReader::_MIDIReceive(std::vector<uint8_t>&& bytes)
void BooBackendMIDIReader::_MIDIReceive(std::vector<uint8_t>&& bytes, double time)
{
std::unique_lock<std::mutex> lk(m_midiMutex);
m_queue.emplace_back(std::chrono::steady_clock::now(), std::move(bytes));
std::unique_lock<std::mutex> lk(m_midiMutex, std::defer_lock_t{});
if (m_useLock)
lk.lock();
m_queue.emplace_back(time, std::move(bytes));
#if 0
openlog("LogIt", (LOG_CONS|LOG_PERROR|LOG_PID), LOG_DAEMON);
syslog(LOG_EMERG, "MIDI receive %f\n", time);
closelog();
#endif
}
void BooBackendMIDIReader::pumpReader(double dt)
{
dt += 0.001; /* Add 1ms to ensure consumer keeps up with producer */
std::unique_lock<std::mutex> lk(m_midiMutex);
std::unique_lock<std::mutex> lk(m_midiMutex, std::defer_lock_t{});
if (m_useLock)
lk.lock();
if (m_queue.empty())
return;
/* Determine range of buffer updates within this period */
auto periodEnd = m_queue.cbegin();
std::chrono::steady_clock::time_point startPt = m_queue.front().first;
for (; periodEnd != m_queue.cend() ; ++periodEnd)
double startPt = m_queue.front().first;
for (; periodEnd != m_queue.cend(); ++periodEnd)
{
double delta = std::chrono::duration_cast<std::chrono::microseconds>
(periodEnd->first - startPt).count() / 1000000.0;
double delta = periodEnd->first - startPt;
if (delta > dt)
break;
}
@@ -167,8 +156,17 @@ void BooBackendMIDIReader::pumpReader(double dt)
return;
/* Dispatch buffers */
for (auto it = m_queue.begin() ; it != periodEnd ;)
for (auto it = m_queue.begin(); it != periodEnd;)
{
#if 0
char str[64];
sprintf(str, "MIDI %zu %f ", it->second.size(), it->first);
for (uint8_t byte : it->second)
sprintf(str + strlen(str), "%02X ", byte);
openlog("LogIt", (LOG_CONS|LOG_PERROR|LOG_PID), LOG_DAEMON);
syslog(LOG_EMERG, "%s\n", str);
closelog();
#endif
m_decoder.receiveBytes(it->second.cbegin(), it->second.cend());
it = m_queue.erase(it);
}
@@ -178,17 +176,25 @@ void BooBackendMIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->keyOff(chan, key, velocity);
#if 0
openlog("LogIt", (LOG_CONS|LOG_PERROR|LOG_PID), LOG_DAEMON);
syslog(LOG_EMERG, "NoteOff %d", key);
closelog();
#endif
}
void BooBackendMIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->keyOn(chan, key, velocity);
#if 0
openlog("LogIt", (LOG_CONS|LOG_PERROR|LOG_PID), LOG_DAEMON);
syslog(LOG_EMERG, "NoteOn %d", key);
closelog();
#endif
}
void BooBackendMIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/)
{
}
void BooBackendMIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/) {}
void BooBackendMIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
{
@@ -202,9 +208,7 @@ void BooBackendMIDIReader::programChange(uint8_t chan, uint8_t program)
seq->setChanProgram(chan, program);
}
void BooBackendMIDIReader::channelPressure(uint8_t /*chan*/, uint8_t /*pressure*/)
{
}
void BooBackendMIDIReader::channelPressure(uint8_t /*chan*/, uint8_t /*pressure*/) {}
void BooBackendMIDIReader::pitchBend(uint8_t chan, int16_t pitch)
{
@@ -212,20 +216,15 @@ void BooBackendMIDIReader::pitchBend(uint8_t chan, int16_t pitch)
seq->setPitchWheel(chan, (pitch - 0x2000) / float(0x2000));
}
void BooBackendMIDIReader::allSoundOff(uint8_t chan)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->allOff(chan, true);
}
void BooBackendMIDIReader::resetAllControllers(uint8_t /*chan*/)
{
}
void BooBackendMIDIReader::resetAllControllers(uint8_t /*chan*/) {}
void BooBackendMIDIReader::localControl(uint8_t /*chan*/, bool /*on*/)
{
}
void BooBackendMIDIReader::localControl(uint8_t /*chan*/, bool /*on*/) {}
void BooBackendMIDIReader::allNotesOff(uint8_t chan)
{
@@ -233,67 +232,39 @@ void BooBackendMIDIReader::allNotesOff(uint8_t chan)
seq->allOff(chan, false);
}
void BooBackendMIDIReader::omniMode(uint8_t /*chan*/, bool /*on*/)
{
}
void BooBackendMIDIReader::omniMode(uint8_t /*chan*/, bool /*on*/) {}
void BooBackendMIDIReader::polyMode(uint8_t /*chan*/, bool /*on*/)
{
}
void BooBackendMIDIReader::polyMode(uint8_t /*chan*/, bool /*on*/) {}
void BooBackendMIDIReader::sysex(const void* /*data*/, size_t /*len*/) {}
void BooBackendMIDIReader::sysex(const void* /*data*/, size_t /*len*/)
{
}
void BooBackendMIDIReader::timeCodeQuarterFrame(uint8_t /*message*/, uint8_t /*value*/) {}
void BooBackendMIDIReader::timeCodeQuarterFrame(uint8_t /*message*/, uint8_t /*value*/)
{
}
void BooBackendMIDIReader::songPositionPointer(uint16_t /*pointer*/) {}
void BooBackendMIDIReader::songPositionPointer(uint16_t /*pointer*/)
{
}
void BooBackendMIDIReader::songSelect(uint8_t /*song*/) {}
void BooBackendMIDIReader::songSelect(uint8_t /*song*/)
{
}
void BooBackendMIDIReader::tuneRequest() {}
void BooBackendMIDIReader::tuneRequest()
{
}
void BooBackendMIDIReader::startSeq() {}
void BooBackendMIDIReader::continueSeq() {}
void BooBackendMIDIReader::startSeq()
{
}
void BooBackendMIDIReader::stopSeq() {}
void BooBackendMIDIReader::continueSeq()
{
}
void BooBackendMIDIReader::reset() {}
void BooBackendMIDIReader::stopSeq()
{
}
BooBackendVoiceAllocator::BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine) : m_booEngine(booEngine) {}
void BooBackendMIDIReader::reset()
{
}
BooBackendVoiceAllocator::BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine)
: m_booEngine(booEngine)
{}
std::unique_ptr<IBackendVoice>
BooBackendVoiceAllocator::allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch)
std::unique_ptr<IBackendVoice> BooBackendVoiceAllocator::allocateVoice(Voice& clientVox, double sampleRate,
bool dynamicPitch)
{
return std::make_unique<BooBackendVoice>(m_booEngine, clientVox, sampleRate, dynamicPitch);
}
std::unique_ptr<IBackendSubmix> BooBackendVoiceAllocator::allocateSubmix(Submix& clientSmx)
std::unique_ptr<IBackendSubmix> BooBackendVoiceAllocator::allocateSubmix(Submix& clientSmx, bool mainOut, int busId)
{
return std::make_unique<BooBackendSubmix>(m_booEngine, clientSmx);
return std::make_unique<BooBackendSubmix>(m_booEngine, clientSmx, mainOut, busId);
}
std::vector<std::pair<std::string, std::string>> BooBackendVoiceAllocator::enumerateMIDIDevices()
@@ -303,7 +274,7 @@ std::vector<std::pair<std::string, std::string>> BooBackendVoiceAllocator::enume
std::unique_ptr<IMIDIReader> BooBackendVoiceAllocator::allocateMIDIReader(Engine& engine, const char* name)
{
std::unique_ptr<IMIDIReader> ret = std::make_unique<BooBackendMIDIReader>(engine, name);
std::unique_ptr<IMIDIReader> ret = std::make_unique<BooBackendMIDIReader>(engine, name, m_booEngine.useMIDILock());
if (!static_cast<BooBackendMIDIReader&>(*ret).m_midiIn)
return {};
return ret;
@@ -314,14 +285,9 @@ void BooBackendVoiceAllocator::register5MsCallback(std::function<void(double)>&&
m_booEngine.register5MsCallback(std::move(callback));
}
AudioChannelSet BooBackendVoiceAllocator::getAvailableSet()
{
return AudioChannelSet(m_booEngine.getAvailableSet());
}
AudioChannelSet BooBackendVoiceAllocator::getAvailableSet() { return AudioChannelSet(m_booEngine.getAvailableSet()); }
void BooBackendVoiceAllocator::pumpAndMixVoices()
{
m_booEngine.pumpAndMixVoices();
}
void BooBackendVoiceAllocator::pumpAndMixVoices() { m_booEngine.pumpAndMixVoices(); }
void BooBackendVoiceAllocator::setVolume(float vol) { m_booEngine.setVolume(vol); }
}

File diff suppressed because it is too large Load Diff

285
lib/DirectoryEnumerator.cpp Normal file
View File

@@ -0,0 +1,285 @@
#ifdef _WIN32
#include <windows.h>
#include <stdio.h>
#else
#include <dirent.h>
#endif
#include <sys/stat.h>
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
#endif
#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)
#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
#endif
#include <map>
#include "amuse/DirectoryEnumerator.hpp"
namespace amuse
{
DirectoryEnumerator::DirectoryEnumerator(const SystemChar* path, Mode mode, bool sizeSort, bool reverse, bool noHidden)
{
Sstat theStat;
if (Stat(path, &theStat) || !S_ISDIR(theStat.st_mode))
return;
#if _WIN32
SystemString wc(path);
wc += _S("/*");
WIN32_FIND_DATAW d;
HANDLE dir = FindFirstFileW(wc.c_str(), &d);
if (dir == INVALID_HANDLE_VALUE)
return;
switch (mode)
{
case Mode::Native:
do
{
if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S("..")))
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
continue;
SystemString fp(path);
fp += _S('/');
fp += d.cFileName;
Sstat st;
if (Stat(fp.c_str(), &st))
continue;
size_t sz = 0;
bool isDir = false;
if (S_ISDIR(st.st_mode))
isDir = true;
else if (S_ISREG(st.st_mode))
sz = st.st_size;
else
continue;
m_entries.push_back(std::move(Entry(std::move(fp), d.cFileName, sz, isDir)));
} while (FindNextFileW(dir, &d));
break;
case Mode::DirsThenFilesSorted:
case Mode::DirsSorted:
{
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;
do
{
if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S("..")))
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
continue;
SystemString fp(path);
fp += _S('/');
fp += d.cFileName;
Sstat st;
if (Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode))
continue;
sort.emplace(std::make_pair(d.cFileName, Entry(std::move(fp), d.cFileName, 0, true)));
} while (FindNextFileW(dir, &d));
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
if (mode == Mode::DirsSorted)
break;
FindClose(dir);
dir = FindFirstFileW(wc.c_str(), &d);
}
case Mode::FilesSorted:
{
if (mode == Mode::FilesSorted)
m_entries.clear();
if (sizeSort)
{
std::multimap<size_t, Entry> sort;
do
{
if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S("..")))
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
continue;
SystemString fp(path);
fp += _S('/');
fp += d.cFileName;
Sstat st;
if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
continue;
sort.emplace(std::make_pair(st.st_size, Entry(std::move(fp), d.cFileName, st.st_size, false)));
} while (FindNextFileW(dir, &d));
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
}
else
{
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;
do
{
if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S("..")))
continue;
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
continue;
SystemString fp(path);
fp += _S('/');
fp += d.cFileName;
Sstat st;
if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
continue;
sort.emplace(std::make_pair(d.cFileName, Entry(std::move(fp), d.cFileName, st.st_size, false)));
} while (FindNextFileW(dir, &d));
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
}
break;
}
}
FindClose(dir);
#else
DIR* dir = opendir(path);
if (!dir)
return;
const dirent* d;
switch (mode)
{
case Mode::Native:
while ((d = readdir(dir)))
{
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (noHidden && d->d_name[0] == '.')
continue;
SystemString fp(path);
fp += '/';
fp += d->d_name;
Sstat st;
if (Stat(fp.c_str(), &st))
continue;
size_t sz = 0;
bool isDir = false;
if (S_ISDIR(st.st_mode))
isDir = true;
else if (S_ISREG(st.st_mode))
sz = st.st_size;
else
continue;
m_entries.push_back(std::move(Entry(std::move(fp), d->d_name, sz, isDir)));
}
break;
case Mode::DirsThenFilesSorted:
case Mode::DirsSorted:
{
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;
while ((d = readdir(dir)))
{
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (noHidden && d->d_name[0] == '.')
continue;
SystemString fp(path);
fp += '/';
fp += d->d_name;
Sstat st;
if (Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode))
continue;
sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, 0, true)));
}
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
if (mode == Mode::DirsSorted)
break;
rewinddir(dir);
}
case Mode::FilesSorted:
{
if (mode == Mode::FilesSorted)
m_entries.clear();
if (sizeSort)
{
std::multimap<size_t, Entry> sort;
while ((d = readdir(dir)))
{
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (noHidden && d->d_name[0] == '.')
continue;
SystemString fp(path);
fp += '/';
fp += d->d_name;
Sstat st;
if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
continue;
sort.emplace(std::make_pair(st.st_size, Entry(std::move(fp), d->d_name, st.st_size, false)));
}
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
}
else
{
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;
while ((d = readdir(dir)))
{
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
continue;
if (noHidden && d->d_name[0] == '.')
continue;
SystemString fp(path);
fp += '/';
fp += d->d_name;
Sstat st;
if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
continue;
sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, st.st_size, false)));
}
if (reverse)
for (auto it = sort.crbegin(); it != sort.crend(); ++it)
m_entries.push_back(std::move(it->second));
else
for (auto& e : sort)
m_entries.push_back(std::move(e.second));
}
break;
}
}
closedir(dir);
#endif
}
}

View File

View File

@@ -7,6 +7,7 @@
namespace amuse
{
/* clang-format off */
static const float rsmpTab12khz[] =
{
0.097504, 0.802216, 0.101593, -0.000977,
@@ -138,29 +139,41 @@ static const float rsmpTab12khz[] =
-0.001038, 0.105804, 0.802032, 0.093506,
-0.000977, 0.101593, 0.802216, 0.097504,
};
/* clang-format on */
EffectChorus::EffectChorus(uint32_t baseDelay, uint32_t variation, uint32_t period)
: x90_baseDelay(clamp(5u, baseDelay, 15u)),
x94_variation(clamp(0u, variation, 5u)),
x98_period(clamp(500u, period, 10000u))
{}
: x90_baseDelay(clamp(5u, baseDelay, 15u))
, x94_variation(clamp(0u, variation, 5u))
, x98_period(clamp(500u, period, 10000u))
{
}
template <typename T>
EffectChorusImp<T>::EffectChorusImp(uint32_t baseDelay, uint32_t variation,
uint32_t period, double sampleRate)
: EffectChorus(baseDelay, variation, period),
m_sampsPerMs(std::ceil(sampleRate / 1000.0)),
m_blockSamples(m_sampsPerMs * 5)
EffectChorusImp<T>::EffectChorusImp(uint32_t baseDelay, uint32_t variation, uint32_t period, double sampleRate)
: EffectChorus(baseDelay, variation, period)
{
_setup(sampleRate);
}
template <typename T>
void EffectChorusImp<T>::_setup(double sampleRate)
{
m_sampsPerMs = std::ceil(sampleRate / 1000.0);
m_blockSamples = m_sampsPerMs * 5;
delete[] x0_lastChans[0][0];
T* buf = new T[m_blockSamples * AMUSE_CHORUS_NUM_BLOCKS * 8];
memset(buf, 0, m_blockSamples * AMUSE_CHORUS_NUM_BLOCKS * 8 * sizeof(T));
size_t chanPitch = m_blockSamples * AMUSE_CHORUS_NUM_BLOCKS;
for (int c=0 ; c<8 ; ++c)
for (int i=0 ; i<AMUSE_CHORUS_NUM_BLOCKS ; ++i)
for (int c = 0; c < 8; ++c)
for (int i = 0; i < AMUSE_CHORUS_NUM_BLOCKS; ++i)
x0_lastChans[c][i] = buf + chanPitch * c + m_blockSamples * i;
x6c_src.x88_trigger = chanPitch;
m_dirty = true;
}
template <typename T>
@@ -196,7 +209,7 @@ void EffectChorusImp<T>::SrcInfo::doSrc1(size_t blockSamples, size_t chanCount)
float cur = x70_smpBase[x7c_posHi];
T* dest = x6c_dest;
for (size_t i=0 ; i<blockSamples ; ++i)
for (size_t i = 0; i < blockSamples; ++i)
{
const float* selTab = &rsmpTab12khz[x78_posLo >> 23 & 0x1fc];
@@ -237,7 +250,7 @@ void EffectChorusImp<T>::SrcInfo::doSrc2(size_t blockSamples, size_t chanCount)
float cur = x70_smpBase[x7c_posHi];
T* dest = x6c_dest;
for (size_t i=0 ; i<blockSamples ; ++i)
for (size_t i = 0; i < blockSamples; ++i)
{
const float* selTab = &rsmpTab12khz[x78_posLo >> 23 & 0x1fc];
++x7c_posHi;
@@ -294,25 +307,18 @@ void EffectChorusImp<T>::applyEffect(T* audio, size_t frameCount, const ChannelM
_update();
size_t remFrames = frameCount;
for (size_t f=0 ; f<frameCount ;)
for (size_t f = 0; f < frameCount;)
{
uint8_t next = x24_currentLast + 1;
uint8_t buf = next % 3;
T* bufs[8] =
{
x0_lastChans[0][buf],
x0_lastChans[1][buf],
x0_lastChans[2][buf],
x0_lastChans[3][buf],
x0_lastChans[4][buf],
x0_lastChans[5][buf],
x0_lastChans[6][buf],
x0_lastChans[7][buf],
T* bufs[8] = {
x0_lastChans[0][buf], x0_lastChans[1][buf], x0_lastChans[2][buf], x0_lastChans[3][buf],
x0_lastChans[4][buf], x0_lastChans[5][buf], x0_lastChans[6][buf], x0_lastChans[7][buf],
};
T* inBuf = audio;
for (size_t s=0 ; f<frameCount && s<m_blockSamples ; ++s, ++f)
for (size_t c=0 ; c<chanMap.m_channelCount && c<8 ; ++c)
for (size_t s = 0; f < frameCount && s < m_blockSamples; ++s, ++f)
for (size_t c = 0; c < chanMap.m_channelCount && c < 8; ++c)
*bufs[c]++ = *inBuf++;
x6c_src.x84_pitchHi = (x60_pitchOffset >> 16) + 1;
@@ -327,7 +333,7 @@ void EffectChorusImp<T>::applyEffect(T* audio, size_t frameCount, const ChannelM
T* outBuf = audio;
size_t bs = std::min(remFrames, size_t(m_blockSamples));
for (size_t c=0 ; c<chanMap.m_channelCount && c<8 ; ++c)
for (size_t c = 0; c < chanMap.m_channelCount && c < 8; ++c)
{
x6c_src.x7c_posHi = x5c_currentPosHi;
x6c_src.x78_posLo = x58_currentPosLo;
@@ -344,7 +350,8 @@ void EffectChorusImp<T>::applyEffect(T* audio, size_t frameCount, const ChannelM
case 1:
x6c_src.doSrc2(bs, chanMap.m_channelCount);
break;
default: break;
default:
break;
}
}
@@ -363,5 +370,4 @@ void EffectChorusImp<T>::applyEffect(T* audio, size_t frameCount, const ChannelM
template class EffectChorusImp<int16_t>;
template class EffectChorusImp<int32_t>;
template class EffectChorusImp<float>;
}

View File

@@ -8,29 +8,35 @@ namespace amuse
{
template <typename T>
EffectDelayImp<T>::EffectDelayImp(uint32_t initDelay, uint32_t initFeedback,
uint32_t initOutput, double sampleRate)
: m_sampsPerMs(std::ceil(sampleRate / 1000.0)),
m_blockSamples(m_sampsPerMs * 5)
EffectDelayImp<T>::EffectDelayImp(uint32_t initDelay, uint32_t initFeedback, uint32_t initOutput, double sampleRate)
{
initDelay = clamp(10u, initDelay, 5000u);
initFeedback = clamp(0u, initFeedback, 100u);
initOutput = clamp(0u, initOutput, 100u);
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
{
x3c_delay[i] = initDelay;
x48_feedback[i] = initFeedback;
x54_output[i] = initOutput;
}
_setup(sampleRate);
}
template <typename T>
void EffectDelayImp<T>::_setup(double sampleRate)
{
m_sampsPerMs = std::ceil(sampleRate / 1000.0);
m_blockSamples = m_sampsPerMs * 5;
_update();
}
template <typename T>
void EffectDelayImp<T>::_update()
{
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
{
x0_currentSize[i] = ((x3c_delay[i] - 5) * m_sampsPerMs + 159) / 160;
xc_currentPos[i] = 0;
@@ -50,12 +56,12 @@ void EffectDelayImp<T>::applyEffect(T* audio, size_t frameCount, const ChannelMa
if (m_dirty)
_update();
for (size_t f=0 ; f<frameCount ;)
for (size_t f = 0; f < frameCount;)
{
for (int c=0 ; c<chanMap.m_channelCount ; ++c)
for (int c = 0; c < chanMap.m_channelCount; ++c)
{
T* chanAud = audio + c;
for (int i=0 ; i<m_blockSamples && f<frameCount ; ++i, ++f)
for (int i = 0; i < m_blockSamples && f < frameCount; ++i, ++f)
{
T& liveSamp = chanAud[chanMap.m_channelCount * i];
T& samp = x30_chanLines[c][xc_currentPos[c] * m_blockSamples + i];
@@ -71,5 +77,4 @@ void EffectDelayImp<T>::applyEffect(T* audio, size_t frameCount, const ChannelMa
template class EffectDelayImp<int16_t>;
template class EffectDelayImp<int32_t>;
template class EffectDelayImp<float>;
}

View File

@@ -6,6 +6,8 @@
namespace amuse
{
/* clang-format off */
/* Comb-filter delays */
static const size_t CTapDelays[] =
{
@@ -34,6 +36,8 @@ static const size_t LPTapDelays[] =
73
};
/* clang-format on */
void ReverbDelayLine::allocate(int32_t delay)
{
delay += 2;
@@ -53,47 +57,55 @@ void ReverbDelayLine::setdelay(int32_t delay)
x4_outPoint += x8_length;
}
EffectReverbStd::EffectReverbStd(float coloration, float mix, float time,
float damping, float preDelay)
: x140_x1c8_coloration(clamp(0.f, coloration, 1.f)),
x144_x1cc_mix(clamp(0.f, mix, 1.f)),
x148_x1d0_time(clamp(0.01f, time, 10.f)),
x14c_x1d4_damping(clamp(0.f, damping, 1.f)),
x150_x1d8_preDelay(clamp(0.f, preDelay, 0.1f))
{}
EffectReverbStd::EffectReverbStd(float coloration, float mix, float time, float damping, float preDelay)
: x140_x1c8_coloration(clamp(0.f, coloration, 1.f))
, x144_x1cc_mix(clamp(0.f, mix, 1.f))
, x148_x1d0_time(clamp(0.01f, time, 10.f))
, x14c_x1d4_damping(clamp(0.f, damping, 1.f))
, x150_x1d8_preDelay(clamp(0.f, preDelay, 0.1f))
{
}
EffectReverbHi::EffectReverbHi(float coloration, float mix, float time,
float damping, float preDelay, float crosstalk)
: EffectReverbStd(coloration, mix, time, damping, preDelay),
x1dc_crosstalk(clamp(0.f, crosstalk, 1.0f))
{}
EffectReverbHi::EffectReverbHi(float coloration, float mix, float time, float damping, float preDelay, float crosstalk)
: EffectReverbStd(coloration, mix, time, damping, preDelay), x1dc_crosstalk(clamp(0.f, crosstalk, 1.0f))
{
}
template <typename T>
EffectReverbStdImp<T>::EffectReverbStdImp(float coloration, float mix, float time,
float damping, float preDelay, double sampleRate)
: EffectReverbStd(coloration, mix, time, damping, preDelay),
m_sampleRate(sampleRate)
{}
EffectReverbStdImp<T>::EffectReverbStdImp(float coloration, float mix, float time, float damping, float preDelay,
double sampleRate)
: EffectReverbStd(coloration, mix, time, damping, preDelay)
{
_setup(sampleRate);
}
template <typename T>
void EffectReverbStdImp<T>::_setup(double sampleRate)
{
m_sampleRate = sampleRate;
_update();
}
template <typename T>
void EffectReverbStdImp<T>::_update()
{
float timeSamples = x148_x1d0_time * m_sampleRate;
for (int c=0 ; c<8 ; ++c)
double rateRatio = m_sampleRate / 32000.0;
for (int c = 0; c < 8; ++c)
{
for (int t=0 ; t<2 ; ++t)
for (int t = 0; t < 2; ++t)
{
ReverbDelayLine& combLine = x78_C[c][t];
size_t tapDelay = CTapDelays[t] * m_sampleRate / 32000.0;
size_t tapDelay = CTapDelays[t] * rateRatio;
combLine.allocate(tapDelay);
combLine.setdelay(tapDelay);
xf4_combCoef[c][t] = std::pow(10.f, tapDelay * -3.f / timeSamples);
}
for (int t=0 ; t<2 ; ++t)
for (int t = 0; t < 2; ++t)
{
ReverbDelayLine& allPassLine = x0_AP[c][t];
size_t tapDelay = APTapDelays[t] * m_sampleRate / 32000.0;
size_t tapDelay = APTapDelays[t] * rateRatio;
allPassLine.allocate(tapDelay);
allPassLine.setdelay(tapDelay);
}
@@ -111,7 +123,7 @@ void EffectReverbStdImp<T>::_update()
if (x150_x1d8_preDelay != 0.f)
{
x120_preDelayTime = m_sampleRate * x150_x1d8_preDelay;
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
{
x124_preDelayLine[i].reset(new float[x120_preDelayTime]);
memset(x124_preDelayLine[i].get(), 0, x120_preDelayTime * sizeof(float));
@@ -121,7 +133,7 @@ void EffectReverbStdImp<T>::_update()
else
{
x120_preDelayTime = 0;
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
{
x124_preDelayLine[i] = nullptr;
x130_preDelayPtr[i] = nullptr;
@@ -140,9 +152,9 @@ void EffectReverbStdImp<T>::applyEffect(T* audio, size_t frameCount, const Chann
float dampWet = x118_level * 0.6f;
float dampDry = 0.6f - dampWet;
for (size_t f=0 ; f<frameCount ; f+=160)
for (size_t f = 0; f < frameCount; f += 160)
{
for (unsigned c=0 ; c<chanMap.m_channelCount ; ++c)
for (unsigned c = 0; c < chanMap.m_channelCount; ++c)
{
float* combCoefs = xf4_combCoef[c];
float& lpLastOut = x10c_lpLastout[c];
@@ -154,7 +166,7 @@ void EffectReverbStdImp<T>::applyEffect(T* audio, size_t frameCount, const Chann
ReverbDelayLine* linesAP = x0_AP[c];
int procSamples = std::min(size_t(160), frameCount - f);
for (int s=0 ; s<procSamples ; ++s)
for (int s = 0; s < procSamples; ++s)
{
float sample = audio[s * chanMap.m_channelCount + c];
@@ -197,8 +209,8 @@ void EffectReverbStdImp<T>::applyEffect(T* audio, size_t frameCount, const Chann
/* All-pass filter stage */
linesAP[0].xc_inputs[linesAP[0].x0_inPoint] =
xf0_allPassCoef * linesAP[0].x10_lastInput + linesC[0].x10_lastInput + linesC[1].x10_lastInput;
float lowPass = -(xf0_allPassCoef * linesAP[0].xc_inputs[linesAP[0].x0_inPoint] -
linesAP[0].x10_lastInput);
float lowPass =
-(xf0_allPassCoef * linesAP[0].xc_inputs[linesAP[0].x0_inPoint] - linesAP[0].x10_lastInput);
linesAP[0].x0_inPoint += 1;
linesAP[0].x10_lastInput = linesAP[0].xc_inputs[linesAP[0].x4_outPoint];
@@ -212,8 +224,8 @@ void EffectReverbStdImp<T>::applyEffect(T* audio, size_t frameCount, const Chann
lpLastOut = x11c_damping * lpLastOut + lowPass * 0.3f;
linesAP[1].xc_inputs[linesAP[1].x0_inPoint] = xf0_allPassCoef * linesAP[1].x10_lastInput + lpLastOut;
float allPass = -(xf0_allPassCoef * linesAP[1].xc_inputs[linesAP[1].x0_inPoint] -
linesAP[1].x10_lastInput);
float allPass =
-(xf0_allPassCoef * linesAP[1].xc_inputs[linesAP[1].x0_inPoint] - linesAP[1].x10_lastInput);
linesAP[1].x0_inPoint += 1;
linesAP[1].x10_lastInput = linesAP[1].xc_inputs[linesAP[1].x4_outPoint];
@@ -235,12 +247,17 @@ void EffectReverbStdImp<T>::applyEffect(T* audio, size_t frameCount, const Chann
}
template <typename T>
EffectReverbHiImp<T>::EffectReverbHiImp(float coloration, float mix, float time,
float damping, float preDelay, float crosstalk,
double sampleRate)
: EffectReverbHi(coloration, mix, time, damping, preDelay, crosstalk),
m_sampleRate(sampleRate)
EffectReverbHiImp<T>::EffectReverbHiImp(float coloration, float mix, float time, float damping, float preDelay,
float crosstalk, double sampleRate)
: EffectReverbHi(coloration, mix, time, damping, preDelay, crosstalk)
{
_setup(sampleRate);
}
template <typename T>
void EffectReverbHiImp<T>::_setup(double sampleRate)
{
m_sampleRate = sampleRate;
_update();
}
@@ -248,27 +265,28 @@ template <typename T>
void EffectReverbHiImp<T>::_update()
{
float timeSamples = x148_x1d0_time * m_sampleRate;
for (int c=0 ; c<8 ; ++c)
double rateRatio = m_sampleRate / 32000.0;
for (int c = 0; c < 8; ++c)
{
for (int t=0 ; t<3 ; ++t)
for (int t = 0; t < 3; ++t)
{
ReverbDelayLine& combLine = xb4_C[c][t];
size_t tapDelay = CTapDelays[t] * m_sampleRate / 32000.0;
size_t tapDelay = CTapDelays[t] * rateRatio;
combLine.allocate(tapDelay);
combLine.setdelay(tapDelay);
x16c_combCoef[c][t] = std::pow(10.f, tapDelay * -3.f / timeSamples);
}
for (int t=0 ; t<2 ; ++t)
for (int t = 0; t < 2; ++t)
{
ReverbDelayLine& allPassLine = x0_AP[c][t];
size_t tapDelay = APTapDelays[t] * m_sampleRate / 32000.0;
size_t tapDelay = APTapDelays[t] * rateRatio;
allPassLine.allocate(tapDelay);
allPassLine.setdelay(tapDelay);
}
ReverbDelayLine& lpLine = x78_LP[c];
size_t tapDelay = LPTapDelays[c] * m_sampleRate / 32000.0;
size_t tapDelay = LPTapDelays[c] * rateRatio;
lpLine.allocate(tapDelay);
lpLine.setdelay(tapDelay);
}
@@ -285,7 +303,7 @@ void EffectReverbHiImp<T>::_update()
if (x150_x1d8_preDelay != 0.f)
{
x1a4_preDelayTime = m_sampleRate * x150_x1d8_preDelay;
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
{
x1ac_preDelayLine[i].reset(new float[x1a4_preDelayTime]);
memset(x1ac_preDelayLine[i].get(), 0, x1a4_preDelayTime * sizeof(float));
@@ -295,7 +313,7 @@ void EffectReverbHiImp<T>::_update()
else
{
x1a4_preDelayTime = 0;
for (int i=0 ; i<8 ; ++i)
for (int i = 0; i < 8; ++i)
{
x1ac_preDelayLine[i] = nullptr;
x1b8_preDelayPtr[i] = nullptr;
@@ -326,7 +344,7 @@ void EffectReverbHiImp<T>::_handleReverb(T* audio, int c, int chanCount, int sam
float damping = x1a0_damping;
int32_t preDelayTime = x1a4_preDelayTime;
for (int s=0 ; s<sampleCount ; ++s)
for (int s = 0; s < sampleCount; ++s)
{
float sample = audio[s * chanCount + c];
@@ -379,8 +397,8 @@ void EffectReverbHiImp<T>::_handleReverb(T* audio, int c, int chanCount, int sam
linesC[2].x4_outPoint = 0;
/* All-pass filter stage */
linesAP[0].xc_inputs[linesAP[0].x0_inPoint] =
allPassCoef * linesAP[0].x10_lastInput + linesC[0].x10_lastInput + linesC[1].x10_lastInput + linesC[2].x10_lastInput;
linesAP[0].xc_inputs[linesAP[0].x0_inPoint] = allPassCoef * linesAP[0].x10_lastInput + linesC[0].x10_lastInput +
linesC[1].x10_lastInput + linesC[2].x10_lastInput;
linesAP[1].xc_inputs[linesAP[1].x0_inPoint] =
allPassCoef * linesAP[1].x10_lastInput -
@@ -432,16 +450,16 @@ void EffectReverbHiImp<T>::_handleReverb(T* audio, int c, int chanCount, int sam
template <typename T>
void EffectReverbHiImp<T>::_doCrosstalk(T* audio, float wet, float dry, int chanCount, int sampleCount)
{
for (int i=0 ; i<sampleCount ; ++i)
for (int i = 0; i < sampleCount; ++i)
{
T* base = &audio[chanCount*i];
T* base = &audio[chanCount * i];
float allWet = 0;
for (int c=0 ; c<chanCount ; ++c)
for (int c = 0; c < chanCount; ++c)
{
allWet += base[c] * wet;
base[c] *= dry;
}
for (int c=0 ; c<chanCount ; ++c)
for (int c = 0; c < chanCount; ++c)
base[c] = ClampFull<T>(base[c] + allWet);
}
}
@@ -452,10 +470,10 @@ void EffectReverbHiImp<T>::applyEffect(T* audio, size_t frameCount, const Channe
if (m_dirty)
_update();
for (size_t f=0 ; f<frameCount ; f+=160)
for (size_t f = 0; f < frameCount; f += 160)
{
size_t blockSamples = std::min(size_t(160), frameCount - f);
for (unsigned i=0 ; i<chanMap.m_channelCount ; ++i)
for (unsigned i = 0; i < chanMap.m_channelCount; ++i)
{
if (i == 0 && x1a8_internalCrosstalk != 0.f)
{
@@ -475,5 +493,4 @@ template class EffectReverbStdImp<float>;
template class EffectReverbHiImp<int16_t>;
template class EffectReverbHiImp<int32_t>;
template class EffectReverbHiImp<float>;
}

View File

@@ -18,28 +18,15 @@ void Emitter::_destroy()
m_vox->kill();
}
void Emitter::setPos(const Vector3f& pos)
{
}
void Emitter::setPos(const Vector3f& pos) {}
void Emitter::setDir(const Vector3f& dir)
{
}
void Emitter::setDir(const Vector3f& dir) {}
void Emitter::setMaxDist(float maxDist)
{
}
void Emitter::setMaxDist(float maxDist) {}
void Emitter::setMaxVol(float maxVol)
{
}
void Emitter::setMaxVol(float maxVol) {}
void Emitter::setMinVol(float minVol)
{
}
void Emitter::setFalloff(float falloff)
{
}
void Emitter::setMinVol(float minVol) {}
void Emitter::setFalloff(float falloff) {}
}

View File

@@ -11,13 +11,13 @@
namespace amuse
{
static const float FullLevels[8] = {1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f};
Engine::~Engine()
{
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
if (!seq->m_destroyed)
seq->_destroy();
while (m_activeSubmixes.size())
removeSubmix(&m_activeSubmixes.front());
for (std::shared_ptr<Emitter>& emitter : m_activeEmitters)
emitter->_destroy();
for (std::shared_ptr<Voice>& vox : m_activeVoices)
@@ -25,8 +25,11 @@ Engine::~Engine()
}
Engine::Engine(IBackendVoiceAllocator& backend, AmplitudeMode ampMode)
: m_backend(backend), m_ampMode(ampMode)
: m_backend(backend), m_ampMode(ampMode), m_defaultStudio(_allocateStudio(true))
{
m_defaultStudio->getAuxA().makeReverbStd(0.5f, 0.8f, 3.0f, 0.5f, 0.1f);
m_defaultStudio->getAuxB().makeChorus(15, 0, 500);
m_defaultStudioReady = true;
backend.register5MsCallback(std::bind(&Engine::_5MsCallback, this, std::placeholders::_1));
m_midiReader = backend.allocateMIDIReader(*this);
}
@@ -53,38 +56,48 @@ std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(int groupId)
return {};
}
std::list<std::shared_ptr<Voice>>::iterator
Engine::_allocateVoice(const AudioGroup& group, int groupId, double sampleRate,
bool dynamicPitch, bool emitter, Submix* smx)
std::list<std::shared_ptr<Voice>>::iterator Engine::_allocateVoice(const AudioGroup& group, int groupId,
double sampleRate, bool dynamicPitch, bool emitter,
std::weak_ptr<Studio> studio)
{
auto it = m_activeVoices.emplace(m_activeVoices.end(),
new Voice(*this, group, groupId, m_nextVid++, emitter, smx));
if (smx)
m_activeVoices.back()->m_backendVoice =
smx->m_backendSubmix->allocateVoice(*m_activeVoices.back(), sampleRate, dynamicPitch);
else
m_activeVoices.back()->m_backendVoice =
m_backend.allocateVoice(*m_activeVoices.back(), sampleRate, dynamicPitch);
std::shared_ptr<Studio> st = studio.lock();
auto it =
m_activeVoices.emplace(m_activeVoices.end(), new Voice(*this, group, groupId, m_nextVid++, emitter, studio));
m_activeVoices.back()->m_backendVoice = m_backend.allocateVoice(*m_activeVoices.back(), sampleRate, dynamicPitch);
m_activeVoices.back()->m_backendVoice->setChannelLevels(st->getMaster().m_backendSubmix.get(), FullLevels, false);
m_activeVoices.back()->m_backendVoice->setChannelLevels(st->getAuxA().m_backendSubmix.get(), FullLevels, false);
m_activeVoices.back()->m_backendVoice->setChannelLevels(st->getAuxB().m_backendSubmix.get(), FullLevels, false);
return it;
}
std::list<std::shared_ptr<Sequencer>>::iterator
Engine::_allocateSequencer(const AudioGroup& group, int groupId,
int setupId, Submix* smx)
std::list<std::shared_ptr<Sequencer>>::iterator Engine::_allocateSequencer(const AudioGroup& group, int groupId,
int setupId, std::weak_ptr<Studio> studio)
{
const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId);
if (!songGroup)
return {};
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
new Sequencer(*this, group, groupId, *songGroup, setupId, smx));
return it;
if (songGroup)
{
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
new Sequencer(*this, group, groupId, songGroup, setupId, studio));
return it;
}
const SFXGroupIndex* sfxGroup = group.getProj().getSFXGroupIndex(groupId);
if (sfxGroup)
{
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
new Sequencer(*this, group, groupId, sfxGroup, studio));
return it;
}
return {};
}
std::list<Submix>::iterator Engine::_allocateSubmix(Submix* smx)
std::shared_ptr<Studio> Engine::_allocateStudio(bool mainOut)
{
auto it = m_activeSubmixes.emplace(m_activeSubmixes.end(), *this, smx);
m_activeSubmixes.back().m_backendSubmix = m_backend.allocateSubmix(m_activeSubmixes.back());
return it;
std::shared_ptr<Studio> ret = std::make_shared<Studio>(*this, mainOut);
m_activeStudios.emplace(m_activeStudios.end(), ret);
ret->m_master.m_backendSubmix = m_backend.allocateSubmix(ret->m_master, mainOut, 0);
ret->m_auxA.m_backendSubmix = m_backend.allocateSubmix(ret->m_auxA, mainOut, 1);
ret->m_auxB.m_backendSubmix = m_backend.allocateSubmix(ret->m_auxB, mainOut, 2);
return ret;
}
std::list<std::shared_ptr<Voice>>::iterator Engine::_destroyVoice(std::list<std::shared_ptr<Voice>>::iterator it)
@@ -98,7 +111,8 @@ std::list<std::shared_ptr<Voice>>::iterator Engine::_destroyVoice(std::list<std:
return m_activeVoices.erase(it);
}
std::list<std::shared_ptr<Sequencer>>::iterator Engine::_destroySequencer(std::list<std::shared_ptr<Sequencer>>::iterator it)
std::list<std::shared_ptr<Sequencer>>::iterator
Engine::_destroySequencer(std::list<std::shared_ptr<Sequencer>>::iterator it)
{
#ifndef NDEBUG
assert(this == &(*it)->getEngine());
@@ -109,20 +123,9 @@ std::list<std::shared_ptr<Sequencer>>::iterator Engine::_destroySequencer(std::l
return m_activeSequencers.erase(it);
}
std::list<Submix>::iterator Engine::_destroySubmix(std::list<Submix>::iterator it)
{
#ifndef NDEBUG
assert(this == &it->getEngine());
#endif
if (it->m_destroyed)
return m_activeSubmixes.begin();
it->_destroy();
return m_activeSubmixes.erase(it);
}
void Engine::_bringOutYourDead()
{
for (auto it = m_activeEmitters.begin() ; it != m_activeEmitters.end() ;)
for (auto it = m_activeEmitters.begin(); it != m_activeEmitters.end();)
{
Emitter* emitter = it->get();
if (emitter->getVoice()->_isRecursivelyDead())
@@ -134,7 +137,7 @@ void Engine::_bringOutYourDead()
++it;
}
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ;)
for (auto it = m_activeVoices.begin(); it != m_activeVoices.end();)
{
Voice* vox = it->get();
vox->_bringOutYourDead();
@@ -146,7 +149,7 @@ void Engine::_bringOutYourDead()
++it;
}
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ;)
for (auto it = m_activeSequencers.begin(); it != m_activeSequencers.end();)
{
Sequencer* seq = it->get();
seq->_bringOutYourDead();
@@ -157,6 +160,15 @@ void Engine::_bringOutYourDead()
}
++it;
}
for (auto it = m_activeStudios.begin(); it != m_activeStudios.end();)
{
std::shared_ptr<Studio> st = it->lock();
if (!st)
it = m_activeStudios.erase(it);
else
++it;
}
}
void Engine::_5MsCallback(double dt)
@@ -230,7 +242,7 @@ void Engine::removeAudioGroup(const AudioGroupData& data)
AudioGroup* grp = search->second.get();
/* Destroy runtime entities within group */
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ;)
for (auto it = m_activeVoices.begin(); it != m_activeVoices.end();)
{
Voice* vox = it->get();
if (&vox->getAudioGroup() == grp)
@@ -242,7 +254,7 @@ void Engine::removeAudioGroup(const AudioGroupData& data)
++it;
}
for (auto it = m_activeEmitters.begin() ; it != m_activeEmitters.end() ;)
for (auto it = m_activeEmitters.begin(); it != m_activeEmitters.end();)
{
Emitter* emitter = it->get();
if (&emitter->getAudioGroup() == grp)
@@ -254,7 +266,7 @@ void Engine::removeAudioGroup(const AudioGroupData& data)
++it;
}
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ;)
for (auto it = m_activeSequencers.begin(); it != m_activeSequencers.end();)
{
Sequencer* seq = it->get();
if (&seq->getAudioGroup() == grp)
@@ -277,67 +289,11 @@ void Engine::removeAudioGroup(const AudioGroupData& data)
m_audioGroups.erase(search);
}
/** Create new Submix (a.k.a 'Studio') within root mix engine */
Submix* Engine::addSubmix(Submix* smx)
{
return &*_allocateSubmix(smx);
}
std::list<Submix>::iterator Engine::_removeSubmix(std::list<Submix>::iterator smx)
{
/* Delete all voices bound to submix */
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ; ++it)
{
Voice* vox = it->get();
Submix* vsmx = vox->getSubmix();
if (vsmx == &*smx)
vox->kill();
}
/* Delete all sequencers bound to submix */
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ; ++it)
{
Sequencer* seq = it->get();
Submix* ssmx = seq->getSubmix();
if (ssmx == &*smx)
seq->kill();
}
/* Delete all submixes bound to submix */
for (auto it = m_activeSubmixes.begin() ; it != m_activeSubmixes.end() ;)
{
Submix* ssmx = it->getParentSubmix();
if (ssmx == &*smx)
{
it = _removeSubmix(it);
continue;
}
++it;
}
/* Delete submix */
return _destroySubmix(smx);
}
/** Remove Submix and deallocate */
void Engine::removeSubmix(Submix* smx)
{
if (!smx)
return;
for (auto it = m_activeSubmixes.begin() ; it != m_activeSubmixes.end() ;)
{
if (&*it == &*smx)
{
it = _removeSubmix(it);
break;
}
++it;
}
}
/** Create new Studio within engine */
std::shared_ptr<Studio> Engine::addStudio(bool mainOut) { return _allocateStudio(mainOut); }
/** Start soundFX playing from loaded audio groups */
std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, Submix* smx)
std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, std::weak_ptr<Studio> smx)
{
auto search = m_sfxLookup.find(sfxId);
if (search == m_sfxLookup.end())
@@ -349,8 +305,7 @@ std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, Submix*
return nullptr;
std::list<std::shared_ptr<Voice>>::iterator ret =
_allocateVoice(*grp, std::get<1>(search->second),
32000.0, true, false, smx);
_allocateVoice(*grp, std::get<1>(search->second), 32000.0, true, false, smx);
ObjectId oid = (grp->getDataFormat() == DataFormat::PC) ? entry->objId : SBig(entry->objId);
if (!(*ret)->loadSoundObject(oid, 0, 1000.f, entry->defKey, entry->defVel, 0))
@@ -365,8 +320,8 @@ std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, Submix*
}
/** Start soundFX playing from loaded audio groups, attach to positional emitter */
std::shared_ptr<Emitter> Engine::addEmitter(const Vector3f& pos, const Vector3f& dir, float maxDist,
float falloff, int sfxId, float minVol, float maxVol, Submix* smx)
std::shared_ptr<Emitter> Engine::addEmitter(const Vector3f& pos, const Vector3f& dir, float maxDist, float falloff,
int sfxId, float minVol, float maxVol, std::weak_ptr<Studio> smx)
{
auto search = m_sfxLookup.find(sfxId);
if (search == m_sfxLookup.end())
@@ -378,8 +333,7 @@ std::shared_ptr<Emitter> Engine::addEmitter(const Vector3f& pos, const Vector3f&
return nullptr;
std::list<std::shared_ptr<Voice>>::iterator vox =
_allocateVoice(*grp, std::get<1>(search->second),
32000.0, true, true, smx);
_allocateVoice(*grp, std::get<1>(search->second), 32000.0, true, true, smx);
auto emitIt = m_activeEmitters.emplace(m_activeEmitters.end(), new Emitter(*this, *grp, std::move(*vox)));
Emitter& ret = *(*emitIt);
@@ -403,20 +357,39 @@ std::shared_ptr<Emitter> Engine::addEmitter(const Vector3f& pos, const Vector3f&
}
/** Start song playing from loaded audio groups */
std::shared_ptr<Sequencer> Engine::seqPlay(int groupId, int songId,
const unsigned char* arrData, Submix* smx)
std::shared_ptr<Sequencer> Engine::seqPlay(int groupId, int songId, const unsigned char* arrData,
std::weak_ptr<Studio> smx)
{
std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId);
if (!songGrp.second)
return {};
if (songGrp.second)
{
std::list<std::shared_ptr<Sequencer>>::iterator ret = _allocateSequencer(*songGrp.first, groupId, songId, smx);
if (!*ret)
return {};
std::list<std::shared_ptr<Sequencer>>::iterator ret = _allocateSequencer(*songGrp.first, groupId, songId, smx);
if (!*ret)
return {};
if (arrData)
(*ret)->playSong(arrData);
return *ret;
}
if (arrData)
(*ret)->playSong(arrData);
return *ret;
std::pair<AudioGroup*, const SFXGroupIndex*> sfxGrp = _findSFXGroup(groupId);
if (sfxGrp.second)
{
std::list<std::shared_ptr<Sequencer>>::iterator ret = _allocateSequencer(*sfxGrp.first, groupId, songId, smx);
if (!*ret)
return {};
return *ret;
}
return {};
}
extern "C" const float VolumeLUT[];
/** Set total volume of engine */
void Engine::setVolume(float vol)
{
m_backend.setVolume(VolumeLUT[int(clamp(0.f, vol, 1.f) * 65536)] * 1.46245869f);
}
/** Find voice from VoiceId */
@@ -442,7 +415,7 @@ std::shared_ptr<Voice> Engine::findVoice(int vid)
/** Stop all voices in `kg`, stops immediately (no KeyOff) when `flag` set */
void Engine::killKeygroup(uint8_t kg, bool now)
{
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ;)
for (auto it = m_activeVoices.begin(); it != m_activeVoices.end();)
{
Voice* vox = it->get();
if (vox->m_keygroup == kg)
@@ -464,7 +437,7 @@ void Engine::killKeygroup(uint8_t kg, bool now)
/** Send all voices using `macroId` the message `val` */
void Engine::sendMacroMessage(ObjectId macroId, int32_t val)
{
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ; ++it)
for (auto it = m_activeVoices.begin(); it != m_activeVoices.end(); ++it)
{
Voice* vox = it->get();
if (vox->getObjectId() == macroId)
@@ -474,5 +447,4 @@ void Engine::sendMacroMessage(ObjectId macroId, int32_t val)
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
seq->sendMacroMessage(macroId, val);
}
}

View File

@@ -1,8 +1,20 @@
#include "amuse/Envelope.hpp"
#include "amuse/Voice.hpp"
namespace amuse
{
static int32_t MIDItoTIME[104] = {/* [0..103] -> milliseconds */
0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110,
110, 120, 130, 140, 150, 160, 170, 190, 200, 220, 230, 250,
270, 290, 310, 330, 350, 380, 410, 440, 470, 500, 540, 580,
620, 660, 710, 760, 820, 880, 940, 1000, 1000, 1100, 1200, 1300,
1400, 1500, 1600, 1700, 1800, 2000, 2100, 2300, 2400, 2600, 2800, 3000,
3200, 3500, 3700, 4000, 4300, 4600, 4900, 5300, 5700, 6100, 6500, 7000,
7500, 8100, 8600, 9300, 9900, 10000, 11000, 12000, 13000, 14000, 15000, 16000,
17000, 18000, 19000, 21000, 22000, 24000, 26000, 28000, 30000, 32000, 34000, 37000,
39000, 42000, 45000, 49000, 50000, 55000, 60000, 65000};
void Envelope::reset(const ADSR* adsr)
{
m_phase = State::Attack;
@@ -12,6 +24,7 @@ void Envelope::reset(const ADSR* adsr)
m_sustainFactor = adsr->getSustain();
m_releaseTime = adsr->getRelease();
m_releaseStartFactor = 0.0;
m_adsrSet = true;
}
void Envelope::reset(const ADSRDLS* adsr, int8_t note, int8_t vel)
@@ -23,6 +36,17 @@ void Envelope::reset(const ADSRDLS* adsr, int8_t note, int8_t vel)
m_sustainFactor = adsr->getSustain();
m_releaseTime = adsr->getRelease();
m_releaseStartFactor = 0.0;
m_adsrSet = true;
}
void Envelope::keyOff(const Voice& vox)
{
double releaseTime = m_releaseTime;
if (vox.m_state.m_useAdsrControllers)
releaseTime = MIDItoTIME[clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiRelease)), 103)] / 1000.0;
m_phase = (releaseTime != 0.0) ? State::Release : State::Complete;
m_curTime = 0.0;
}
void Envelope::keyOff()
@@ -31,7 +55,7 @@ void Envelope::keyOff()
m_curTime = 0.0;
}
float Envelope::advance(double dt)
float Envelope::advance(double dt, const Voice& vox)
{
double thisTime = m_curTime;
m_curTime += dt;
@@ -40,14 +64,18 @@ float Envelope::advance(double dt)
{
case State::Attack:
{
if (m_attackTime == 0.0)
double attackTime = m_attackTime;
if (vox.m_state.m_useAdsrControllers)
attackTime = MIDItoTIME[clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiAttack)), 103)] / 1000.0;
if (attackTime == 0.0)
{
m_phase = State::Decay;
m_curTime = 0.0;
m_releaseStartFactor = 1.f;
return 1.f;
}
double attackFac = thisTime / m_attackTime;
double attackFac = thisTime / attackTime;
if (attackFac >= 1.0)
{
m_phase = State::Decay;
@@ -60,36 +88,52 @@ float Envelope::advance(double dt)
}
case State::Decay:
{
if (m_decayTime == 0.0)
double decayTime = m_decayTime;
if (vox.m_state.m_useAdsrControllers)
decayTime = MIDItoTIME[clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiDecay)), 103)] / 1000.0;
double sustainFactor = m_sustainFactor;
if (vox.m_state.m_useAdsrControllers)
sustainFactor = clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiSustain)), 127) / 127.0;
if (decayTime == 0.0)
{
m_phase = State::Sustain;
m_curTime = 0.0;
m_releaseStartFactor = m_sustainFactor;
return m_sustainFactor;
m_releaseStartFactor = sustainFactor;
return sustainFactor;
}
double decayFac = thisTime / m_decayTime;
double decayFac = thisTime / decayTime;
if (decayFac >= 1.0)
{
m_phase = State::Sustain;
m_curTime = 0.0;
m_releaseStartFactor = m_sustainFactor;
return m_sustainFactor;
m_releaseStartFactor = sustainFactor;
return sustainFactor;
}
m_releaseStartFactor = (1.0 - decayFac) + decayFac * m_sustainFactor;
m_releaseStartFactor = (1.0 - decayFac) + decayFac * sustainFactor;
return m_releaseStartFactor;
}
case State::Sustain:
{
return m_sustainFactor;
double sustainFactor = m_sustainFactor;
if (vox.m_state.m_useAdsrControllers)
sustainFactor = clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiSustain)), 127) / 127.0;
return sustainFactor;
}
case State::Release:
{
if (m_releaseTime == 0.0)
double releaseTime = m_releaseTime;
if (vox.m_state.m_useAdsrControllers)
releaseTime = MIDItoTIME[clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiRelease)), 103)] / 1000.0;
if (releaseTime == 0.0)
{
m_phase = State::Complete;
return 0.f;
}
double releaseFac = thisTime / m_releaseTime;
double releaseFac = thisTime / releaseTime;
if (releaseFac >= 1.0)
{
m_phase = State::Complete;
@@ -103,4 +147,82 @@ float Envelope::advance(double dt)
}
}
float Envelope::advance(double dt)
{
double thisTime = m_curTime;
m_curTime += dt;
switch (m_phase)
{
case State::Attack:
{
double attackTime = m_attackTime;
if (attackTime == 0.0)
{
m_phase = State::Decay;
m_curTime = 0.0;
m_releaseStartFactor = 1.f;
return 1.f;
}
double attackFac = thisTime / attackTime;
if (attackFac >= 1.0)
{
m_phase = State::Decay;
m_curTime = 0.0;
m_releaseStartFactor = 1.f;
return 1.f;
}
m_releaseStartFactor = attackFac;
return attackFac;
}
case State::Decay:
{
double decayTime = m_decayTime;
double sustainFactor = m_sustainFactor;
if (decayTime == 0.0)
{
m_phase = State::Sustain;
m_curTime = 0.0;
m_releaseStartFactor = sustainFactor;
return sustainFactor;
}
double decayFac = thisTime / decayTime;
if (decayFac >= 1.0)
{
m_phase = State::Sustain;
m_curTime = 0.0;
m_releaseStartFactor = sustainFactor;
return sustainFactor;
}
m_releaseStartFactor = (1.0 - decayFac) + decayFac * sustainFactor;
return m_releaseStartFactor;
}
case State::Sustain:
{
return m_sustainFactor;
}
case State::Release:
{
double releaseTime = m_releaseTime;
if (releaseTime == 0.0)
{
m_phase = State::Complete;
return 0.f;
}
double releaseFac = thisTime / releaseTime;
if (releaseFac >= 1.0)
{
m_phase = State::Complete;
return 0.f;
}
return std::min(m_releaseStartFactor, 1.0 - releaseFac);
}
case State::Complete:
default:
return 0.f;
}
}
}

View File

@@ -92,7 +92,7 @@ unsigned N64MusyXDecompressFrame(int16_t* out, const uint8_t* in,
adpcm_get_predicted_frame(frame, &in[0x0], &in[0x8], rshift);
procSamples = (remSamples < 2) ? remSamples : 2;
memcpy(out, frame, 2 * procSamples);
memmove(out, frame, 2 * procSamples);
samples += procSamples;
remSamples -= procSamples;
if (samples == lastSample)
@@ -124,7 +124,7 @@ unsigned N64MusyXDecompressFrame(int16_t* out, const uint8_t* in,
adpcm_get_predicted_frame(frame, &in[0x4], &in[0x18], rshift);
procSamples = (remSamples < 2) ? remSamples : 2;
memcpy(out, frame, 2 * procSamples);
memmove(out, frame, 2 * procSamples);
samples += procSamples;
remSamples -= procSamples;
if (samples == lastSample)
@@ -145,7 +145,7 @@ unsigned N64MusyXDecompressFrameRanged(int16_t* out, const uint8_t* in,
int16_t final[64];
unsigned procSamples = N64MusyXDecompressFrame(final, in, coefs, firstSample + lastSample);
unsigned samples = procSamples - firstSample;
memcpy(out, final + firstSample, samples * 2);
memmove(out, final + firstSample, samples * 2);
return samples;
}

View File

@@ -2,13 +2,14 @@
#include "amuse/Submix.hpp"
#include "amuse/Voice.hpp"
#include "amuse/Engine.hpp"
#include <map>
namespace amuse
{
void Sequencer::ChannelState::_bringOutYourDead()
{
for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;)
for (auto it = m_chanVoxs.begin(); it != m_chanVoxs.end();)
{
Voice* vox = it->second.get();
vox->_bringOutYourDead();
@@ -20,7 +21,7 @@ void Sequencer::ChannelState::_bringOutYourDead()
++it;
}
for (auto it = m_keyoffVoxs.begin() ; it != m_keyoffVoxs.end() ;)
for (auto it = m_keyoffVoxs.begin(); it != m_keyoffVoxs.end();)
{
Voice* vox = it->get();
vox->_bringOutYourDead();
@@ -46,58 +47,103 @@ void Sequencer::_bringOutYourDead()
void Sequencer::_destroy()
{
Entity::_destroy();
if (m_submix)
{
m_engine.removeSubmix(m_submix);
m_submix = nullptr;
}
if (m_studio)
m_studio.reset();
}
Sequencer::~Sequencer()
{
if (m_submix)
{
m_engine.removeSubmix(m_submix);
m_submix = nullptr;
}
if (m_studio)
m_studio.reset();
}
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId,
const SongGroupIndex& songGroup, int setupId, Submix* smx)
: Entity(engine, group, groupId), m_songGroup(songGroup)
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SongGroupIndex* songGroup, int setupId,
std::weak_ptr<Studio> studio)
: Entity(engine, group, groupId), m_songGroup(songGroup), m_studio(studio)
{
auto it = m_songGroup.m_midiSetups.find(setupId);
if (it != m_songGroup.m_midiSetups.cend())
auto it = m_songGroup->m_midiSetups.find(setupId);
if (it != m_songGroup->m_midiSetups.cend())
m_midiSetup = it->second->data();
m_submix = m_engine.addSubmix(smx);
m_submix->makeReverbHi(0.2f, 1.f, 1.f, 0.5f, 0.f, 0.f);
}
Sequencer::ChannelState::~ChannelState()
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, const SFXGroupIndex* sfxGroup,
std::weak_ptr<Studio> studio)
: Entity(engine, group, groupId), m_sfxGroup(sfxGroup), m_studio(studio)
{
std::map<uint16_t, const SFXGroupIndex::SFXEntry*> sortSFX;
for (const auto& sfx : sfxGroup->m_sfxEntries)
sortSFX[sfx.first] = sfx.second;
m_sfxMappings.reserve(sortSFX.size());
for (const auto& sfx : sortSFX)
m_sfxMappings.push_back(sfx.second);
}
Sequencer::ChannelState::ChannelState(Sequencer& parent, uint8_t chanId)
: m_parent(parent), m_chanId(chanId), m_setup(m_parent.m_midiSetup[chanId])
Sequencer::ChannelState::~ChannelState() {}
Sequencer::ChannelState::ChannelState(Sequencer& parent, uint8_t chanId) : m_parent(parent), m_chanId(chanId)
{
if (chanId == 9)
if (m_parent.m_songGroup)
{
auto it = m_parent.m_songGroup.m_drumPages.find(m_setup.programNo);
if (it != m_parent.m_songGroup.m_drumPages.cend())
m_page = it->second;
if (m_parent.m_midiSetup)
{
m_setup = &m_parent.m_midiSetup[chanId];
if (chanId == 9)
{
auto it = m_parent.m_songGroup->m_drumPages.find(m_setup->programNo);
if (it != m_parent.m_songGroup->m_drumPages.cend())
{
m_page = it->second;
m_curProgram = m_setup->programNo;
}
}
else
{
auto it = m_parent.m_songGroup->m_normPages.find(m_setup->programNo);
if (it != m_parent.m_songGroup->m_normPages.cend())
{
m_page = it->second;
m_curProgram = m_setup->programNo;
}
}
m_curVol = m_setup->volume / 127.f;
m_curPan = m_setup->panning / 64.f - 1.f;
m_ctrlVals[0x5b] = m_setup->reverb;
m_ctrlVals[0x5d] = m_setup->chorus;
}
else
{
if (chanId == 9)
{
auto it = m_parent.m_songGroup->m_drumPages.find(0);
if (it != m_parent.m_songGroup->m_drumPages.cend())
m_page = it->second;
}
else
{
auto it = m_parent.m_songGroup->m_normPages.find(0);
if (it != m_parent.m_songGroup->m_normPages.cend())
m_page = it->second;
}
m_curVol = 1.f;
m_curPan = 0.f;
m_ctrlVals[0x5b] = 0;
m_ctrlVals[0x5d] = 0;
}
}
else
else if (m_parent.m_sfxGroup)
{
auto it = m_parent.m_songGroup.m_normPages.find(m_setup.programNo);
if (it != m_parent.m_songGroup.m_normPages.cend())
m_page = it->second;
m_curVol = 1.f;
m_curPan = 0.f;
m_ctrlVals[0x5b] = 0;
m_ctrlVals[0x5d] = 0;
}
m_curVol = m_setup.volume / 127.f;
m_curPan = m_setup.panning / 64.f - 1.f;
m_ctrlVals[0x5b] = m_setup.reverb;
m_ctrlVals[0x5d] = m_setup.chorus;
m_ctrlVals[7] = 127;
m_ctrlVals[10] = 64;
}
void Sequencer::advance(double dt)
@@ -132,41 +178,68 @@ size_t Sequencer::getVoiceCount() const
std::shared_ptr<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
{
if (!m_page)
if (m_parent.m_songGroup && !m_page)
return {};
/* If portamento is enabled for voice, pre-empt spawning new voices */
if (std::shared_ptr<Voice> lastVoice = m_lastVoice.lock())
{
uint8_t lastNote = lastVoice->getLastNote();
if (lastVoice->doPortamento(note))
{
m_chanVoxs.erase(lastNote);
m_chanVoxs[note] = lastVoice;
return lastVoice;
}
}
/* Ensure keyoff sent first */
auto keySearch = m_chanVoxs.find(note);
if (keySearch != m_chanVoxs.cend())
{
if (keySearch->second == m_lastVoice.lock())
m_lastVoice.reset();
keySearch->second->keyOff();
keySearch->second->setPedal(false);
m_keyoffVoxs.emplace(std::move(keySearch->second));
m_chanVoxs.erase(keySearch);
}
std::list<std::shared_ptr<Voice>>::iterator ret =
m_parent.m_engine._allocateVoice(m_parent.m_audioGroup,
m_parent.m_groupId, 32000.0,
true, false, m_parent.m_submix);
std::list<std::shared_ptr<Voice>>::iterator ret = m_parent.m_engine._allocateVoice(
m_parent.m_audioGroup, m_parent.m_groupId, 32000.0, true, false, m_parent.m_studio);
if (*ret)
{
m_chanVoxs[note] = *ret;
(*ret)->installCtrlValues(m_ctrlVals);
ObjectId oid = (m_parent.m_audioGroup.getDataFormat() == DataFormat::PC) ? m_page->objId : SBig(m_page->objId);
if (!(*ret)->loadSoundObject(oid, 0, 1000.f, note, velocity, m_ctrlVals[1]))
ObjectId oid;
if (m_parent.m_songGroup)
oid = (m_parent.m_audioGroup.getDataFormat() == DataFormat::PC) ? m_page->objId : SBig(m_page->objId);
else if (m_parent.m_sfxMappings.size())
{
size_t lookupIdx = note % m_parent.m_sfxMappings.size();
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent.m_sfxMappings[lookupIdx];
oid = (m_parent.m_audioGroup.getDataFormat() == DataFormat::PC) ? sfxEntry->objId : SBig(sfxEntry->objId);
note = sfxEntry->defKey;
}
else
return {};
if (!(*ret)->loadSoundObject(oid, 0, m_parent.m_ticksPerSec, note, velocity, m_ctrlVals[1]))
{
m_parent.m_engine._destroyVoice(ret);
return {};
}
(*ret)->setVolume(m_parent.m_curVol * m_curVol);
(*ret)->setReverbVol(m_ctrlVals[0x5b] / 127.f);
(*ret)->setAuxBVol(m_ctrlVals[0x5d] / 127.f);
(*ret)->setPan(m_curPan);
(*ret)->setPitchWheel(m_curPitchWheel);
if (m_ctrlVals[64] > 64)
(*ret)->setPedal(true);
m_lastVoice = *ret;
}
return *ret;
@@ -189,6 +262,8 @@ void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
if (keySearch == m_chanVoxs.cend())
return;
if (keySearch->second == m_lastVoice.lock())
m_lastVoice.reset();
keySearch->second->keyOff();
m_keyoffVoxs.emplace(std::move(keySearch->second));
m_chanVoxs.erase(keySearch);
@@ -218,30 +293,34 @@ void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
case 10:
setPan(val / 64.f - 1.f);
break;
default: break;
default:
break;
}
}
bool Sequencer::ChannelState::programChange(int8_t prog)
{
if (m_chanId == 9)
if (m_parent.m_songGroup)
{
auto it = m_parent.m_songGroup.m_drumPages.find(prog);
if (it != m_parent.m_songGroup.m_drumPages.cend())
if (m_chanId == 9)
{
m_page = it->second;
m_curProgram = prog;
return true;
auto it = m_parent.m_songGroup->m_drumPages.find(prog);
if (it != m_parent.m_songGroup->m_drumPages.cend())
{
m_page = it->second;
m_curProgram = prog;
return true;
}
}
}
else
{
auto it = m_parent.m_songGroup.m_normPages.find(prog);
if (it != m_parent.m_songGroup.m_normPages.cend())
else
{
m_page = it->second;
m_curProgram = prog;
return true;
auto it = m_parent.m_songGroup->m_normPages.find(prog);
if (it != m_parent.m_songGroup->m_normPages.cend())
{
m_page = it->second;
m_curProgram = prog;
return true;
}
}
}
return false;
@@ -268,6 +347,15 @@ void Sequencer::setCtrlValue(uint8_t chan, uint8_t ctrl, int8_t val)
if (chan > 15)
return;
if (ctrl == 0x66)
{
printf("Loop Start\n");
}
else if (ctrl == 0x67)
{
printf("Loop End\n");
}
if (!m_chanStates[chan])
m_chanStates[chan].emplace(*this, chan);
@@ -294,15 +382,14 @@ void Sequencer::setPitchWheel(uint8_t chan, float pitchWheel)
m_chanStates[chan]->setPitchWheel(pitchWheel);
}
void Sequencer::setTempo(double ticksPerSec)
{
m_ticksPerSec = ticksPerSec;
}
void Sequencer::setTempo(double ticksPerSec) { m_ticksPerSec = ticksPerSec; }
void Sequencer::ChannelState::allOff()
{
for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;)
for (auto it = m_chanVoxs.begin(); it != m_chanVoxs.end();)
{
if (it->second == m_lastVoice.lock())
m_lastVoice.reset();
it->second->keyOff();
m_keyoffVoxs.emplace(std::move(it->second));
it = m_chanVoxs.erase(it);
@@ -350,11 +437,13 @@ void Sequencer::allOff(uint8_t chan, bool now)
void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
{
for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;)
for (auto it = m_chanVoxs.begin(); it != m_chanVoxs.end();)
{
Voice* vox = it->second.get();
if (vox->m_keygroup == kg)
{
if (it->second == m_lastVoice.lock())
m_lastVoice.reset();
if (now)
{
it = m_chanVoxs.erase(it);
@@ -370,7 +459,7 @@ void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
if (now)
{
for (auto it = m_keyoffVoxs.begin() ; it != m_keyoffVoxs.end() ;)
for (auto it = m_keyoffVoxs.begin(); it != m_keyoffVoxs.end();)
{
Voice* vox = it->get();
if (vox->m_keygroup == kg)
@@ -443,6 +532,7 @@ void Sequencer::playSong(const unsigned char* arrData, bool dieOnEnd)
m_arrData = arrData;
m_dieOnEnd = dieOnEnd;
m_songState.initialize(arrData);
setTempo(m_songState.getTempo() * 384 / 60);
m_state = SequencerState::Playing;
}
@@ -494,9 +584,16 @@ void Sequencer::setVolume(float vol)
int8_t Sequencer::getChanProgram(int8_t chanId) const
{
if (chanId > 15 || !m_chanStates[chanId])
if (chanId > 15)
return 0;
if (!m_chanStates[chanId])
{
if (!m_midiSetup)
return 0;
return m_midiSetup[chanId].programNo;
}
return m_chanStates[chanId]->m_curProgram;
}
@@ -532,5 +629,4 @@ void Sequencer::prevChanProgram(int8_t chanId)
return m_chanStates[chanId]->prevProgram();
}
}

1498
lib/SongConverter.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,10 @@ static uint32_t DecodeRLE(const unsigned char*& data)
++data;
thisPart = thisPart * 256 + *data;
if (thisPart == 0)
{
++data;
return -1;
}
}
if (thisPart == 32767)
@@ -68,28 +71,17 @@ static uint32_t DecodeTimeRLE(const unsigned char*& data)
void SongState::Header::swapBig()
{
m_version = SBig(m_version);
m_chanIdxOff = SBig(m_chanIdxOff);
m_trackIdxOff = SBig(m_trackIdxOff);
m_regionIdxOff = SBig(m_regionIdxOff);
m_chanMapOff = SBig(m_chanMapOff);
m_tempoTableOff = SBig(m_tempoTableOff);
m_initialTempo = SBig(m_initialTempo);
m_unkOff = SBig(m_unkOff);
for (int i=0 ; i<64 ; ++i)
m_chanOffs[i] = SBig(m_chanOffs[i]);
}
void SongState::ChanHeader::swapBig()
bool SongState::TrackRegion::indexValid(bool bigEndian) const
{
m_startTick = SBig(m_startTick);
m_unk1 = SBig(m_unk1);
m_unk2 = SBig(m_unk2);
m_dataIndex = SBig(m_dataIndex);
m_unk3 = SBig(m_unk3);
m_startTick2 = SBig(m_startTick2);
m_unk4 = SBig(m_unk4);
m_unk5 = SBig(m_unk5);
m_unk6 = SBig(m_unk6);
m_unk7 = SBig(m_unk7);
return (bigEndian ? SBig(m_regionIndex) : m_regionIndex) >= 0;
}
void SongState::TempoChange::swapBig()
@@ -98,57 +90,265 @@ void SongState::TempoChange::swapBig()
m_tempo = SBig(m_tempo);
}
void SongState::Channel::Header::swapBig()
void SongState::Track::Header::swapBig()
{
m_type = SBig(m_type);
m_pitchOff = SBig(m_pitchOff);
m_modOff = SBig(m_modOff);
}
SongState::Channel::Channel(SongState& parent, uint8_t midiChan, uint32_t startTick,
const unsigned char* song, const unsigned char* chan)
: m_parent(parent), m_midiChan(midiChan), m_startTick(startTick), m_dataBase(chan + 12)
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
: m_parent(parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions)
{
m_data = m_dataBase;
Header header = *reinterpret_cast<const Header*>(chan);
header.swapBig();
if (header.m_type != 8)
{
m_data = nullptr;
return;
}
if (header.m_pitchOff)
m_pitchWheelData = song + header.m_pitchOff;
if (header.m_modOff)
m_modWheelData = song + header.m_modOff;
m_waitCountdown = startTick;
m_lastPitchTick = startTick;
m_lastModTick = startTick;
m_waitCountdown += int32_t(DecodeTimeRLE(m_data));
for (int i = 0; i < 128; ++i)
m_remNoteLengths[i] = INT_MIN;
}
void SongState::initialize(const unsigned char* ptr)
void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
{
m_header = *reinterpret_cast<const Header*>(ptr);
m_header.swapBig();
m_curRegion = region;
uint32_t regionIdx = (m_parent.m_bigEndian ? SBig(m_curRegion->m_regionIndex) : m_curRegion->m_regionIndex);
m_nextRegion = &m_curRegion[1];
/* Initialize all channels */
for (int i=0 ; i<64 ; ++i)
m_data = m_parent.m_songData +
(m_parent.m_bigEndian ? SBig(m_parent.m_regionIdx[regionIdx]) : m_parent.m_regionIdx[regionIdx]);
Header header = *reinterpret_cast<const Header*>(m_data);
if (m_parent.m_bigEndian)
header.swapBig();
m_data += 12;
if (header.m_pitchOff)
m_pitchWheelData = m_parent.m_songData + header.m_pitchOff;
if (header.m_modOff)
m_modWheelData = m_parent.m_songData + header.m_modOff;
m_eventWaitCountdown = 0;
m_lastPitchTick = m_parent.m_curTick;
m_lastPitchVal = 0;
m_lastModTick = m_parent.m_curTick;
m_lastModVal = 0;
if (seq)
{
if (m_header.m_chanOffs[i])
seq->setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f));
seq->setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127));
}
if (m_parent.m_sngVersion == 1)
m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data));
else
{
int32_t absTick = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data))
: *reinterpret_cast<const int32_t*>(m_data));
m_eventWaitCountdown = absTick;
m_lastN64EventTick = absTick;
m_data += 4;
}
}
void SongState::Track::advanceRegion(Sequencer* seq) { setRegion(seq, m_nextRegion); }
int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
{
isBig = ptr[0] == 0;
Header header = *reinterpret_cast<const Header*>(ptr);
if (isBig)
header.swapBig();
const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + header.m_trackIdxOff);
const uint32_t* regionIdxTable = reinterpret_cast<const uint32_t*>(ptr + header.m_regionIdxOff);
/* First determine maximum index of MIDI regions across all tracks */
uint32_t maxRegionIdx = 0;
for (int i = 0; i < 64; ++i)
{
if (trackIdx[i])
{
ChanHeader cHeader = *reinterpret_cast<const ChanHeader*>(ptr + m_header.m_chanOffs[i]);
cHeader.swapBig();
const uint32_t* chanIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_chanIdxOff);
const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff);
m_channels[i].emplace(*this, chanMap[i], cHeader.m_startTick, ptr,
ptr + SBig(chanIdx[cHeader.m_dataIndex]));
const TrackRegion* region = nullptr;
const TrackRegion* nextRegion =
reinterpret_cast<const TrackRegion*>(ptr + (isBig ? SBig(trackIdx[i]) : trackIdx[i]));
/* Iterate all regions */
while (nextRegion->indexValid(isBig))
{
region = nextRegion;
uint32_t regionIdx = (isBig ? SBig(region->m_regionIndex) : region->m_regionIndex);
maxRegionIdx = std::max(maxRegionIdx, regionIdx);
nextRegion = &region[1];
}
}
}
/* Perform 2 trials, first assuming revised format (more likely) */
int v = 1;
for (; v >= 0; --v)
{
bool bad = false;
/* Validate all tracks */
for (int i = 0; i < 64; ++i)
{
if (trackIdx[i])
{
const TrackRegion* region = nullptr;
const TrackRegion* nextRegion =
reinterpret_cast<const TrackRegion*>(ptr + (isBig ? SBig(trackIdx[i]) : trackIdx[i]));
/* Iterate all regions */
while (nextRegion->indexValid(isBig))
{
region = nextRegion;
uint32_t regionIdx = (isBig ? SBig(region->m_regionIndex) : region->m_regionIndex);
nextRegion = &region[1];
const unsigned char* data =
ptr + (isBig ? SBig(regionIdxTable[regionIdx]) : regionIdxTable[regionIdx]);
/* Can't reliably validate final region */
if (regionIdx == maxRegionIdx)
continue;
/* Expected end pointer (next region) */
const unsigned char* expectedEnd =
ptr + (isBig ? SBig(regionIdxTable[regionIdx + 1]) : regionIdxTable[regionIdx + 1]);
Track::Header header = *reinterpret_cast<const Track::Header*>(data);
if (isBig)
header.swapBig();
data += 12;
/* continuous pitch data */
if (header.m_pitchOff)
{
const unsigned char* dptr = ptr + header.m_pitchOff;
while (DecodeRLE(dptr) != 0xffffffff)
{
DecodeContinuousRLE(dptr);
}
if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd))
continue;
}
/* continuous modulation data */
if (header.m_modOff)
{
const unsigned char* dptr = ptr + header.m_modOff;
while (DecodeRLE(dptr) != 0xffffffff)
{
DecodeContinuousRLE(dptr);
}
if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd))
continue;
}
/* Loop through as many commands as we can for this time period */
if (v == 1)
{
/* Revised */
while (true)
{
/* Delta time */
DecodeTimeRLE(data);
/* Load next command */
if (*reinterpret_cast<const uint16_t*>(data) == 0xffff)
{
/* End of channel */
data += 2;
break;
}
else if (data[0] & 0x80 && data[1] & 0x80)
{
/* Control change */
data += 2;
}
else if (data[0] & 0x80)
{
/* Program change */
data += 2;
}
else
{
/* Note */
data += 4;
}
}
}
else
{
/* Legacy */
while (true)
{
/* Delta-time */
data += 4;
/* Load next command */
if (*reinterpret_cast<const uint16_t*>(&data[2]) == 0xffff)
{
/* End of channel */
data += 4;
break;
}
else
{
if ((data[2] & 0x80) != 0x80)
{
/* Note */
}
else if (data[2] & 0x80 && data[3] & 0x80)
{
/* Control change */
}
else if (data[2] & 0x80)
{
/* Program change */
}
data += 4;
}
}
}
if (data < (expectedEnd - 4) || (data > expectedEnd))
{
bad = true;
break;
}
}
if (bad)
break;
}
}
if (bad)
continue;
break;
}
return v;
}
bool SongState::initialize(const unsigned char* ptr)
{
m_sngVersion = DetectVersion(ptr, m_bigEndian);
if (m_sngVersion < 0)
return false;
m_songData = ptr;
m_header = *reinterpret_cast<const Header*>(ptr);
if (m_bigEndian)
m_header.swapBig();
const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_trackIdxOff);
m_regionIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_regionIdxOff);
const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff);
/* Initialize all tracks */
for (int i = 0; i < 64; ++i)
{
if (trackIdx[i])
{
const TrackRegion* region =
reinterpret_cast<const TrackRegion*>(ptr + (m_bigEndian ? SBig(trackIdx[i]) : trackIdx[i]));
m_tracks[i].emplace(*this, chanMap[i], region);
}
else
m_channels[i] = std::experimental::nullopt;
m_tracks[i] = std::experimental::nullopt;
}
/* Initialize tempo */
@@ -157,18 +357,44 @@ void SongState::initialize(const unsigned char* ptr)
else
m_tempoPtr = nullptr;
m_tempo = m_header.m_initialTempo;
m_tempo = m_header.m_initialTempo & 0x7fffffff;
m_curTick = 0;
m_songState = SongPlayState::Playing;
return true;
}
bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
{
if (!m_data)
return true;
int32_t endTick = m_parent.m_curTick + ticks;
/* Advance region if needed */
while (m_nextRegion->indexValid(m_parent.m_bigEndian))
{
uint32_t nextRegTick = (m_parent.m_bigEndian ? SBig(m_nextRegion->m_startTick) : m_nextRegion->m_startTick);
if (endTick > nextRegTick)
advanceRegion(&seq);
else
break;
}
/* Stop finished notes */
for (int i = 0; i < 128; ++i)
{
if (m_remNoteLengths[i] != INT_MIN)
{
m_remNoteLengths[i] -= ticks;
if (m_remNoteLengths[i] <= 0)
{
seq.keyOff(m_midiChan, i, 0);
m_remNoteLengths[i] = INT_MIN;
}
}
}
if (!m_data)
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
/* Update continuous pitch data */
if (m_pitchWheelData)
{
@@ -224,7 +450,7 @@ bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
m_lastModTick = nextTick;
remModTicks -= (nextTick - modTick);
modTick = nextTick;
seq.setCtrlValue(m_midiChan, 1, clamp(0, (m_lastModVal + 8192) * 128 / 16384, 127));
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127));
continue;
}
remModTicks -= (nextTick - modTick);
@@ -235,61 +461,115 @@ bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
}
}
/* Stop finished notes */
for (int i=0 ; i<128 ; ++i)
/* Loop through as many commands as we can for this time period */
if (m_parent.m_sngVersion == 1)
{
if (m_remNoteLengths[i])
/* Revision */
while (true)
{
if (m_remNoteLengths[i] <= ticks)
/* Advance wait timer if active, returning if waiting */
if (m_eventWaitCountdown)
{
seq.keyOff(m_midiChan, i, 0);
m_remNoteLengths[i] = 0;
m_eventWaitCountdown -= ticks;
ticks = 0;
if (m_eventWaitCountdown > 0)
return false;
}
/* Load next command */
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
{
/* End of channel */
m_data = nullptr;
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
}
else if (m_data[0] & 0x80 && m_data[1] & 0x80)
{
/* Control change */
uint8_t val = m_data[0] & 0x7f;
uint8_t ctrl = m_data[1] & 0x7f;
seq.setCtrlValue(m_midiChan, ctrl, val);
m_data += 2;
}
else if (m_data[0] & 0x80)
{
/* Program change */
uint8_t prog = m_data[0] & 0x7f;
seq.setChanProgram(m_midiChan, prog);
m_data += 2;
}
else
m_remNoteLengths[i] -= ticks;
{
/* Note */
uint8_t note = m_data[0] & 0x7f;
uint8_t vel = m_data[1] & 0x7f;
uint16_t length = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data + 2))
: *reinterpret_cast<const uint16_t*>(m_data + 2));
seq.keyOn(m_midiChan, note, vel);
m_remNoteLengths[note] = length;
m_data += 4;
}
/* Set next delta-time */
m_eventWaitCountdown += int32_t(DecodeTimeRLE(m_data));
}
}
/* Loop through as many commands as we can for this time period */
while (true)
else
{
/* Advance wait timer if active, returning if waiting */
if (m_waitCountdown)
/* Legacy */
while (true)
{
m_waitCountdown -= ticks;
ticks = 0;
if (m_waitCountdown > 0)
return false;
}
/* Advance wait timer if active, returning if waiting */
if (m_eventWaitCountdown)
{
m_eventWaitCountdown -= ticks;
ticks = 0;
if (m_eventWaitCountdown > 0)
return false;
}
/* Load next command */
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
{
/* End of channel */
m_data = nullptr;
return true;
}
else if (m_data[0] & 0x80)
{
/* Control change */
uint8_t val = m_data[0] & 0x7f;
uint8_t ctrl = m_data[1] & 0x7f;
seq.setCtrlValue(m_midiChan, ctrl, val);
m_data += 2;
}
else
{
/* Note */
uint8_t note = m_data[0] & 0x7f;
uint8_t vel = m_data[1] & 0x7f;
uint16_t length = SBig(*reinterpret_cast<const uint16_t*>(m_data + 2));
seq.keyOn(m_midiChan, note, vel);
m_remNoteLengths[note] = length;
/* Load next command */
if (*reinterpret_cast<const uint16_t*>(&m_data[2]) == 0xffff)
{
/* End of channel */
m_data = nullptr;
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
}
else
{
if ((m_data[2] & 0x80) != 0x80)
{
/* Note */
uint16_t length = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data))
: *reinterpret_cast<const uint16_t*>(m_data));
uint8_t note = m_data[2] & 0x7f;
uint8_t vel = m_data[3] & 0x7f;
seq.keyOn(m_midiChan, note, vel);
m_remNoteLengths[note] = length;
}
else if (m_data[2] & 0x80 && m_data[3] & 0x80)
{
/* Control change */
uint8_t val = m_data[2] & 0x7f;
uint8_t ctrl = m_data[3] & 0x7f;
seq.setCtrlValue(m_midiChan, ctrl, val);
}
else if (m_data[2] & 0x80)
{
/* Program change */
uint8_t prog = m_data[2] & 0x7f;
seq.setChanProgram(m_midiChan, prog);
}
m_data += 4;
}
/* Set next delta-time */
int32_t absTick = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data))
: *reinterpret_cast<const int32_t*>(m_data));
m_eventWaitCountdown += absTick - m_lastN64EventTick;
m_lastN64EventTick = absTick;
m_data += 4;
}
/* Set next delta-time */
m_waitCountdown += int32_t(DecodeTimeRLE(m_data));
}
return false;
@@ -317,7 +597,8 @@ bool SongState::advance(Sequencer& seq, double dt)
if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
{
TempoChange change = *m_tempoPtr;
change.swapBig();
if (m_bigEndian)
change.swapBig();
if (m_curTick + remTicks > change.m_tick)
remTicks = change.m_tick - m_curTick;
@@ -325,16 +606,17 @@ bool SongState::advance(Sequencer& seq, double dt)
if (remTicks <= 0)
{
/* Turn over tempo */
m_tempo = change.m_tempo;
m_tempo = change.m_tempo & 0x7fffffff;
seq.setTempo(m_tempo * 384 / 60);
++m_tempoPtr;
continue;
}
}
/* Advance all channels */
for (std::experimental::optional<Channel>& chan : m_channels)
if (chan)
done &= chan->advance(seq, remTicks);
/* Advance all tracks */
for (std::experimental::optional<Track>& trk : m_tracks)
if (trk)
done &= trk->advance(seq, remTicks);
m_curTick += remTicks;
@@ -348,5 +630,4 @@ bool SongState::advance(Sequencer& seq, double dt)
m_songState = SongPlayState::Stopped;
return done;
}
}

View File

@@ -6,6 +6,10 @@
#include "amuse/AudioGroupPool.hpp"
#include <string.h>
/* Squelch Win32 macro pollution >.< */
#undef SendMessage
#undef GetMessage
namespace amuse
{
@@ -22,18 +26,17 @@ void SoundMacroState::Command::swapBig()
words[1] = SBig(words[1]);
}
void SoundMacroState::Evaluator::addComponent(uint8_t midiCtrl, float scale,
Combine combine, VarType varType)
void SoundMacroState::Evaluator::addComponent(uint8_t midiCtrl, float scale, Combine combine, VarType varType)
{
m_comps.push_back({midiCtrl, scale, combine, varType});
}
float SoundMacroState::Evaluator::evaluate(const Voice& vox, const SoundMacroState& st) const
float SoundMacroState::Evaluator::evaluate(double time, const Voice& vox, const SoundMacroState& st) const
{
float value = 0.f;
/* Iterate each component */
for (auto it=m_comps.cbegin() ; it != m_comps.cend() ; ++it)
for (auto it = m_comps.cbegin(); it != m_comps.cend(); ++it)
{
const Component& comp = *it;
float thisValue = 0.f;
@@ -49,21 +52,21 @@ float SoundMacroState::Evaluator::evaluate(const Voice& vox, const SoundMacroSta
break;
case 129:
/* Aftertouch */
thisValue = vox.getAftertouch();
thisValue = vox.getAftertouch() * (2.f / 127.f);
break;
case 130:
/* LFO1 */
if (vox.m_lfoPeriods[0])
thisValue = std::sin(vox.m_voiceTime / vox.m_lfoPeriods[0] * 2.f * M_PIF);
thisValue = std::sin(time / vox.m_lfoPeriods[0] * 2.f * M_PIF);
break;
case 131:
/* LFO2 */
if (vox.m_lfoPeriods[1])
thisValue = std::sin(vox.m_voiceTime / vox.m_lfoPeriods[1] * 2.f * M_PIF);
thisValue = std::sin(time / vox.m_lfoPeriods[1] * 2.f * M_PIF);
break;
case 132:
/* Surround panning */
thisValue = vox.m_curSpan * 64.f + 64.f;
thisValue = vox.m_curSpan;
break;
case 133:
/* Macro-starting key */
@@ -75,15 +78,18 @@ float SoundMacroState::Evaluator::evaluate(const Voice& vox, const SoundMacroSta
break;
case 135:
/* Time since macro-start (ms) */
thisValue = st.m_execTime * 1000.f;
thisValue = clamp(0.f, float(st.m_execTime * 1000.f), 16383.f);
break;
default:
thisValue = vox.getCtrlValue(comp.m_midiCtrl);
if (comp.m_midiCtrl == 10) /* Centered pan computation */
thisValue = vox.getCtrlValue(comp.m_midiCtrl) * (2.f / 127.f) - 1.f;
else
thisValue = vox.getCtrlValue(comp.m_midiCtrl) * (2.f / 127.f);
break;
}
}
else if (comp.m_varType == VarType::Var)
thisValue = st.m_variables[std::max(0, std::min(255, int(comp.m_midiCtrl)))];
thisValue = st.m_variables[clamp(0, int(comp.m_midiCtrl), 255)];
/* Apply scale */
thisValue *= comp.m_scale;
@@ -116,8 +122,8 @@ void SoundMacroState::initialize(const unsigned char* ptr, int step, bool swapDa
initialize(ptr, step, 1000.f, 0, 0, 0, swapData);
}
void SoundMacroState::initialize(const unsigned char* ptr, int step, double ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool swapData)
void SoundMacroState::initialize(const unsigned char* ptr, int step, double ticksPerSec, uint8_t midiKey,
uint8_t midiVel, uint8_t midiMod, bool swapData)
{
m_ticksPerSec = ticksPerSec;
m_initKey = midiKey;
@@ -135,7 +141,8 @@ void SoundMacroState::initialize(const unsigned char* ptr, int step, double tick
m_loopCountdown = -1;
m_lastPlayMacroVid = -1;
m_useAdsrControllers = false;
m_portamentoMode = 0;
m_portamentoMode = 2;
m_portamentoTime = 0.5f;
m_header = *reinterpret_cast<const Header*>(ptr);
if (swapData)
m_header.swapBig();
@@ -196,8 +203,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec, m_initKey, m_initVel, m_initMod);
}
break;
@@ -214,8 +220,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec, m_initKey, m_initVel, m_initMod);
}
break;
@@ -298,8 +303,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec, m_initKey, m_initVel, m_initMod);
break;
}
@@ -343,8 +347,8 @@ bool SoundMacroState::advance(Voice& vox, double dt)
int8_t addNote = cmd.m_data[0];
ObjectId macroId = *reinterpret_cast<ObjectId*>(&cmd.m_data[1]);
int16_t macroStep = *reinterpret_cast<int16_t*>(&cmd.m_data[3]);
//int8_t priority = cmd.m_data[5];
//int8_t maxVoices = cmd.m_data[6];
// int8_t priority = cmd.m_data[5];
// int8_t maxVoices = cmd.m_data[6];
std::shared_ptr<Voice> sibVox = vox.startChildMacro(addNote, macroId, macroStep);
if (sibVox)
@@ -387,8 +391,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec, m_initKey, m_initVel, m_initMod);
}
break;
@@ -419,7 +422,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
bool orgVel = cmd.m_data[4];
int32_t eval = int32_t(orgVel ? m_initVel : m_curVel) * scale / 127 + add;
eval = std::max(0, std::min(127, eval));
eval = clamp(0, eval, 127);
if (curve != 0)
{
@@ -455,7 +458,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
double secTime = fadeTime / q;
int32_t eval = int32_t(m_curVel) * scale / 127 + add;
eval = std::max(0, std::min(127, eval));
eval = clamp(0, eval, 127);
const Curve* curveData;
if (curve != 0)
@@ -510,8 +513,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec, m_initKey, m_initVel, m_initMod);
}
break;
@@ -528,7 +530,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
float secTime = fadeTime / q;
int32_t eval = int32_t(m_curVel) * scale / 127 + add;
eval = std::max(0, std::min(127, eval));
eval = clamp(0, eval, 127);
const Curve* curveData;
if (curve != 0)
@@ -555,6 +557,15 @@ bool SoundMacroState::advance(Voice& vox, double dt)
m_midiDecay = cmd.m_data[1];
m_midiSustain = cmd.m_data[2];
m_midiRelease = cmd.m_data[3];
/* Bootstrap ADSR defaults here */
if (!vox.getCtrlValue(m_midiSustain))
{
vox.setCtrlValue(m_midiAttack, 10);
vox.setCtrlValue(m_midiSustain, 127);
vox.setCtrlValue(m_midiRelease, 10);
}
break;
}
case Op::RndNote:
@@ -767,8 +778,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
if (macroId == m_header.m_macroId)
m_pc.push_back({m_pc.back().first, macroStep});
else
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod, true);
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec, m_initKey, m_initVel, m_initMod, true);
m_header = *reinterpret_cast<const Header*>(m_pc.back().first);
if (vox.getAudioGroup().getDataFormat() != DataFormat::PC)
@@ -797,7 +807,8 @@ bool SoundMacroState::advance(Voice& vox, double dt)
vox.m_messageTrap.macroId = macroId;
vox.m_messageTrap.macroStep = macroStep;
break;
default: break;
default:
break;
}
break;
@@ -820,7 +831,8 @@ bool SoundMacroState::advance(Voice& vox, double dt)
vox.m_messageTrap.macroId = 0xffff;
vox.m_messageTrap.macroStep = -1;
break;
default: break;
default:
break;
}
break;
@@ -1230,14 +1242,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
return false;
}
void SoundMacroState::keyoffNotify(Voice& vox)
{
m_keyoff = true;
}
void SoundMacroState::sampleEndNotify(Voice& vox)
{
m_sampleEnd = true;
}
void SoundMacroState::keyoffNotify(Voice& vox) { m_keyoff = true; }
void SoundMacroState::sampleEndNotify(Voice& vox) { m_sampleEnd = true; }
}

42
lib/Studio.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "amuse/Studio.hpp"
#include "amuse/Engine.hpp"
namespace amuse
{
#ifndef NDEBUG
bool Studio::_cyclicCheck(Studio* leaf)
{
for (auto it = m_studiosOut.begin(); it != m_studiosOut.end();)
{
if (leaf == it->m_targetStudio.get() || it->m_targetStudio->_cyclicCheck(leaf))
return true;
++it;
}
return false;
}
#endif
Studio::Studio(Engine& engine, bool mainOut) : m_engine(engine), m_master(engine), m_auxA(engine), m_auxB(engine)
{
if (mainOut && engine.m_defaultStudioReady)
addStudioSend(engine.getDefaultStudio(), 1.f, 1.f, 1.f);
}
void Studio::addStudioSend(std::weak_ptr<Studio> studio, float dry, float auxA, float auxB)
{
m_studiosOut.emplace_back(studio, dry, auxA, auxB);
#ifndef NDEBUG
/* Cyclic check */
assert(!_cyclicCheck(this));
#endif
}
void Studio::resetOutputSampleRate(double sampleRate)
{
m_master.resetOutputSampleRate(sampleRate);
m_auxA.resetOutputSampleRate(sampleRate);
m_auxB.resetOutputSampleRate(sampleRate);
}
}

View File

@@ -3,14 +3,7 @@
namespace amuse
{
void Submix::_destroy()
{
m_destroyed = true;
}
Submix::Submix(Engine& engine, Submix* smx)
: m_root(engine), m_submix(smx)
{}
Submix::Submix(Engine& engine) : m_root(engine) {}
EffectChorus& Submix::makeChorus(uint32_t baseDelay, uint32_t variation, uint32_t period)
{
@@ -22,14 +15,13 @@ EffectDelay& Submix::makeDelay(uint32_t initDelay, uint32_t initFeedback, uint32
return makeEffect<EffectDelay>(initDelay, initFeedback, initOutput);
}
EffectReverbStd& Submix::makeReverbStd(float coloration, float mix, float time,
float damping, float preDelay)
EffectReverbStd& Submix::makeReverbStd(float coloration, float mix, float time, float damping, float preDelay)
{
return makeEffect<EffectReverbStd>(coloration, mix, time, damping, preDelay);
}
EffectReverbHi& Submix::makeReverbHi(float coloration, float mix, float time,
float damping, float preDelay, float crosstalk)
EffectReverbHi& Submix::makeReverbHi(float coloration, float mix, float time, float damping, float preDelay,
float crosstalk)
{
return makeEffect<EffectReverbHi>(coloration, mix, time, damping, preDelay, crosstalk);
}
@@ -52,4 +44,9 @@ void Submix::applyEffect(float* audio, size_t frameCount, const ChannelMap& chan
((EffectBase<float>&)*effect).applyEffect(audio, frameCount, chanMap);
}
void Submix::resetOutputSampleRate(double sampleRate)
{
for (const std::unique_ptr<EffectBaseTypeless>& effect : m_effectStack)
effect->resetOutputSampleRate(sampleRate);
}
}

View File

@@ -6,16 +6,11 @@
namespace amuse
{
static float Dot(const Vector3f& a, const Vector3f& b)
{
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
static float Dot(const Vector3f& a, const Vector3f& b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; }
static float Length(const Vector3f& a)
{
if (std::fabs(a[0]) <= FLT_EPSILON &&
std::fabs(a[1]) <= FLT_EPSILON &&
std::fabs(a[2]) <= FLT_EPSILON)
if (std::fabs(a[0]) <= FLT_EPSILON && std::fabs(a[1]) <= FLT_EPSILON && std::fabs(a[2]) <= FLT_EPSILON)
return 0.f;
return std::sqrt(Dot(a, a));
}
@@ -42,6 +37,7 @@ static void Cross(Vector3f& out, const Vector3f& a, const Vector3f& b)
class SimpleMatrix
{
Vector3f m_mat[3];
public:
SimpleMatrix(const Vector3f& dir, const Vector3f& up)
{
@@ -86,46 +82,32 @@ struct ReferenceVector
}
};
static const ReferenceVector StereoVectors[8] =
{
{-0.80901f, 0.58778f, 0.f, 0.3f},
{ 0.80901f, 0.58778f, 0.f, 0.3f},
static const ReferenceVector StereoVectors[8] = {
{-0.80901f, 0.58778f, 0.f, 0.3f}, {0.80901f, 0.58778f, 0.f, 0.3f},
};
static const ReferenceVector QuadVectors[8] =
{
{-0.70710f, 0.70710f, 0.f, 0.1f},
{ 0.70710f, 0.70710f, 0.f, 0.1f},
static const ReferenceVector QuadVectors[8] = {
{-0.70710f, 0.70710f, 0.f, 0.1f},
{0.70710f, 0.70710f, 0.f, 0.1f},
{-0.70710f, -0.70710f, 0.f, 0.1f},
{ 0.70710f, -0.70710f, 0.f, 0.1f},
{0.70710f, -0.70710f, 0.f, 0.1f},
};
static const ReferenceVector Sur51Vectors[8] =
{
{-0.70710f, 0.70710f, 0.f, 0.1f},
{ 0.70710f, 0.70710f, 0.f, 0.1f},
{-0.70710f, -0.70710f, 0.f, 0.1f},
{ 0.70710f, -0.70710f, 0.f, 0.1f},
{ 0.0f, 1.0f, 0.f, 0.1f},
{ 0.0f, 1.0f, 0.f, 1.0f},
static const ReferenceVector Sur51Vectors[8] = {
{-0.70710f, 0.70710f, 0.f, 0.1f}, {0.70710f, 0.70710f, 0.f, 0.1f}, {-0.70710f, -0.70710f, 0.f, 0.1f},
{0.70710f, -0.70710f, 0.f, 0.1f}, {0.0f, 1.0f, 0.f, 0.1f}, {0.0f, 1.0f, 0.f, 1.0f},
};
static const ReferenceVector Sur71Vectors[8] =
{
{-0.70710f, 0.70710f, 0.f, 0.1f},
{ 0.70710f, 0.70710f, 0.f, 0.1f},
{-0.70710f, -0.70710f, 0.f, 0.1f},
{ 0.70710f, -0.70710f, 0.f, 0.1f},
{ 0.0f, 1.0f, 0.f, 0.1f},
{ 0.0f, 1.0f, 0.f, 1.0f},
{-1.f, 0.0f, 0.f, 0.1f},
{ 1.f, 0.0f, 0.f, 0.1f},
static const ReferenceVector Sur71Vectors[8] = {
{-0.70710f, 0.70710f, 0.f, 0.1f}, {0.70710f, 0.70710f, 0.f, 0.1f}, {-0.70710f, -0.70710f, 0.f, 0.1f},
{0.70710f, -0.70710f, 0.f, 0.1f}, {0.0f, 1.0f, 0.f, 0.1f}, {0.0f, 1.0f, 0.f, 1.0f},
{-1.f, 0.0f, 0.f, 0.1f}, {1.f, 0.0f, 0.f, 0.1f},
};
void SurroundProfiles::SetupRefs(float matOut[8], const ChannelMap& map,
const Vector3f& listenEmit, const ReferenceVector refs[])
void SurroundProfiles::SetupRefs(float matOut[8], const ChannelMap& map, const Vector3f& listenEmit,
const ReferenceVector refs[])
{
for (unsigned i=0 ; i<map.m_channelCount && i<8 ; ++i)
for (unsigned i = 0; i < map.m_channelCount && i < 8; ++i)
{
matOut[i] = 0.f;
if (map.m_channels[i] == AudioChannel::Unknown)
@@ -137,9 +119,8 @@ void SurroundProfiles::SetupRefs(float matOut[8], const ChannelMap& map,
}
}
void SurroundProfiles::SetupMatrix(float matOut[8], const ChannelMap& map, AudioChannelSet set,
const Vector3f& emitPos, const Vector3f& listenPos,
const Vector3f& listenHeading, const Vector3f& listenUp)
void SurroundProfiles::SetupMatrix(float matOut[8], const ChannelMap& map, AudioChannelSet set, const Vector3f& emitPos,
const Vector3f& listenPos, const Vector3f& listenHeading, const Vector3f& listenUp)
{
Vector3f listenDelta;
listenDelta[0] = emitPos[0] - listenPos[0];
@@ -171,5 +152,4 @@ void SurroundProfiles::SetupMatrix(float matOut[8], const ChannelMap& map, Audio
break;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,13 @@
namespace amuse
{
/* clang-format off */
/*
/* # [-10,0] dB RMS volume scale generator
* import math
* print(" %.10ff," % 0.0)
* for i in range(65536):
* print(" %.10ff," % (math.sqrt(10 ** (i/65536 - 1)) - 0.31622))
*/
extern "C" const float VolumeLUT[] =
const float VolumeLUT[] =
{
0.f,
0.0000077660f,
@@ -65548,5 +65547,4 @@ extern "C" const float VolumeLUT[] =
0.6837448660f,
0.6837624328f
};
}
/* clang-format on */

10
standalone_bootstrap.bat Normal file
View File

@@ -0,0 +1,10 @@
@echo off
git clone https://github.com/AxioDL/boo.git
pushd boo
git submodule update --recursive --init
popd
git clone https://github.com/libAthena/athena.git
pushd athena
git submodule update --recursive --init
popd

11
standalone_bootstrap.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
git clone https://github.com/AxioDL/boo.git
pushd boo
git submodule update --recursive --init
popd
git clone https://github.com/libAthena/athena.git
pushd athena
git submodule update --recursive --init
popd