This commit is contained in:
Jack Andersen 2016-06-08 15:25:42 -10:00
commit ad784966e9
26 changed files with 2040 additions and 687 deletions

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,158 @@
#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,23 +16,16 @@
#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) {}
@ -42,13 +35,21 @@ 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*)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

@ -31,6 +31,7 @@ public:
const unsigned char* getSampleData(uint32_t offset) const;
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;}
};

View File

@ -12,35 +12,70 @@ 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,
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_pool(pool), m_sdir(sdir), m_samp(samp),
: 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) {}
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),
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, unsigned char* pool,
unsigned char* sdir, unsigned char* samp, bool absOffs, N64DataTag)
: m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp),
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, unsigned char* pool,
unsigned char* sdir, unsigned char* samp, bool absOffs, PCDataTag)
: m_proj(proj), m_pool(pool), m_sdir(sdir), m_samp(samp),
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
{
@ -64,6 +99,8 @@ public:
IntrusiveAudioGroupData(IntrusiveAudioGroupData&& other);
IntrusiveAudioGroupData& operator=(IntrusiveAudioGroupData&& other);
void dangleOwnership() {m_owns = false;}
};
}

View File

@ -51,6 +51,8 @@ public:
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;}
};
}

View File

@ -10,7 +10,6 @@
#include "IBackendVoiceAllocator.hpp"
#include <mutex>
#include <list>
#include <chrono>
namespace amuse
{
@ -76,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);

View File

@ -37,6 +37,7 @@ public:
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, IntrusiveAudioGroupData>> LoadContainer(const char* path, Type& typeOut);
static std::vector<std::pair<std::string, SongData>> LoadSongs(const char* path);
};

View File

@ -28,7 +28,7 @@ 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 */
const SongGroupIndex::MIDISetup* m_midiSetup = nullptr; /**< Selected MIDI setup (may be null) */
Submix* m_submix = nullptr; /**< Submix this sequencer outputs to (or NULL for the main output mix) */
const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */
@ -44,7 +44,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);
@ -79,6 +79,7 @@ class Sequencer : public Entity
void _bringOutYourDead();
void _destroy();
public:
~Sequencer();
Sequencer(Engine& engine, const AudioGroup& group, int groupId,

View File

@ -15,7 +15,9 @@ IntrusiveAudioGroupData::~IntrusiveAudioGroupData()
}
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;

View File

@ -145,7 +145,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 +161,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;

View File

@ -2,6 +2,7 @@
#include "amuse/Voice.hpp"
#include "amuse/Submix.hpp"
#include "amuse/Engine.hpp"
#include <syslog.h>
namespace amuse
{
@ -119,8 +120,8 @@ std::string BooBackendMIDIReader::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)
@ -130,40 +131,46 @@ BooBackendMIDIReader::BooBackendMIDIReader(Engine& engine, const char* name)
{
m_midiIn = voxAlloc.m_booEngine.newRealMIDIIn(dev.first.c_str(),
std::bind(&BooBackendMIDIReader::_MIDIReceive, this,
std::placeholders::_1));
std::placeholders::_1, std::placeholders::_2));
if (m_midiIn)
return;
}
m_midiIn = voxAlloc.m_booEngine.newVirtualMIDIIn(std::bind(&BooBackendMIDIReader::_MIDIReceive, this,
std::placeholders::_1));
std::placeholders::_1, std::placeholders::_2));
}
else
m_midiIn = voxAlloc.m_booEngine.newRealMIDIIn(name,
std::bind(&BooBackendMIDIReader::_MIDIReceive, this,
std::placeholders::_1));
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;
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;
}
@ -174,6 +181,15 @@ void BooBackendMIDIReader::pumpReader(double dt)
/* Dispatch buffers */
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);
}
@ -183,12 +199,22 @@ 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*/)
@ -308,7 +334,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;

View File

@ -49,17 +49,17 @@ const char* ContainerRegistry::TypeToName(Type tp)
case Type::MetroidPrime2:
return "Metroid Prime 2 (GCN)";
case Type::RogueSquadronPC:
return "Star Wars: Rogue Squadron (PC)";
return "Star Wars - Rogue Squadron (PC)";
case Type::RogueSquadronN64:
return "Star Wars: Rogue Squadron (N64)";
return "Star Wars - Rogue Squadron (N64)";
case Type::BattleForNabooPC:
return "Star Wars Episode I: Battle for Naboo (PC)";
return "Star Wars Episode I - Battle for Naboo (PC)";
case Type::BattleForNabooN64:
return "Star Wars Episode I: Battle for Naboo (N64)";
return "Star Wars Episode I - Battle for Naboo (N64)";
case Type::RogueSquadron2:
return "Star Wars: Rogue Squadron 2 (GCN)";
return "Star Wars - Rogue Squadron 2 (GCN)";
case Type::RogueSquadron3:
return "Star Wars: Rogue Squadron 3 (GCN)";
return "Star Wars - Rogue Squadron 3 (GCN)";
}
}
@ -119,7 +119,8 @@ static bool IsSongExtension(const char* path, const char*& dotOut)
static bool ValidateMP1(FILE* fp)
{
FileLength(fp);
if (FileLength(fp) > 40 * 1024 * 1024)
return false;
uint32_t magic;
fread(&magic, 1, 4, fp);
@ -228,29 +229,34 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadMP1(FILE
ReadString(fp);
std::string name = ReadString(fp);
uint32_t len;
fread(&len, 1, 4, fp);
len = SBig(len);
std::unique_ptr<uint8_t[]> pool(new uint8_t[len]);
fread(pool.get(), 1, len, fp);
uint32_t poolLen;
fread(&poolLen, 1, 4, fp);
poolLen = SBig(poolLen);
std::unique_ptr<uint8_t[]> pool(new uint8_t[poolLen]);
fread(pool.get(), 1, poolLen, fp);
fread(&len, 1, 4, fp);
len = SBig(len);
std::unique_ptr<uint8_t[]> proj(new uint8_t[len]);
fread(proj.get(), 1, len, fp);
uint32_t projLen;
fread(&projLen, 1, 4, fp);
projLen = SBig(projLen);
std::unique_ptr<uint8_t[]> proj(new uint8_t[projLen]);
fread(proj.get(), 1, projLen, fp);
fread(&len, 1, 4, fp);
len = SBig(len);
std::unique_ptr<uint8_t[]> samp(new uint8_t[len]);
fread(samp.get(), 1, len, fp);
uint32_t sampLen;
fread(&sampLen, 1, 4, fp);
sampLen = SBig(sampLen);
std::unique_ptr<uint8_t[]> samp(new uint8_t[sampLen]);
fread(samp.get(), 1, sampLen, fp);
fread(&len, 1, 4, fp);
len = SBig(len);
std::unique_ptr<uint8_t[]> sdir(new uint8_t[len]);
fread(sdir.get(), 1, len, fp);
uint32_t sdirLen;
fread(&sdirLen, 1, 4, fp);
sdirLen = SBig(sdirLen);
std::unique_ptr<uint8_t[]> sdir(new uint8_t[sdirLen]);
fread(sdir.get(), 1, sdirLen, fp);
ret.emplace_back(std::move(name), IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(), GCNDataTag{}});
ret.emplace_back(std::move(name), IntrusiveAudioGroupData{proj.release(), projLen,
pool.release(), poolLen,
sdir.release(), sdirLen,
samp.release(), sampLen, GCNDataTag{}});
}
}
FSeek(fp, origPos, SEEK_SET);
@ -263,7 +269,8 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadMP1(FILE
static bool ValidateMP1Songs(FILE* fp)
{
FileLength(fp);
if (FileLength(fp) > 40 * 1024 * 1024)
return false;
uint32_t magic;
fread(&magic, 1, 4, fp);
@ -404,7 +411,8 @@ static std::vector<std::pair<std::string, ContainerRegistry::SongData>> LoadMP1S
static bool ValidateMP2(FILE* fp)
{
FileLength(fp);
if (FileLength(fp) > 40 * 1024 * 1024)
return false;
uint32_t magic;
fread(&magic, 1, 4, fp);
@ -528,21 +536,26 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadMP2(FILE
uint32_t sampSz;
fread(&sampSz, 1, 4, fp);
sampSz = SBig(sampSz);
if (projSz && poolSz && sdirSz && sampSz)
{
std::unique_ptr<uint8_t[]> pool(new uint8_t[poolSz]);
fread(pool.get(), 1, poolSz, fp);
std::unique_ptr<uint8_t[]> pool(new uint8_t[poolSz]);
fread(pool.get(), 1, poolSz, fp);
std::unique_ptr<uint8_t[]> proj(new uint8_t[projSz]);
fread(proj.get(), 1, projSz, fp);
std::unique_ptr<uint8_t[]> proj(new uint8_t[projSz]);
fread(proj.get(), 1, projSz, fp);
std::unique_ptr<uint8_t[]> sdir(new uint8_t[sdirSz]);
fread(sdir.get(), 1, sdirSz, fp);
std::unique_ptr<uint8_t[]> sdir(new uint8_t[sdirSz]);
fread(sdir.get(), 1, sdirSz, fp);
std::unique_ptr<uint8_t[]> samp(new uint8_t[sampSz]);
fread(samp.get(), 1, sampSz, fp);
std::unique_ptr<uint8_t[]> samp(new uint8_t[sampSz]);
fread(samp.get(), 1, sampSz, fp);
ret.emplace_back(std::move(name), IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(), GCNDataTag{}});
ret.emplace_back(std::move(name), IntrusiveAudioGroupData{proj.release(), projSz,
pool.release(), poolSz,
sdir.release(), sdirSz,
samp.release(), sampSz, GCNDataTag{}});
}
}
}
FSeek(fp, origPos, SEEK_SET);
@ -589,6 +602,8 @@ static void SwapN64Rom32(void* data, size_t size)
static bool ValidateRS1PC(FILE* fp)
{
size_t endPos = FileLength(fp);
if (endPos > 100 * 1024 * 1024)
return false;
uint32_t fstOff;
uint32_t fstSz;
@ -640,41 +655,49 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS1PC(FI
fread(entries.get(), fstSz, 1, fp);
std::unique_ptr<uint8_t[]> proj;
size_t projSz = 0;
std::unique_ptr<uint8_t[]> pool;
size_t poolSz = 0;
std::unique_ptr<uint8_t[]> sdir;
size_t sdirSz = 0;
std::unique_ptr<uint8_t[]> samp;
size_t sampSz = 0;
for (uint32_t i=0 ; i<elemCount ; ++i)
{
RS1FSTEntry& entry = entries[i];
if (!strncmp("proj_SND", entry.name, 16))
{
projSz = entry.decompSz;
proj.reset(new uint8_t[entry.decompSz]);
FSeek(fp, entry.offset, SEEK_SET);
fread(proj.get(), 1, entry.decompSz, fp);
}
else if (!strncmp("pool_SND", entry.name, 16))
{
poolSz = entry.decompSz;
pool.reset(new uint8_t[entry.decompSz]);
FSeek(fp, entry.offset, SEEK_SET);
fread(pool.get(), 1, entry.decompSz, fp);
}
else if (!strncmp("sdir_SND", entry.name, 16))
{
sdirSz = entry.decompSz;
sdir.reset(new uint8_t[entry.decompSz]);
FSeek(fp, entry.offset, SEEK_SET);
fread(sdir.get(), 1, entry.decompSz, fp);
}
else if (!strncmp("samp_SND", entry.name, 16))
{
sampSz = entry.decompSz;
samp.reset(new uint8_t[entry.decompSz]);
FSeek(fp, entry.offset, SEEK_SET);
fread(samp.get(), 1, entry.decompSz, fp);
}
}
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(),
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projSz, pool.release(), poolSz,
sdir.release(), sdirSz, samp.release(), sampSz,
false, PCDataTag{}});
}
}
@ -764,9 +787,13 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS1N64(F
const RS1FSTEntry* lastEnt = reinterpret_cast<const RS1FSTEntry*>(dataSeg + fstEnd);
std::unique_ptr<uint8_t[]> proj;
size_t projSz = 0;
std::unique_ptr<uint8_t[]> pool;
size_t poolSz = 0;
std::unique_ptr<uint8_t[]> sdir;
size_t sdirSz = 0;
std::unique_ptr<uint8_t[]> samp;
size_t sampSz = 0;
for (; entry != lastEnt ; ++entry)
{
@ -778,7 +805,7 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS1N64(F
if (ent.compSz == 0xffffffff)
{
proj.reset(new uint8_t[ent.decompSz]);
memcpy(proj.get(), dataSeg + ent.offset, ent.decompSz);
memmove(proj.get(), dataSeg + ent.offset, ent.decompSz);
}
else
{
@ -786,13 +813,14 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS1N64(F
uLongf outSz = ent.decompSz;
uncompress(proj.get(), &outSz, dataSeg + ent.offset, ent.compSz);
}
projSz = ent.decompSz;
}
else if (!strncmp("pool_SND", ent.name, 16))
{
if (ent.compSz == 0xffffffff)
{
pool.reset(new uint8_t[ent.decompSz]);
memcpy(pool.get(), dataSeg + ent.offset, ent.decompSz);
memmove(pool.get(), dataSeg + ent.offset, ent.decompSz);
}
else
{
@ -800,13 +828,14 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS1N64(F
uLongf outSz = ent.decompSz;
uncompress(pool.get(), &outSz, dataSeg + ent.offset, ent.compSz);
}
poolSz = ent.decompSz;
}
else if (!strncmp("sdir_SND", ent.name, 16))
{
if (ent.compSz == 0xffffffff)
{
sdir.reset(new uint8_t[ent.decompSz]);
memcpy(sdir.get(), dataSeg + ent.offset, ent.decompSz);
memmove(sdir.get(), dataSeg + ent.offset, ent.decompSz);
}
else
{
@ -814,13 +843,14 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS1N64(F
uLongf outSz = ent.decompSz;
uncompress(sdir.get(), &outSz, dataSeg + ent.offset, ent.compSz);
}
sdirSz = ent.decompSz;
}
else if (!strncmp("samp_SND", ent.name, 16))
{
if (ent.compSz == 0xffffffff)
{
samp.reset(new uint8_t[ent.decompSz]);
memcpy(samp.get(), dataSeg + ent.offset, ent.decompSz);
memmove(samp.get(), dataSeg + ent.offset, ent.decompSz);
}
else
{
@ -828,11 +858,12 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS1N64(F
uLongf outSz = ent.decompSz;
uncompress(samp.get(), &outSz, dataSeg + ent.offset, ent.compSz);
}
sampSz = ent.decompSz;
}
}
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(),
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projSz, pool.release(), poolSz,
sdir.release(), sdirSz, samp.release(), sampSz,
false, N64DataTag{}});
}
@ -842,7 +873,9 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS1N64(F
static bool ValidateBFNPC(FILE* fp)
{
size_t endPos = FileLength(fp);
if (endPos > 100 * 1024 * 1024)
return false;
uint32_t fstOff;
uint32_t fstSz;
if (fread(&fstOff, 1, 4, fp) == 4 && fread(&fstSz, 1, 4, fp) == 4)
@ -893,9 +926,13 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadBFNPC(FI
fread(entries.get(), fstSz, 1, fp);
std::unique_ptr<uint8_t[]> proj;
size_t projSz = 0;
std::unique_ptr<uint8_t[]> pool;
size_t poolSz = 0;
std::unique_ptr<uint8_t[]> sdir;
size_t sdirSz = 0;
std::unique_ptr<uint8_t[]> samp;
size_t sampSz = 0;
for (uint32_t i=0 ; i<elemCount ; ++i)
{
@ -905,29 +942,33 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadBFNPC(FI
proj.reset(new uint8_t[entry.decompSz]);
FSeek(fp, entry.offset, SEEK_SET);
fread(proj.get(), 1, entry.decompSz, fp);
projSz = entry.decompSz;
}
else if (!strncmp("pool", entry.name, 16))
{
pool.reset(new uint8_t[entry.decompSz]);
FSeek(fp, entry.offset, SEEK_SET);
fread(pool.get(), 1, entry.decompSz, fp);
poolSz = entry.decompSz;
}
else if (!strncmp("sdir", entry.name, 16))
{
sdir.reset(new uint8_t[entry.decompSz]);
FSeek(fp, entry.offset, SEEK_SET);
fread(sdir.get(), 1, entry.decompSz, fp);
sdirSz = entry.decompSz;
}
else if (!strncmp("samp", entry.name, 16))
{
samp.reset(new uint8_t[entry.decompSz]);
FSeek(fp, entry.offset, SEEK_SET);
fread(samp.get(), 1, entry.decompSz, fp);
sampSz = entry.decompSz;
}
}
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(),
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projSz, pool.release(), poolSz,
sdir.release(), sdirSz, samp.release(), sampSz,
true, PCDataTag{}});
}
}
@ -1017,9 +1058,13 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadBFNN64(F
const RS1FSTEntry* lastEnt = reinterpret_cast<const RS1FSTEntry*>(dataSeg + fstEnd);
std::unique_ptr<uint8_t[]> proj;
size_t projSz = 0;
std::unique_ptr<uint8_t[]> pool;
size_t poolSz = 0;
std::unique_ptr<uint8_t[]> sdir;
size_t sdirSz = 0;
std::unique_ptr<uint8_t[]> samp;
size_t sampSz = 0;
for (; entry != lastEnt ; ++entry)
{
@ -1031,7 +1076,7 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadBFNN64(F
if (ent.compSz == 0xffffffff)
{
proj.reset(new uint8_t[ent.decompSz]);
memcpy(proj.get(), dataSeg + ent.offset, ent.decompSz);
memmove(proj.get(), dataSeg + ent.offset, ent.decompSz);
}
else
{
@ -1039,13 +1084,14 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadBFNN64(F
uLongf outSz = ent.decompSz;
uncompress(proj.get(), &outSz, dataSeg + ent.offset, ent.compSz);
}
projSz = ent.decompSz;
}
else if (!strncmp("pool", ent.name, 16))
{
if (ent.compSz == 0xffffffff)
{
pool.reset(new uint8_t[ent.decompSz]);
memcpy(pool.get(), dataSeg + ent.offset, ent.decompSz);
memmove(pool.get(), dataSeg + ent.offset, ent.decompSz);
}
else
{
@ -1053,13 +1099,14 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadBFNN64(F
uLongf outSz = ent.decompSz;
uncompress(pool.get(), &outSz, dataSeg + ent.offset, ent.compSz);
}
poolSz = ent.decompSz;
}
else if (!strncmp("sdir", ent.name, 16))
{
if (ent.compSz == 0xffffffff)
{
sdir.reset(new uint8_t[ent.decompSz]);
memcpy(sdir.get(), dataSeg + ent.offset, ent.decompSz);
memmove(sdir.get(), dataSeg + ent.offset, ent.decompSz);
}
else
{
@ -1067,13 +1114,14 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadBFNN64(F
uLongf outSz = ent.decompSz;
uncompress(sdir.get(), &outSz, dataSeg + ent.offset, ent.compSz);
}
sdirSz = ent.decompSz;
}
else if (!strncmp("samp", ent.name, 16))
{
if (ent.compSz == 0xffffffff)
{
samp.reset(new uint8_t[ent.decompSz]);
memcpy(samp.get(), dataSeg + ent.offset, ent.decompSz);
memmove(samp.get(), dataSeg + ent.offset, ent.decompSz);
}
else
{
@ -1081,11 +1129,12 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadBFNN64(F
uLongf outSz = ent.decompSz;
uncompress(samp.get(), &outSz, dataSeg + ent.offset, ent.compSz);
}
sampSz = ent.decompSz;
}
}
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(),
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projSz, pool.release(), poolSz,
sdir.release(), sdirSz, samp.release(), sampSz,
true, N64DataTag{}});
}
@ -1164,7 +1213,9 @@ struct RS23SONHead
static bool ValidateRS2(FILE* fp)
{
size_t endPos = FileLength(fp);
if (endPos > 600 * 1024 * 1024)
return false;
uint64_t fstOff;
fread(&fstOff, 1, 8, fp);
fstOff = SBig(fstOff);
@ -1228,21 +1279,24 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS2(FILE
head.swapBig();
std::unique_ptr<uint8_t[]> pool(new uint8_t[head.poolLen]);
memcpy(pool.get(), audData.get() + head.poolOff, head.poolLen);
memmove(pool.get(), audData.get() + head.poolOff, head.poolLen);
std::unique_ptr<uint8_t[]> proj(new uint8_t[head.projLen]);
memcpy(proj.get(), audData.get() + head.projOff, head.projLen);
memmove(proj.get(), audData.get() + head.projOff, head.projLen);
std::unique_ptr<uint8_t[]> sdir(new uint8_t[head.sdirLen]);
memcpy(sdir.get(), audData.get() + head.sdirOff, head.sdirLen);
memmove(sdir.get(), audData.get() + head.sdirOff, head.sdirLen);
std::unique_ptr<uint8_t[]> samp(new uint8_t[head.sampLen]);
memcpy(samp.get(), audData.get() + head.sampOff, head.sampLen);
memmove(samp.get(), audData.get() + head.sampOff, head.sampLen);
char name[128];
snprintf(name, 128, "GroupFile%u", j);
ret.emplace_back(name, IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(), GCNDataTag{}});
if (head.projLen && head.poolLen && head.sdirLen && head.sampLen)
{
char name[128];
snprintf(name, 128, "GroupFile%02u", j);
ret.emplace_back(name, IntrusiveAudioGroupData{proj.release(), head.projLen, pool.release(), head.poolLen,
sdir.release(), head.sdirLen, samp.release(), head.sampLen, GCNDataTag{}});
}
}
break;
@ -1299,9 +1353,9 @@ static std::vector<std::pair<std::string, ContainerRegistry::SongData>> LoadRS2S
sonHead.swapBig();
char name[128];
snprintf(name, 128, "GroupFile%u-%u", j, s);
snprintf(name, 128, "GroupFile%02u-%u", j, s);
std::unique_ptr<uint8_t[]> song(new uint8_t[sonHead.length]);
memcpy(song.get(), audData.get() + sonHead.offset, sonHead.length);
memmove(song.get(), audData.get() + sonHead.offset, sonHead.length);
ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), sonHead.length,
sonHead.groupId, sonHead.setupId));
}
@ -1334,7 +1388,9 @@ struct RS3FSTEntry
static bool ValidateRS3(FILE* fp)
{
size_t endPos = FileLength(fp);
if (endPos > 600 * 1024 * 1024)
return false;
uint64_t fstOff;
fread(&fstOff, 1, 8, fp);
fstOff = SBig(fstOff);
@ -1396,21 +1452,24 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS3(FILE
head.swapBig();
std::unique_ptr<uint8_t[]> pool(new uint8_t[head.poolLen]);
memcpy(pool.get(), audData.get() + head.poolOff, head.poolLen);
memmove(pool.get(), audData.get() + head.poolOff, head.poolLen);
std::unique_ptr<uint8_t[]> proj(new uint8_t[head.projLen]);
memcpy(proj.get(), audData.get() + head.projOff, head.projLen);
memmove(proj.get(), audData.get() + head.projOff, head.projLen);
std::unique_ptr<uint8_t[]> sdir(new uint8_t[head.sdirLen]);
memcpy(sdir.get(), audData.get() + head.sdirOff, head.sdirLen);
memmove(sdir.get(), audData.get() + head.sdirOff, head.sdirLen);
std::unique_ptr<uint8_t[]> samp(new uint8_t[head.sampLen]);
memcpy(samp.get(), audData.get() + head.sampOff, head.sampLen);
memmove(samp.get(), audData.get() + head.sampOff, head.sampLen);
char name[128];
snprintf(name, 128, "GroupFile%u", j);
ret.emplace_back(name, IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(), GCNDataTag{}});
if (head.projLen && head.poolLen && head.sdirLen && head.sampLen)
{
char name[128];
snprintf(name, 128, "GroupFile%02u", j);
ret.emplace_back(name, IntrusiveAudioGroupData{proj.release(), head.projLen, pool.release(), head.poolLen,
sdir.release(), head.sdirLen, samp.release(), head.sampLen, GCNDataTag{}});
}
}
break;
@ -1538,11 +1597,19 @@ ContainerRegistry::Type ContainerRegistry::DetectContainerType(const char* path)
return Type::Invalid;
}
std::vector<std::pair<std::string, IntrusiveAudioGroupData>>
ContainerRegistry::LoadContainer(const char* path)
{
Type typeOut;
return LoadContainer(path, typeOut);
};
std::vector<std::pair<std::string, IntrusiveAudioGroupData>>
ContainerRegistry::LoadContainer(const char* path, Type& typeOut)
{
FILE* fp;
typeOut = Type::Invalid;
/* See if provided file is one of four raw chunks */
const char* dot = nullptr;
@ -1603,49 +1670,50 @@ ContainerRegistry::LoadContainer(const char* path)
fclose(fp);
fp = fopen(projPath, "rb");
size_t fLen = FileLength(fp);
if (!fLen)
size_t projLen = FileLength(fp);
if (!projLen)
return ret;
std::unique_ptr<uint8_t[]> proj(new uint8_t[fLen]);
fread(proj.get(), 1, fLen, fp);
std::unique_ptr<uint8_t[]> proj(new uint8_t[projLen]);
fread(proj.get(), 1, projLen, fp);
fp = fopen(poolPath, "rb");
fLen = FileLength(fp);
if (!fLen)
size_t poolLen = FileLength(fp);
if (!poolLen)
return ret;
std::unique_ptr<uint8_t[]> pool(new uint8_t[fLen]);
fread(pool.get(), 1, fLen, fp);
std::unique_ptr<uint8_t[]> pool(new uint8_t[poolLen]);
fread(pool.get(), 1, poolLen, fp);
fp = fopen(sdirPath, "rb");
fLen = FileLength(fp);
if (!fLen)
size_t sdirLen = FileLength(fp);
if (!sdirLen)
return ret;
std::unique_ptr<uint8_t[]> sdir(new uint8_t[fLen]);
fread(sdir.get(), 1, fLen, fp);
std::unique_ptr<uint8_t[]> sdir(new uint8_t[sdirLen]);
fread(sdir.get(), 1, sdirLen, fp);
fp = fopen(sampPath, "rb");
fLen = FileLength(fp);
if (!fLen)
size_t sampLen = FileLength(fp);
if (!sampPath)
return ret;
std::unique_ptr<uint8_t[]> samp(new uint8_t[fLen]);
fread(samp.get(), 1, fLen, fp);
std::unique_ptr<uint8_t[]> samp(new uint8_t[sampLen]);
fread(samp.get(), 1, sampLen, fp);
fclose(fp);
/* SDIR-based format detection */
if (*reinterpret_cast<uint32_t*>(sdir.get() + 8) == 0x0)
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(),
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projLen, pool.release(), poolLen,
sdir.release(), sdirLen, samp.release(), sampLen,
GCNDataTag{}});
else if (sdir[9] == 0x0)
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(),
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projLen, pool.release(), poolLen,
sdir.release(), sdirLen, samp.release(), sampLen,
false, N64DataTag{}});
else
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(),
sdir.release(), samp.release(),
ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projLen, pool.release(), poolLen,
sdir.release(), sdirLen, samp.release(), sampLen,
false, PCDataTag{}});
typeOut = Type::Raw4;
return ret;
}
@ -1657,6 +1725,7 @@ ContainerRegistry::LoadContainer(const char* path)
{
auto ret = LoadMP1(fp);
fclose(fp);
typeOut = Type::MetroidPrime;
return ret;
}
@ -1664,6 +1733,7 @@ ContainerRegistry::LoadContainer(const char* path)
{
auto ret = LoadMP2(fp);
fclose(fp);
typeOut = Type::MetroidPrime2;
return ret;
}
@ -1671,6 +1741,7 @@ ContainerRegistry::LoadContainer(const char* path)
{
auto ret = LoadRS1PC(fp);
fclose(fp);
typeOut = Type::RogueSquadronPC;
return ret;
}
@ -1678,6 +1749,7 @@ ContainerRegistry::LoadContainer(const char* path)
{
auto ret = LoadRS1N64(fp);
fclose(fp);
typeOut = Type::RogueSquadronN64;
return ret;
}
@ -1685,6 +1757,7 @@ ContainerRegistry::LoadContainer(const char* path)
{
auto ret = LoadBFNPC(fp);
fclose(fp);
typeOut = Type::BattleForNabooPC;
return ret;
}
@ -1692,6 +1765,7 @@ ContainerRegistry::LoadContainer(const char* path)
{
auto ret = LoadBFNN64(fp);
fclose(fp);
typeOut = Type::BattleForNabooN64;
return ret;
}
@ -1699,6 +1773,7 @@ ContainerRegistry::LoadContainer(const char* path)
{
auto ret = LoadRS2(fp);
fclose(fp);
typeOut = Type::RogueSquadron2;
return ret;
}
@ -1706,6 +1781,7 @@ ContainerRegistry::LoadContainer(const char* path)
{
auto ret = LoadRS3(fp);
fclose(fp);
typeOut = Type::RogueSquadron3;
return ret;
}

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

@ -71,7 +71,7 @@ Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId,
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);
m_submix->makeReverbHi(0.2f, 0.65f, 1.f, 0.5f, 0.f, 0.f);
}
Sequencer::ChannelState::~ChannelState()
@ -79,27 +79,53 @@ Sequencer::ChannelState::~ChannelState()
}
Sequencer::ChannelState::ChannelState(Sequencer& parent, uint8_t chanId)
: m_parent(parent), m_chanId(chanId), m_setup(m_parent.m_midiSetup[chanId])
: m_parent(parent), m_chanId(chanId)
{
if (chanId == 9)
if (m_parent.m_midiSetup)
{
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_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;
}
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_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
{
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;
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;
}
m_curVol = m_setup.volume / 127.f;
m_curPan = m_setup.panning / 64.f - 1.f;
m_ctrlVals[7] = 127;
m_ctrlVals[10] = 64;
m_ctrlVals[0x5b] = m_setup.reverb;
m_ctrlVals[0x5d] = m_setup.chorus;
}
void Sequencer::advance(double dt)

View File

@ -503,7 +503,7 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
{
const int16_t* pcm = reinterpret_cast<const int16_t*>(m_curSampleData);
remCount = std::min(samplesRem, m_lastSamplePos - m_curSamplePos);
memcpy(data, pcm + m_curSamplePos, remCount * sizeof(int16_t));
memmove(data, pcm + m_curSamplePos, remCount * sizeof(int16_t));
decSamples = remCount;
break;
}
@ -568,7 +568,7 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
{
const int16_t* pcm = reinterpret_cast<const int16_t*>(m_curSampleData);
remCount = std::min(samplesRem, m_lastSamplePos - m_curSamplePos);
memcpy(data, pcm + m_curSamplePos, remCount * sizeof(int16_t));
memmove(data, pcm + m_curSamplePos, remCount * sizeof(int16_t));
decSamples = remCount;
break;
}