mirror of
https://github.com/AxioDL/amuse.git
synced 2025-12-08 13:14:58 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ad8c06b99 | ||
|
|
e99dbc7e0a | ||
|
|
d6b9d4fca1 | ||
|
|
c7f093c5ee | ||
|
|
22a8534887 | ||
|
|
0c606fa9b7 | ||
|
|
a0241574ba | ||
|
|
bd10015024 | ||
|
|
a0bb35433a | ||
|
|
3bc47baa1d | ||
|
|
7666b51c50 | ||
|
|
ee29fb4b1e | ||
|
|
ad23f9d0c4 | ||
|
|
d2a8430746 | ||
|
|
b231cf9104 | ||
| 22610f32e9 | |||
|
|
3f98ada70a | ||
|
|
7b9c7a4eb6 | ||
|
|
797908a126 | ||
|
|
f69af0e9af | ||
|
|
3521d94d1c | ||
|
|
7659371cb6 | ||
|
|
e7c7e5ffd3 | ||
|
|
fa3007b65c | ||
|
|
e4ae1f1f88 | ||
|
|
6f538dc19c | ||
|
|
6f7a7405d7 | ||
|
|
fd0dd8922a | ||
|
|
57bb631f9b | ||
|
|
ad784966e9 | ||
|
|
e8c6418633 | ||
|
|
117d7046dd | ||
|
|
f260019b89 | ||
|
|
85b6f406f1 | ||
|
|
168c4d3cfd | ||
|
|
62ece61cb2 | ||
|
|
9ae92313e3 | ||
|
|
81bd897ec1 | ||
|
|
8930e005df | ||
|
|
e932539ec4 | ||
|
|
a047b1f6c8 | ||
|
|
d132b1be34 |
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
61
AudioUnit/AmuseContainingApp.hpp
Normal file
61
AudioUnit/AmuseContainingApp.hpp
Normal 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__
|
||||
@@ -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
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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* _Nonnull)group;
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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
|
||||
|
||||
21
AudioUnit/AudioUnitViewController.xib
Normal file
21
AudioUnit/AudioUnitViewController.xib
Normal 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>
|
||||
@@ -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>/../.."
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -10,12 +10,14 @@ set(SOURCES
|
||||
lib/AudioGroupPool.cpp
|
||||
lib/AudioGroupProject.cpp
|
||||
lib/AudioGroupSampleDirectory.cpp
|
||||
lib/DirectoryEnumerator.cpp
|
||||
lib/Emitter.cpp
|
||||
lib/Engine.cpp
|
||||
lib/Envelope.cpp
|
||||
lib/Listener.cpp
|
||||
lib/Sequencer.cpp
|
||||
lib/SoundMacroState.cpp
|
||||
lib/SongConverter.cpp
|
||||
lib/SongState.cpp
|
||||
lib/Voice.cpp
|
||||
lib/VolumeLUT.cpp
|
||||
@@ -35,6 +37,7 @@ set(HEADERS
|
||||
include/amuse/AudioGroupPool.hpp
|
||||
include/amuse/AudioGroupProject.hpp
|
||||
include/amuse/AudioGroupSampleDirectory.hpp
|
||||
include/amuse/DirectoryEnumerator.hpp
|
||||
include/amuse/Emitter.hpp
|
||||
include/amuse/Engine.hpp
|
||||
include/amuse/Entity.hpp
|
||||
@@ -42,6 +45,7 @@ set(HEADERS
|
||||
include/amuse/Listener.hpp
|
||||
include/amuse/Sequencer.hpp
|
||||
include/amuse/SoundMacroState.hpp
|
||||
include/amuse/SongConverter.hpp
|
||||
include/amuse/SongState.hpp
|
||||
include/amuse/Voice.hpp
|
||||
include/amuse/Submix.hpp
|
||||
@@ -81,7 +85,13 @@ if(TARGET boo)
|
||||
# VST Target
|
||||
add_subdirectory(VST)
|
||||
|
||||
# Multi-platform CLI tool
|
||||
add_executable(amuseplay WIN32 driver/main.cpp)
|
||||
# Multi-platform CLI tools
|
||||
|
||||
# Player
|
||||
add_executable(amuseplay WIN32 driver/amuseplay.cpp)
|
||||
target_link_libraries(amuseplay amuse boo ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES})
|
||||
|
||||
# Converter
|
||||
add_executable(amuseconv driver/amuseconv.cpp)
|
||||
target_link_libraries(amuseconv amuse ${BOO_SYS_LIBS} logvisor athena-core ${ZLIB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
482
VST/AudioGroupFilePresenter.cpp
Normal file
482
VST/AudioGroupFilePresenter.cpp
Normal file
@@ -0,0 +1,482 @@
|
||||
#include "AudioGroupFilePresenter.hpp"
|
||||
#include "VSTBackend.hpp"
|
||||
#include <Shellapi.h>
|
||||
#include <Shlwapi.h>
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
static const wchar_t *const GMNames[128] =
|
||||
{
|
||||
L"Acoustic Grand Piano", L"Bright Acoustic Piano", L"Electric Grand Piano", L"Honky-tonk Piano", L"Rhodes Piano", L"Chorused Piano",
|
||||
L"Harpsichord", L"Clavinet", L"Celesta", L"Glockenspiel", L"Music Box", L"Vibraphone", L"Marimba", L"Xylophone", L"Tubular Bells", L"Dulcimer",
|
||||
L"Drawbar Organ", L"Percussive Organ", L"Rock Organ", L"Church Organ", L"Reed Organ", L"Accordion", L"Harmonica", L"Tango Accordion",
|
||||
L"Acoustic Guitar (nylon)", L"Acoustic Guitar (steel)", L"Electric Guitar (jazz)", L"Electric Guitar (clean)", L"Electric Guitar (muted)",
|
||||
L"Overdriven Guitar", L"Distortion Guitar", L"Guitar Harmonics", L"Acoustic Bass", L"Electric Bass (finger)", L"Electric Bass (pick)",
|
||||
L"Fretless Bass", L"Slap Bass 1", L"Slap Bass 2", L"Synth Bass 1", L"Synth Bass 2", L"Violin", L"Viola", L"Cello", L"Contrabass",
|
||||
L"Tremelo Strings", L"Pizzicato Strings", L"Orchestral Harp", L"Timpani", L"String Ensemble 1", L"String Ensemble 2", L"SynthStrings 1",
|
||||
L"SynthStrings 2", L"Choir Aahs", L"Voice Oohs", L"Synth Voice", L"Orchestra Hit", L"Trumpet", L"Trombone", L"Tuba", L"Muted Trumpet",
|
||||
L"French Horn", L"Brass Section", L"Synth Brass 1", L"Synth Brass 2", L"Soprano Sax", L"Alto Sax", L"Tenor Sax", L"Baritone Sax",
|
||||
L"Oboe", L"English Horn", L"Bassoon", L"Clarinet", L"Piccolo", L"Flute", L"Recorder", L"Pan Flute", L"Bottle Blow", L"Shakuhachi", L"Whistle",
|
||||
L"Ocarina", L"Lead 1 (square)", L"Lead 2 (sawtooth)", L"Lead 3 (calliope lead)", L"Lead 4 (chiff lead)", L"Lead 5 (charang)",
|
||||
L"Lead 6 (voice)", L"Lead 7 (fifths)", L"Lead 8 (bass + lead)", L"Pad 1 (new age)", L"Pad 2 (warm)", L"Pad 3 (polysynth)", L"Pad 4 (choir)",
|
||||
L"Pad 5 (bowed)", L"Pad 6 (metallic)", L"Pad 7 (halo)", L"Pad 8 (sweep)", L"FX 1 (rain)", L"FX 2 (soundtrack)", L"FX 3 (crystal)",
|
||||
L"FX 4 (atmosphere)", L"FX 5 (brightness)", L"FX 6 (goblins)", L"FX 7 (echoes)", L"FX 8 (sci-fi)", L"Sitar", L"Banjo", L"Shamisen", L"Koto",
|
||||
L"Kalimba", L"Bagpipe", L"Fiddle", L"Shanai", L"Tinkle Bell", L"Agogo", L"Steel Drums", L"Woodblock", L"Taiko Drum", L"Melodic Tom",
|
||||
L"Synth Drum", L"Reverse Cymbal", L"Guitar Fret Noise", L"Breath Noise", L"Seashore", L"Bird Tweet", L"Telephone Ring", L"Helicopter",
|
||||
L"Applause", L"Gunshot"
|
||||
};
|
||||
|
||||
static const wchar_t *const GMPercNames[128] =
|
||||
{
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, L"Acoustic Bass Drum", L"Bass Drum 1", L"Side Stick",
|
||||
L"Acoustic Snare", L"Hand Clap", L"Electric Snare", L"Low Floor Tom", L"Closed Hi-Hat",
|
||||
L"High Floor Tom", L"Pedal Hi-Hat", L"Low Tom", L"Open Hi-Hat", L"Low-Mid Tom", L"Hi-Mid Tom",
|
||||
L"Crash Cymbal 1", L"High Tom", L"Ride Cymbal 1", L"Chinese Cymbal", L"Ride Bell", L"Tambourine",
|
||||
L"Splash Cymbal", L"Cowbell", L"Crash Cymbal 2", L"Vibraslap", L"Ride Cymbal 2", L"Hi Bongo",
|
||||
L"Low Bongo", L"Mute Hi Conga", L"Open Hi Conga", L"Low Conga", L"High Timbale", L"Low Timbale",
|
||||
L"High Agogo", L"Low Agogo", L"Cabasa", L"Maracas", L"Short Whistle", L"Long Whistle", L"Short Guiro",
|
||||
L"Long Guiro", L"Claves", L"Hi Wood Block", L"Low Wood Block", L"Mute Cuica", L"Open Cuica",
|
||||
L"Mute Triangle", L"Open Triangle"
|
||||
};
|
||||
|
||||
bool AudioGroupDataCollection::loadProj()
|
||||
{
|
||||
std::wstring path = m_path + L"\\proj";
|
||||
athena::io::FileReader r(path, 1024 * 32, false);
|
||||
if (r.hasError())
|
||||
return false;
|
||||
std::vector<uint8_t>& ret = m_projData;
|
||||
size_t len = r.length();
|
||||
ret.resize(len);
|
||||
r.readUBytesToBuf(ret.data(), len);
|
||||
return ret.size() != 0;
|
||||
}
|
||||
|
||||
bool AudioGroupDataCollection::loadPool()
|
||||
{
|
||||
std::wstring path = m_path + L"\\pool";
|
||||
athena::io::FileReader r(path, 1024 * 32, false);
|
||||
if (r.hasError())
|
||||
return false;
|
||||
std::vector<uint8_t>& ret = m_poolData;
|
||||
size_t len = r.length();
|
||||
ret.resize(len);
|
||||
r.readUBytesToBuf(ret.data(), len);
|
||||
return ret.size() != 0;
|
||||
}
|
||||
|
||||
bool AudioGroupDataCollection::loadSdir()
|
||||
{
|
||||
std::wstring path = m_path + L"\\sdir";
|
||||
athena::io::FileReader r(path, 1024 * 32, false);
|
||||
if (r.hasError())
|
||||
return false;
|
||||
std::vector<uint8_t>& ret = m_sdirData;
|
||||
size_t len = r.length();
|
||||
ret.resize(len);
|
||||
r.readUBytesToBuf(ret.data(), len);
|
||||
return ret.size() != 0;
|
||||
}
|
||||
|
||||
bool AudioGroupDataCollection::loadSamp()
|
||||
{
|
||||
std::wstring path = m_path + L"\\samp";
|
||||
athena::io::FileReader r(path, 1024 * 32, false);
|
||||
if (r.hasError())
|
||||
return false;
|
||||
std::vector<uint8_t>& ret = m_sampData;
|
||||
size_t len = r.length();
|
||||
ret.resize(len);
|
||||
r.readUBytesToBuf(ret.data(), len);
|
||||
return ret.size() != 0;
|
||||
}
|
||||
|
||||
bool AudioGroupDataCollection::loadMeta()
|
||||
{
|
||||
std::wstring path = m_path + L"\\meta";
|
||||
athena::io::FileReader r(path, 1024 * 32, false);
|
||||
if (r.hasError())
|
||||
return false;
|
||||
std::experimental::optional<MetaData>& ret = m_metaData;
|
||||
ret.emplace(r);
|
||||
return ret.operator bool();
|
||||
}
|
||||
|
||||
AudioGroupDataCollection::AudioGroupDataCollection(const std::wstring& path, const std::wstring& name)
|
||||
: m_path(path), m_name(name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool AudioGroupDataCollection::_attemptLoad()
|
||||
{
|
||||
if (m_metaData && m_loadedData && m_loadedGroup)
|
||||
return true;
|
||||
if (!loadProj())
|
||||
return false;
|
||||
if (!loadPool())
|
||||
return false;
|
||||
if (!loadSdir())
|
||||
return false;
|
||||
if (!loadSamp())
|
||||
return false;
|
||||
if (!loadMeta())
|
||||
return false;
|
||||
|
||||
return _indexData();
|
||||
}
|
||||
|
||||
bool AudioGroupDataCollection::_indexData()
|
||||
{
|
||||
switch (m_metaData->fmt)
|
||||
{
|
||||
case amuse::DataFormat::GCN:
|
||||
default:
|
||||
m_loadedData.emplace(m_projData.data(), m_projData.size(),
|
||||
m_poolData.data(), m_poolData.size(),
|
||||
m_sdirData.data(), m_sdirData.size(),
|
||||
m_sampData.data(), m_sampData.size(),
|
||||
amuse::GCNDataTag{});
|
||||
break;
|
||||
case amuse::DataFormat::N64:
|
||||
m_loadedData.emplace(m_projData.data(), m_projData.size(),
|
||||
m_poolData.data(), m_poolData.size(),
|
||||
m_sdirData.data(), m_sdirData.size(),
|
||||
m_sampData.data(), m_sampData.size(),
|
||||
m_metaData->absOffs, amuse::N64DataTag{});
|
||||
break;
|
||||
case amuse::DataFormat::PC:
|
||||
m_loadedData.emplace(m_projData.data(), m_projData.size(),
|
||||
m_poolData.data(), m_poolData.size(),
|
||||
m_sdirData.data(), m_sdirData.size(),
|
||||
m_sampData.data(), m_sampData.size(),
|
||||
m_metaData->absOffs, amuse::PCDataTag{});
|
||||
break;
|
||||
}
|
||||
|
||||
return m_loadedData.operator bool();
|
||||
}
|
||||
|
||||
void AudioGroupDataCollection::addToEngine(amuse::Engine& engine)
|
||||
{
|
||||
m_loadedGroup = engine.addAudioGroup(*m_loadedData);
|
||||
m_groupTokens.clear();
|
||||
if (m_loadedGroup)
|
||||
{
|
||||
m_groupTokens.reserve(m_loadedGroup->getProj().songGroups().size() +
|
||||
m_loadedGroup->getProj().sfxGroups().size());
|
||||
|
||||
{
|
||||
const auto& songGroups = m_loadedGroup->getProj().songGroups();
|
||||
std::map<int, const amuse::SongGroupIndex*> sortGroups;
|
||||
for (const auto& pair : songGroups)
|
||||
sortGroups[pair.first] = &pair.second;
|
||||
for (const auto& pair : sortGroups)
|
||||
m_groupTokens.emplace_back(pair.first, pair.second);
|
||||
}
|
||||
{
|
||||
const auto& sfxGroups = m_loadedGroup->getProj().sfxGroups();
|
||||
std::map<int, const amuse::SFXGroupIndex*> sortGroups;
|
||||
for (const auto& pair : sfxGroups)
|
||||
sortGroups[pair.first] = &pair.second;
|
||||
for (const auto& pair : sortGroups)
|
||||
m_groupTokens.emplace_back(pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupDataCollection::removeFromEngine(amuse::Engine& engine) const
|
||||
{
|
||||
engine.removeAudioGroup(*m_loadedData);
|
||||
}
|
||||
|
||||
AudioGroupCollection::AudioGroupCollection(const std::wstring& path, const std::wstring& name)
|
||||
: m_path(path), m_name(name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AudioGroupCollection::addCollection(std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>>&& collection)
|
||||
{
|
||||
for (std::pair<std::wstring, amuse::IntrusiveAudioGroupData>& pair : collection)
|
||||
{
|
||||
std::wstring collectionPath = m_path + L'\\' + pair.first;
|
||||
|
||||
amuse::IntrusiveAudioGroupData& dataIn = pair.second;
|
||||
auto search = m_groups.find(pair.first);
|
||||
if (search == m_groups.end())
|
||||
{
|
||||
search = m_groups.emplace(pair.first,
|
||||
std::make_unique<AudioGroupDataCollection>(collectionPath,
|
||||
pair.first)).first;
|
||||
}
|
||||
|
||||
AudioGroupDataCollection& dataCollection = *search->second;
|
||||
dataCollection.m_projData.resize(dataIn.getProjSize());
|
||||
memmove(dataCollection.m_projData.data(), dataIn.getProj(), dataIn.getProjSize());
|
||||
|
||||
dataCollection.m_poolData.resize(dataIn.getPoolSize());
|
||||
memmove(dataCollection.m_poolData.data(), dataIn.getPool(), dataIn.getPoolSize());
|
||||
|
||||
dataCollection.m_sdirData.resize(dataIn.getSdirSize());
|
||||
memmove(dataCollection.m_sdirData.data(), dataIn.getSdir(), dataIn.getSdirSize());
|
||||
|
||||
dataCollection.m_sampData.resize(dataIn.getSampSize());
|
||||
memmove(dataCollection.m_sampData.data(), dataIn.getSamp(), dataIn.getSampSize());
|
||||
|
||||
dataCollection.m_metaData.emplace(dataIn.getDataFormat(), dataIn.getAbsoluteProjOffsets(), true);
|
||||
dataCollection._indexData();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupCollection::update(AudioGroupFilePresenter& presenter)
|
||||
{
|
||||
std::wstring path = m_path + L"\\*";
|
||||
|
||||
WIN32_FIND_DATAW d;
|
||||
HANDLE dir = FindFirstFileW(path.c_str(), &d);
|
||||
if (dir == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
do
|
||||
{
|
||||
if (!wcscmp(d.cFileName, L".") || !wcscmp(d.cFileName, L".."))
|
||||
continue;
|
||||
|
||||
if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
std::wstring nameStr(d.cFileName);
|
||||
auto search = m_groups.find(nameStr);
|
||||
if (search == m_groups.end())
|
||||
{
|
||||
search =
|
||||
m_groups.emplace(nameStr,
|
||||
std::make_unique<AudioGroupDataCollection>(m_path + L'\\' + nameStr,
|
||||
nameStr)).first;
|
||||
search->second->_attemptLoad();
|
||||
}
|
||||
}
|
||||
} while (FindNextFileW(dir, &d));
|
||||
|
||||
FindClose(dir);
|
||||
}
|
||||
|
||||
void AudioGroupFilePresenter::update()
|
||||
{
|
||||
std::wstring path = m_backend.getUserDir() + L"\\*";
|
||||
std::map<std::wstring, std::unique_ptr<AudioGroupCollection>>& theMap = m_audioGroupCollections;
|
||||
|
||||
WIN32_FIND_DATAW d;
|
||||
HANDLE dir = FindFirstFileW(path.c_str(), &d);
|
||||
if (dir == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
do
|
||||
{
|
||||
if (!wcscmp(d.cFileName, L".") || !wcscmp(d.cFileName, L".."))
|
||||
continue;
|
||||
|
||||
if (d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
std::wstring nameStr(d.cFileName);
|
||||
auto search = theMap.find(nameStr);
|
||||
if (search == theMap.end())
|
||||
{
|
||||
search = theMap.emplace(nameStr,
|
||||
std::make_unique<AudioGroupCollection>(m_backend.getUserDir() + L'\\' + nameStr, nameStr)).first;
|
||||
search->second->update(*this);
|
||||
}
|
||||
}
|
||||
} while (FindNextFileW(dir, &d));
|
||||
|
||||
FindClose(dir);
|
||||
}
|
||||
|
||||
void AudioGroupFilePresenter::addCollection(const std::wstring& name,
|
||||
std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>>&& collection)
|
||||
{
|
||||
std::wstring path = m_backend.getUserDir() + L'\\' + name;
|
||||
AudioGroupCollection& insert = *m_audioGroupCollections.emplace(name, std::make_unique<AudioGroupCollection>(path, name)).first->second;
|
||||
CreateDirectory(insert.m_path.c_str(), nullptr);
|
||||
insert.addCollection(std::move(collection));
|
||||
|
||||
for (std::pair<const std::wstring, std::unique_ptr<AudioGroupDataCollection>>& pair : insert.m_groups)
|
||||
{
|
||||
std::wstring collectionPath = insert.m_path + L'\\' + pair.first;
|
||||
CreateDirectory(collectionPath.c_str(), nullptr);
|
||||
|
||||
FILE* fp = _wfopen((collectionPath + L"\\proj").c_str(), L"wb");
|
||||
if (fp)
|
||||
{
|
||||
fwrite(pair.second->m_projData.data(), 1, pair.second->m_projData.size(), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
fp = _wfopen((collectionPath + L"\\pool").c_str(), L"wb");
|
||||
if (fp)
|
||||
{
|
||||
fwrite(pair.second->m_poolData.data(), 1, pair.second->m_poolData.size(), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
fp = _wfopen((collectionPath + L"\\sdir").c_str(), L"wb");
|
||||
if (fp)
|
||||
{
|
||||
fwrite(pair.second->m_sdirData.data(), 1, pair.second->m_sdirData.size(), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
fp = _wfopen((collectionPath + L"\\samp").c_str(), L"wb");
|
||||
if (fp)
|
||||
{
|
||||
fwrite(pair.second->m_sampData.data(), 1, pair.second->m_sampData.size(), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
fp = _wfopen((collectionPath + L"\\meta").c_str(), L"wb");
|
||||
if (fp)
|
||||
{
|
||||
fwrite(&*pair.second->m_metaData, 1, sizeof(*pair.second->m_metaData), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupFilePresenter::removeCollection(unsigned idx)
|
||||
{
|
||||
if (idx < m_iteratorVec.size())
|
||||
{
|
||||
CollectionIterator& it = m_iteratorVec[idx];
|
||||
std::wstring collectionPath = it->second->m_path + L'\0';
|
||||
SHFILEOPSTRUCT op = {};
|
||||
op.wFunc = FO_DELETE;
|
||||
op.pFrom = collectionPath.c_str();
|
||||
op.fFlags = FOF_NO_UI;
|
||||
SHFileOperation(&op);
|
||||
m_audioGroupCollections.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupCollection::populateFiles(VSTEditor& editor, HTREEITEM colHandle, size_t parentIdx)
|
||||
{
|
||||
TVINSERTSTRUCT ins = {};
|
||||
ins.item.mask = TVIF_TEXT | TVIF_PARAM;
|
||||
ins.hParent = colHandle;
|
||||
ins.hInsertAfter = TVI_LAST;
|
||||
|
||||
m_iteratorVec.clear();
|
||||
m_iteratorVec.reserve(m_groups.size());
|
||||
for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it)
|
||||
{
|
||||
ins.item.pszText = LPWSTR(it->first.c_str());
|
||||
ins.item.lParam = LPARAM(0x80000000 | (parentIdx << 16) | m_iteratorVec.size());
|
||||
HTREEITEM item = TreeView_InsertItem(editor.m_collectionTree, &ins);
|
||||
if (editor.m_selCollectionIdx == parentIdx && editor.m_selFileIdx == m_iteratorVec.size())
|
||||
editor.m_deferredCollectionSel = item;
|
||||
m_iteratorVec.push_back(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupFilePresenter::populateCollectionColumn(VSTEditor& editor)
|
||||
{
|
||||
TreeView_DeleteAllItems(editor.m_collectionTree);
|
||||
TVINSERTSTRUCT ins = {};
|
||||
ins.hParent = TVI_ROOT;
|
||||
ins.hInsertAfter = TVI_LAST;
|
||||
ins.item.mask = TVIF_CHILDREN | TVIF_TEXT | TVIF_PARAM;
|
||||
|
||||
m_iteratorVec.clear();
|
||||
m_iteratorVec.reserve(m_audioGroupCollections.size());
|
||||
for (auto it = m_audioGroupCollections.begin() ; it != m_audioGroupCollections.end() ; ++it)
|
||||
{
|
||||
ins.item.cChildren = it->second->m_groups.size() ? 1 : 0;
|
||||
ins.item.pszText = LPWSTR(it->first.c_str());
|
||||
ins.item.lParam = LPARAM(m_iteratorVec.size() << 16);
|
||||
HTREEITEM item = TreeView_InsertItem(editor.m_collectionTree, &ins);
|
||||
it->second->populateFiles(editor, item, m_iteratorVec.size());
|
||||
if (editor.m_selCollectionIdx == m_iteratorVec.size() && editor.m_selFileIdx == -1)
|
||||
editor.m_deferredCollectionSel = item;
|
||||
m_iteratorVec.push_back(it);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupFilePresenter::populateGroupColumn(VSTEditor& editor, int collectionIdx, int fileIdx)
|
||||
{
|
||||
LVITEM item = {};
|
||||
item.mask = LVIF_TEXT;
|
||||
|
||||
ListView_DeleteAllItems(editor.m_groupListView);
|
||||
ListView_DeleteAllItems(editor.m_pageListView);
|
||||
if (collectionIdx < m_iteratorVec.size())
|
||||
{
|
||||
CollectionIterator& it = m_iteratorVec[collectionIdx];
|
||||
if (fileIdx < it->second->m_iteratorVec.size())
|
||||
{
|
||||
size_t idx = 0;
|
||||
AudioGroupCollection::GroupIterator& git = it->second->m_iteratorVec[fileIdx];
|
||||
for (AudioGroupDataCollection::GroupToken& gtok : git->second->m_groupTokens)
|
||||
{
|
||||
wchar_t name[256];
|
||||
wnsprintf(name, 256, L"%d (%s)", gtok.m_groupId, gtok.m_song ? L"Song" : L"SFX");
|
||||
item.pszText = name;
|
||||
item.iItem = idx++;
|
||||
ListView_InsertItem(editor.m_groupListView, &item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioGroupFilePresenter::populatePageColumn(VSTEditor& editor, int collectionIdx, int fileIdx, int groupIdx)
|
||||
{
|
||||
LVITEM item = {};
|
||||
item.mask = LVIF_TEXT | LVIF_PARAM;
|
||||
|
||||
ListView_DeleteAllItems(editor.m_pageListView);
|
||||
if (collectionIdx < m_iteratorVec.size())
|
||||
{
|
||||
CollectionIterator& it = m_iteratorVec[collectionIdx];
|
||||
if (fileIdx < it->second->m_iteratorVec.size())
|
||||
{
|
||||
AudioGroupCollection::GroupIterator& git = it->second->m_iteratorVec[fileIdx];
|
||||
if (groupIdx < git->second->m_groupTokens.size())
|
||||
{
|
||||
AudioGroupDataCollection::GroupToken& groupTok = git->second->m_groupTokens[groupIdx];
|
||||
if (groupTok.m_song)
|
||||
{
|
||||
std::map<uint8_t, const amuse::SongGroupIndex::PageEntry*> sortPages;
|
||||
for (auto& pair : groupTok.m_song->m_normPages)
|
||||
sortPages[pair.first] = pair.second;
|
||||
|
||||
size_t idx = 0;
|
||||
for (auto& pair : sortPages)
|
||||
{
|
||||
wchar_t name[256];
|
||||
wnsprintf(name, 256, L"%d (%s)", pair.first, GMNames[pair.first] ? GMNames[pair.first] : L"???");
|
||||
item.pszText = name;
|
||||
item.iItem = idx++;
|
||||
item.lParam = pair.first;
|
||||
ListView_InsertItem(editor.m_pageListView, &item);
|
||||
}
|
||||
|
||||
sortPages.clear();
|
||||
for (auto& pair : groupTok.m_song->m_drumPages)
|
||||
sortPages[pair.first] = pair.second;
|
||||
|
||||
for (auto& pair : sortPages)
|
||||
{
|
||||
wchar_t name[256];
|
||||
wnsprintf(name, 256, L"%d (%s)", pair.first, GMPercNames[pair.first] ? GMPercNames[pair.first] : L"???");
|
||||
item.pszText = name;
|
||||
item.iItem = idx++;
|
||||
item.lParam = 0x80000000 | pair.first;
|
||||
ListView_InsertItem(editor.m_pageListView, &item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
107
VST/AudioGroupFilePresenter.hpp
Normal file
107
VST/AudioGroupFilePresenter.hpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#ifndef __AMUSE_AUDIOGROUPFILEPRESENTER_HPP__
|
||||
#define __AMUSE_AUDIOGROUPFILEPRESENTER_HPP__
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include "optional.hpp"
|
||||
#include <amuse/amuse.hpp>
|
||||
#include <athena/FileReader.hpp>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <CommCtrl.h>
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
class VSTBackend;
|
||||
class VSTEditor;
|
||||
class AudioGroupFilePresenter;
|
||||
|
||||
struct AudioGroupDataCollection
|
||||
{
|
||||
std::wstring m_path;
|
||||
std::wstring m_name;
|
||||
|
||||
std::vector<uint8_t> m_projData;
|
||||
std::vector<uint8_t> m_poolData;
|
||||
std::vector<uint8_t> m_sdirData;
|
||||
std::vector<uint8_t> m_sampData;
|
||||
|
||||
struct MetaData
|
||||
{
|
||||
amuse::DataFormat fmt;
|
||||
uint32_t absOffs;
|
||||
uint32_t active;
|
||||
MetaData(amuse::DataFormat fmtIn, uint32_t absOffsIn, uint32_t activeIn)
|
||||
: fmt(fmtIn), absOffs(absOffsIn), active(activeIn) {}
|
||||
MetaData(athena::io::FileReader& r)
|
||||
: fmt(amuse::DataFormat(r.readUint32Little())), absOffs(r.readUint32Little()), active(r.readUint32Little()) {}
|
||||
};
|
||||
std::experimental::optional<MetaData> m_metaData;
|
||||
|
||||
std::experimental::optional<amuse::AudioGroupData> m_loadedData;
|
||||
const amuse::AudioGroup* m_loadedGroup;
|
||||
struct GroupToken
|
||||
{
|
||||
int m_groupId;
|
||||
const amuse::SongGroupIndex* m_song = nullptr;
|
||||
const amuse::SFXGroupIndex* m_sfx = nullptr;
|
||||
GroupToken(int id, const amuse::SongGroupIndex* song) : m_groupId(id), m_song(song) {}
|
||||
GroupToken(int id, const amuse::SFXGroupIndex* sfx) : m_groupId(id), m_sfx(sfx) {}
|
||||
};
|
||||
std::vector<GroupToken> m_groupTokens;
|
||||
|
||||
bool loadProj();
|
||||
bool loadPool();
|
||||
bool loadSdir();
|
||||
bool loadSamp();
|
||||
bool loadMeta();
|
||||
|
||||
AudioGroupDataCollection(const std::wstring& path, const std::wstring& name);
|
||||
bool isDataComplete() const {return m_projData.size() && m_poolData.size() && m_sdirData.size() && m_sampData.size() && m_metaData;}
|
||||
bool _attemptLoad();
|
||||
bool _indexData();
|
||||
|
||||
void addToEngine(amuse::Engine& engine);
|
||||
void removeFromEngine(amuse::Engine& engine) const;
|
||||
};
|
||||
|
||||
struct AudioGroupCollection
|
||||
{
|
||||
using GroupIterator = std::map<std::wstring, std::unique_ptr<AudioGroupDataCollection>>::iterator;
|
||||
std::wstring m_path;
|
||||
std::wstring m_name;
|
||||
|
||||
std::map<std::wstring, std::unique_ptr<AudioGroupDataCollection>> m_groups;
|
||||
std::vector<GroupIterator> m_iteratorVec;
|
||||
|
||||
AudioGroupCollection(const std::wstring& path, const std::wstring& name);
|
||||
void addCollection(std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>>&& collection);
|
||||
void update(AudioGroupFilePresenter& presenter);
|
||||
void populateFiles(VSTEditor& editor, HTREEITEM colHandle, size_t parentIdx);
|
||||
};
|
||||
|
||||
class AudioGroupFilePresenter
|
||||
{
|
||||
friend class VSTBackend;
|
||||
public:
|
||||
using CollectionIterator = std::map<std::wstring, std::unique_ptr<AudioGroupCollection>>::iterator;
|
||||
private:
|
||||
VSTBackend& m_backend;
|
||||
std::map<std::wstring, std::unique_ptr<AudioGroupCollection>> m_audioGroupCollections;
|
||||
std::vector<CollectionIterator> m_iteratorVec;
|
||||
public:
|
||||
AudioGroupFilePresenter(VSTBackend& backend) : m_backend(backend) {}
|
||||
void update();
|
||||
void populateCollectionColumn(VSTEditor& editor);
|
||||
void populateGroupColumn(VSTEditor& editor, int collectionIdx, int fileIdx);
|
||||
void populatePageColumn(VSTEditor& editor, int collectionIdx, int fileIdx, int groupIdx);
|
||||
void addCollection(const std::wstring& name,
|
||||
std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>>&& collection);
|
||||
void removeCollection(unsigned idx);
|
||||
VSTBackend& getBackend() {return m_backend;}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __AMUSE_AUDIOGROUPFILEPRESENTER_HPP__
|
||||
@@ -0,0 +1,18 @@
|
||||
set(VST3_SDK_ROOT "" CACHE PATH "Path to VST 3.x SDK directory containing 'public.sdk' and 'plugininterfaces'")
|
||||
if (WIN32 AND (EXISTS ${VST3_SDK_ROOT}))
|
||||
message(STATUS "Found VST SDK; building plugin")
|
||||
include_directories(${VST3_SDK_ROOT} ${VST3_SDK_ROOT}/public.sdk/source/vst2.x)
|
||||
set(VST2_DIR ${VST3_SDK_ROOT}/public.sdk/source/vst2.x)
|
||||
add_definitions(${BOO_SYS_DEFINES})
|
||||
add_library(amuse-vst SHARED
|
||||
VSTBackend.hpp VSTBackend.cpp
|
||||
VSTEditor.hpp VSTEditor.cpp
|
||||
AudioGroupFilePresenter.hpp AudioGroupFilePresenter.cpp
|
||||
${VST2_DIR}/vstplugmain.cpp
|
||||
${VST2_DIR}/audioeffect.cpp
|
||||
${VST2_DIR}/audioeffectx.cpp
|
||||
FileOpenDialog.hpp FileOpenDialog.cpp)
|
||||
target_link_libraries(amuse-vst amuse boo soxr ${ZLIB_LIBRARIES} Winmm soxr
|
||||
Msimg32 Shlwapi logvisor athena-core)
|
||||
set_target_properties(amuse-vst PROPERTIES LINK_FLAGS "/EXPORT:VSTPluginMain")
|
||||
endif()
|
||||
|
||||
239
VST/FileOpenDialog.cpp
Normal file
239
VST/FileOpenDialog.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "FileOpenDialog.hpp"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h> // For common windows data types and function headers
|
||||
#define STRICT_TYPED_ITEMIDS
|
||||
#include <objbase.h> // For COM headers
|
||||
#include <shobjidl.h> // for IFileDialogEvents and IFileDialogControlEvents
|
||||
#include <shlwapi.h>
|
||||
#include <knownfolders.h> // for KnownFolder APIs/datatypes/function headers
|
||||
#include <propvarutil.h> // for PROPVAR-related functions
|
||||
#include <propkey.h> // for the Property key APIs/datatypes
|
||||
#include <propidl.h> // for the Property System APIs
|
||||
#include <strsafe.h> // for StringCchPrintfW
|
||||
#include <shtypes.h> // for COMDLG_FILTERSPEC
|
||||
#include <new>
|
||||
|
||||
// Controls
|
||||
#define CONTROL_GROUP 2000
|
||||
#define CONTROL_RADIOBUTTONLIST 2
|
||||
#define CONTROL_RADIOBUTTON1 1
|
||||
#define CONTROL_RADIOBUTTON2 2 // It is OK for this to have the same IDas CONTROL_RADIOBUTTONLIST,
|
||||
// because it is a child control under CONTROL_RADIOBUTTONLIST
|
||||
|
||||
// IDs for the Task Dialog Buttons
|
||||
#define IDC_BASICFILEOPEN 100
|
||||
#define IDC_ADDITEMSTOCUSTOMPLACES 101
|
||||
#define IDC_ADDCUSTOMCONTROLS 102
|
||||
#define IDC_SETDEFAULTVALUESFORPROPERTIES 103
|
||||
#define IDC_WRITEPROPERTIESUSINGHANDLERS 104
|
||||
#define IDC_WRITEPROPERTIESWITHOUTUSINGHANDLERS 105
|
||||
|
||||
HWND ghMainWnd = 0;
|
||||
HINSTANCE ghAppInst = 0;
|
||||
RECT winRect;
|
||||
|
||||
class CDialogEventHandler : public IFileDialogEvents,
|
||||
public IFileDialogControlEvents
|
||||
{
|
||||
public:
|
||||
// IUnknown methods
|
||||
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
|
||||
{
|
||||
static const QITAB qit[] = {
|
||||
QITABENT(CDialogEventHandler, IFileDialogEvents),
|
||||
QITABENT(CDialogEventHandler, IFileDialogControlEvents),
|
||||
{ 0 },
|
||||
};
|
||||
return QISearch(this, qit, riid, ppv);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(ULONG) AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&_cRef);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP_(ULONG) Release()
|
||||
{
|
||||
long cRef = InterlockedDecrement(&_cRef);
|
||||
if (!cRef)
|
||||
delete this;
|
||||
return cRef;
|
||||
}
|
||||
|
||||
// IFileDialogEvents methods
|
||||
IFACEMETHODIMP OnFileOk(IFileDialog *) { return S_OK; };
|
||||
IFACEMETHODIMP OnFolderChange(IFileDialog *) { return S_OK; };
|
||||
IFACEMETHODIMP OnFolderChanging(IFileDialog *, IShellItem *) { return S_OK; };
|
||||
IFACEMETHODIMP OnHelp(IFileDialog *) { return S_OK; };
|
||||
IFACEMETHODIMP OnSelectionChange(IFileDialog *) { return S_OK; };
|
||||
IFACEMETHODIMP OnShareViolation(IFileDialog *, IShellItem *, FDE_SHAREVIOLATION_RESPONSE *) { return S_OK; };
|
||||
IFACEMETHODIMP OnTypeChange(IFileDialog *pfd);
|
||||
IFACEMETHODIMP OnOverwrite(IFileDialog *, IShellItem *, FDE_OVERWRITE_RESPONSE *) { return S_OK; };
|
||||
|
||||
// IFileDialogControlEvents methods
|
||||
IFACEMETHODIMP OnItemSelected(IFileDialogCustomize *pfdc, DWORD dwIDCtl, DWORD dwIDItem);
|
||||
IFACEMETHODIMP OnButtonClicked(IFileDialogCustomize *, DWORD) { return S_OK; };
|
||||
IFACEMETHODIMP OnCheckButtonToggled(IFileDialogCustomize *, DWORD, BOOL) { return S_OK; };
|
||||
IFACEMETHODIMP OnControlActivating(IFileDialogCustomize *, DWORD) { return S_OK; };
|
||||
|
||||
CDialogEventHandler() : _cRef(1) { };
|
||||
private:
|
||||
~CDialogEventHandler() { };
|
||||
long _cRef;
|
||||
};
|
||||
|
||||
HRESULT CDialogEventHandler_CreateInstance(REFIID riid, void **ppv);
|
||||
|
||||
std::wstring openDB()
|
||||
{
|
||||
std::wstring ret;
|
||||
CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
|
||||
//Cocreate the file open dialog object
|
||||
IFileDialog *pfd = NULL;
|
||||
|
||||
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
//Stuff needed for later
|
||||
const COMDLG_FILTERSPEC rgFExt[] = {{L"Audio Group Archive (*.*)", L"*.*"}};
|
||||
|
||||
//Create event handling
|
||||
IFileDialogEvents *pfde = NULL;
|
||||
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
|
||||
|
||||
if(SUCCEEDED(hr))
|
||||
{
|
||||
//Hook the event handler
|
||||
DWORD dwCookie;
|
||||
|
||||
hr = pfd->Advise(pfde, &dwCookie);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
//Set options for the dialog
|
||||
DWORD dwFlags;
|
||||
|
||||
//Get options first so we do not override
|
||||
hr = pfd->GetOptions(&dwFlags);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
//Get shell items only
|
||||
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
//Types of files to display (not default)
|
||||
hr = pfd->SetFileTypes(ARRAYSIZE(rgFExt), rgFExt);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
//Set default file type to display
|
||||
//hr = pfd->SetDefaultExtension(L"sqlite");
|
||||
|
||||
//if (SUCCEEDED(hr))
|
||||
//{
|
||||
//Show dialog
|
||||
hr = pfd->Show(NULL);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
//Get the result once the user clicks on open
|
||||
IShellItem *result;
|
||||
|
||||
hr = pfd->GetResult(&result);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
//Print out the file name
|
||||
PWSTR fName = NULL;
|
||||
|
||||
hr = result->GetDisplayName(SIGDN_FILESYSPATH, &fName);
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
ret.assign(fName);
|
||||
CoTaskMemFree(fName);
|
||||
}
|
||||
|
||||
result->Release();
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pfd->Unadvise(dwCookie);
|
||||
}
|
||||
|
||||
pfde->Release();
|
||||
}
|
||||
|
||||
pfd->Release();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
HRESULT CDialogEventHandler_CreateInstance(REFIID riid, void **ppv)
|
||||
{
|
||||
*ppv = NULL;
|
||||
CDialogEventHandler *pDialogEventHandler = new (std::nothrow) CDialogEventHandler();
|
||||
HRESULT hr = pDialogEventHandler ? S_OK : E_OUTOFMEMORY;
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = pDialogEventHandler->QueryInterface(riid, ppv);
|
||||
pDialogEventHandler->Release();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT CDialogEventHandler::OnTypeChange(IFileDialog *pfd)
|
||||
{
|
||||
IFileSaveDialog *pfsd;
|
||||
HRESULT hr = pfd->QueryInterface(&pfsd);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
UINT uIndex;
|
||||
hr = pfsd->GetFileTypeIndex(&uIndex); // index of current file-type
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
IPropertyDescriptionList *pdl = NULL;
|
||||
|
||||
|
||||
}
|
||||
pfsd->Release();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
// IFileDialogControlEvents
|
||||
// This method gets called when an dialog control item selection happens (radio-button selection. etc).
|
||||
// For sample sake, let's react to this event by changing the dialog title.
|
||||
HRESULT CDialogEventHandler::OnItemSelected(IFileDialogCustomize *pfdc, DWORD dwIDCtl, DWORD dwIDItem)
|
||||
{
|
||||
IFileDialog *pfd = NULL;
|
||||
HRESULT hr = pfdc->QueryInterface(&pfd);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (dwIDCtl == CONTROL_RADIOBUTTONLIST)
|
||||
{
|
||||
switch (dwIDItem)
|
||||
{
|
||||
case CONTROL_RADIOBUTTON1:
|
||||
hr = pfd->SetTitle(L"Longhorn Dialog");
|
||||
break;
|
||||
|
||||
case CONTROL_RADIOBUTTON2:
|
||||
hr = pfd->SetTitle(L"Vista Dialog");
|
||||
break;
|
||||
}
|
||||
}
|
||||
pfd->Release();
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
8
VST/FileOpenDialog.hpp
Normal file
8
VST/FileOpenDialog.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef __AMUSE_FILEOPENDIALOG_HPP__
|
||||
#define __AMUSE_FILEOPENDIALOG_HPP__
|
||||
|
||||
#include <string>
|
||||
|
||||
std::wstring openDB();
|
||||
|
||||
#endif // __AMUSE_FILEOPENDIALOG_HPP__
|
||||
436
VST/VSTBackend.cpp
Normal file
436
VST/VSTBackend.cpp
Normal file
@@ -0,0 +1,436 @@
|
||||
#include "VSTBackend.hpp"
|
||||
#include "audiodev/AudioVoiceEngine.hpp"
|
||||
#include <Shlobj.h>
|
||||
#include <logvisor/logvisor.hpp>
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
struct VSTVoiceEngine : boo::BaseAudioVoiceEngine
|
||||
{
|
||||
std::vector<float> m_interleavedBuf;
|
||||
float** m_outputData = nullptr;
|
||||
size_t m_renderFrames = 0;
|
||||
size_t m_curBufFrame = 0;
|
||||
|
||||
boo::AudioChannelSet _getAvailableSet()
|
||||
{
|
||||
return boo::AudioChannelSet::Stereo;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
boo::ReceiveFunctor* m_midiReceiver = nullptr;
|
||||
|
||||
struct MIDIIn : public boo::IMIDIIn
|
||||
{
|
||||
MIDIIn(bool virt, boo::ReceiveFunctor&& receiver)
|
||||
: IMIDIIn(virt, std::move(receiver)) {}
|
||||
|
||||
std::string description() const
|
||||
{
|
||||
return "VST MIDI";
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<boo::IMIDIIn> newVirtualMIDIIn(boo::ReceiveFunctor&& receiver)
|
||||
{
|
||||
std::unique_ptr<boo::IMIDIIn> ret = std::make_unique<MIDIIn>(true, std::move(receiver));
|
||||
m_midiReceiver = &ret->m_receiver;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::unique_ptr<boo::IMIDIOut> newVirtualMIDIOut()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<boo::IMIDIInOut> newVirtualMIDIInOut(boo::ReceiveFunctor&& receiver)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<boo::IMIDIIn> newRealMIDIIn(const char* name, boo::ReceiveFunctor&& receiver)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<boo::IMIDIOut> newRealMIDIOut(const char* name)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<boo::IMIDIInOut> newRealMIDIInOut(const char* name, boo::ReceiveFunctor&& receiver)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
bool useMIDILock() const {return false;}
|
||||
|
||||
VSTVoiceEngine()
|
||||
{
|
||||
m_mixInfo.m_periodFrames = 1024;
|
||||
m_mixInfo.m_sampleRate = 44100.0;
|
||||
m_mixInfo.m_sampleFormat = SOXR_FLOAT32_I;
|
||||
m_mixInfo.m_bitsPerSample = 32;
|
||||
_buildAudioRenderClient();
|
||||
}
|
||||
|
||||
void _buildAudioRenderClient()
|
||||
{
|
||||
m_mixInfo.m_channels = _getAvailableSet();
|
||||
unsigned chCount = ChannelCount(m_mixInfo.m_channels);
|
||||
|
||||
m_5msFrames = m_mixInfo.m_sampleRate * 5 / 1000;
|
||||
m_curBufFrame = m_5msFrames;
|
||||
m_mixInfo.m_periodFrames = m_5msFrames;
|
||||
m_interleavedBuf.resize(m_5msFrames * 2);
|
||||
|
||||
boo::ChannelMap& chMapOut = m_mixInfo.m_channelMap;
|
||||
chMapOut.m_channelCount = 2;
|
||||
chMapOut.m_channels[0] = boo::AudioChannel::FrontLeft;
|
||||
chMapOut.m_channels[1] = boo::AudioChannel::FrontRight;
|
||||
|
||||
while (chMapOut.m_channelCount < chCount)
|
||||
chMapOut.m_channels[chMapOut.m_channelCount++] = boo::AudioChannel::Unknown;
|
||||
}
|
||||
|
||||
void _rebuildAudioRenderClient(double sampleRate, size_t periodFrames)
|
||||
{
|
||||
m_mixInfo.m_periodFrames = periodFrames;
|
||||
m_mixInfo.m_sampleRate = sampleRate;
|
||||
_buildAudioRenderClient();
|
||||
|
||||
for (boo::AudioVoice* vox : m_activeVoices)
|
||||
vox->_resetSampleRate(vox->m_sampleRateIn);
|
||||
for (boo::AudioSubmix* smx : m_activeSubmixes)
|
||||
smx->_resetOutputSampleRate();
|
||||
}
|
||||
|
||||
void pumpAndMixVoices()
|
||||
{
|
||||
for (size_t f=0 ; f<m_renderFrames ;)
|
||||
{
|
||||
if (m_curBufFrame == m_5msFrames)
|
||||
{
|
||||
_pumpAndMixVoices(m_5msFrames, m_interleavedBuf.data());
|
||||
m_curBufFrame = 0;
|
||||
}
|
||||
|
||||
size_t remRenderFrames = std::min(m_renderFrames - f, m_5msFrames - m_curBufFrame);
|
||||
if (remRenderFrames)
|
||||
{
|
||||
for (size_t i=0 ; i<2 ; ++i)
|
||||
{
|
||||
float* bufOut = m_outputData[i];
|
||||
for (size_t lf=0 ; lf<remRenderFrames ; ++lf)
|
||||
bufOut[f+lf] = m_interleavedBuf[(m_curBufFrame+lf)*2+i];
|
||||
}
|
||||
m_curBufFrame += remRenderFrames;
|
||||
f += remRenderFrames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double getCurrentSampleRate() const {return m_mixInfo.m_sampleRate;}
|
||||
};
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
#define kBackendID CCONST ('a','m','u','s')
|
||||
|
||||
static logvisor::Module Log("amuse::AudioUnitBackend");
|
||||
|
||||
VSTBackend::VSTBackend(audioMasterCallback cb)
|
||||
: AudioEffectX(cb, 0, 0), m_filePresenter(*this), m_editor(*this)
|
||||
{
|
||||
isSynth();
|
||||
setUniqueID(kBackendID);
|
||||
setNumInputs(0);
|
||||
setNumOutputs(2);
|
||||
setEditor(&m_editor);
|
||||
sizeWindow(600, 420);
|
||||
programsAreChunks();
|
||||
|
||||
m_booBackend = std::make_unique<VSTVoiceEngine>();
|
||||
m_voxAlloc.emplace(*m_booBackend);
|
||||
m_engine.emplace(*m_voxAlloc);
|
||||
|
||||
WCHAR path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, path)))
|
||||
{
|
||||
m_userDir = std::wstring(path) + L"\\Amuse";
|
||||
CreateDirectory(m_userDir.c_str(), nullptr);
|
||||
}
|
||||
|
||||
m_filePresenter.update();
|
||||
}
|
||||
|
||||
VSTBackend::~VSTBackend()
|
||||
{
|
||||
editor = nullptr;
|
||||
}
|
||||
|
||||
AEffEditor* VSTBackend::getEditor()
|
||||
{
|
||||
return &m_editor;
|
||||
}
|
||||
|
||||
VstInt32 VSTBackend::processEvents(VstEvents* events)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_lock);
|
||||
VSTVoiceEngine& engine = static_cast<VSTVoiceEngine&>(*m_booBackend);
|
||||
|
||||
/* Handle group load request */
|
||||
if (m_curGroup != m_reqGroup)
|
||||
{
|
||||
m_curGroup = m_reqGroup;
|
||||
if (m_curSeq)
|
||||
m_curSeq->kill();
|
||||
m_curSeq = m_engine->seqPlay(m_reqGroup, -1, nullptr);
|
||||
m_editor.reselectPage();
|
||||
}
|
||||
|
||||
if (engine.m_midiReceiver)
|
||||
{
|
||||
for (VstInt32 i=0 ; i<events->numEvents ; ++i)
|
||||
{
|
||||
VstMidiEvent* evt = reinterpret_cast<VstMidiEvent*>(events->events[i]);
|
||||
if (evt->type == kVstMidiType)
|
||||
{
|
||||
if (m_routeChannel != -1)
|
||||
{
|
||||
evt->midiData[0] &= ~0xf;
|
||||
evt->midiData[0] |= m_routeChannel & 0xf;
|
||||
}
|
||||
(*engine.m_midiReceiver)(std::vector<uint8_t>(std::cbegin(evt->midiData),
|
||||
std::cbegin(evt->midiData) + evt->byteSize),
|
||||
(m_curFrame + evt->deltaFrames) / sampleRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void VSTBackend::processReplacing(float**, float** outputs, VstInt32 sampleFrames)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_lock);
|
||||
VSTVoiceEngine& engine = static_cast<VSTVoiceEngine&>(*m_booBackend);
|
||||
|
||||
/* Output buffers */
|
||||
engine.m_renderFrames = sampleFrames;
|
||||
engine.m_outputData = outputs;
|
||||
m_engine->pumpEngine();
|
||||
|
||||
m_curFrame += sampleFrames;
|
||||
}
|
||||
|
||||
VstInt32 VSTBackend::canDo(char* text)
|
||||
{
|
||||
VstInt32 returnCode = 0;
|
||||
|
||||
if (!strcmp(text, "receiveVstEvents"))
|
||||
returnCode = 1;
|
||||
else if (!strcmp(text, "receiveVstMidiEvent"))
|
||||
returnCode = 1;
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
VstPlugCategory VSTBackend::getPlugCategory()
|
||||
{
|
||||
return kPlugCategSynth;
|
||||
}
|
||||
|
||||
bool VSTBackend::getEffectName(char* text)
|
||||
{
|
||||
strcpy(text, "Amuse");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VSTBackend::getProductString(char* text)
|
||||
{
|
||||
strcpy(text, "Amuse");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VSTBackend::getVendorString(char* text)
|
||||
{
|
||||
strcpy(text, "AxioDL");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VSTBackend::getProgramNameIndexed(VstInt32 category, VstInt32 index, char* text)
|
||||
{
|
||||
strcpy(text, "Sampler");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VSTBackend::getOutputProperties(VstInt32 index, VstPinProperties* properties)
|
||||
{
|
||||
bool returnCode = false;
|
||||
if (index == 0)
|
||||
{
|
||||
strcpy(properties->label, "Amuse Out");
|
||||
properties->flags = kVstPinIsStereo | kVstPinIsActive;
|
||||
properties->arrangementType = kSpeakerArrStereo;
|
||||
returnCode = true;
|
||||
}
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
VstInt32 VSTBackend::getNumMidiInputChannels()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
void VSTBackend::setSampleRate(float sampleRate)
|
||||
{
|
||||
AudioEffectX::setSampleRate(sampleRate);
|
||||
VSTVoiceEngine& engine = static_cast<VSTVoiceEngine&>(*m_booBackend);
|
||||
engine._rebuildAudioRenderClient(sampleRate, engine.mixInfo().m_periodFrames);
|
||||
}
|
||||
|
||||
void VSTBackend::setBlockSize(VstInt32 blockSize)
|
||||
{
|
||||
AudioEffectX::setBlockSize(blockSize);
|
||||
VSTVoiceEngine& engine = static_cast<VSTVoiceEngine&>(*m_booBackend);
|
||||
engine._rebuildAudioRenderClient(engine.mixInfo().m_sampleRate, blockSize);
|
||||
}
|
||||
|
||||
void VSTBackend::loadGroupFile(int collectionIdx, int fileIdx)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_lock);
|
||||
|
||||
if (m_curSeq)
|
||||
{
|
||||
m_curSeq->kill();
|
||||
m_curSeq.reset();
|
||||
m_curGroup = -1;
|
||||
m_reqGroup = -1;
|
||||
}
|
||||
|
||||
if (collectionIdx < m_filePresenter.m_iteratorVec.size())
|
||||
{
|
||||
AudioGroupFilePresenter::CollectionIterator& it = m_filePresenter.m_iteratorVec[collectionIdx];
|
||||
if (fileIdx < it->second->m_iteratorVec.size())
|
||||
{
|
||||
AudioGroupCollection::GroupIterator& git = it->second->m_iteratorVec[fileIdx];
|
||||
if (m_curData)
|
||||
m_curData->removeFromEngine(*m_engine);
|
||||
git->second->addToEngine(*m_engine);
|
||||
m_curData = git->second.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VSTBackend::setGroup(int groupIdx, bool immediate)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_lock);
|
||||
|
||||
if (!m_curData)
|
||||
return;
|
||||
|
||||
if (groupIdx < m_curData->m_groupTokens.size())
|
||||
{
|
||||
const AudioGroupDataCollection::GroupToken& groupTok = m_curData->m_groupTokens[groupIdx];
|
||||
m_reqGroup = groupTok.m_groupId;
|
||||
if (immediate)
|
||||
{
|
||||
if (m_curSeq)
|
||||
m_curSeq->kill();
|
||||
m_curSeq = m_engine->seqPlay(groupTok.m_groupId, -1, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VSTBackend::_setNormalProgram(int programNo)
|
||||
{
|
||||
if (!m_curSeq)
|
||||
return;
|
||||
m_curSeq->setChanProgram(0, programNo);
|
||||
m_routeChannel = 0;
|
||||
}
|
||||
|
||||
void VSTBackend::setNormalProgram(int programNo)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_lock);
|
||||
_setNormalProgram(programNo);
|
||||
}
|
||||
|
||||
void VSTBackend::_setDrumProgram(int programNo)
|
||||
{
|
||||
if (!m_curSeq)
|
||||
return;
|
||||
m_curSeq->setChanProgram(9, programNo);
|
||||
m_routeChannel = 9;
|
||||
}
|
||||
|
||||
void VSTBackend::setDrumProgram(int programNo)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_lock);
|
||||
_setDrumProgram(programNo);
|
||||
}
|
||||
|
||||
VstInt32 VSTBackend::getChunk(void** data, bool)
|
||||
{
|
||||
size_t allocSz = 14;
|
||||
if (m_curData)
|
||||
allocSz += (m_curData->m_path.size() - m_userDir.size() - 1) * 2;
|
||||
|
||||
uint8_t* buf = new uint8_t[allocSz];
|
||||
if (m_curData)
|
||||
memmove(buf, m_curData->m_path.data() + m_userDir.size() + 1, allocSz - 12);
|
||||
else
|
||||
*reinterpret_cast<wchar_t*>(buf) = L'\0';
|
||||
uint32_t* intVals = reinterpret_cast<uint32_t*>(buf + allocSz - 12);
|
||||
intVals[0] = 0;
|
||||
intVals[1] = m_editor.m_selGroupIdx;
|
||||
intVals[2] = m_editor.m_selPageIdx;
|
||||
*data = buf;
|
||||
return allocSz;
|
||||
}
|
||||
|
||||
VstInt32 VSTBackend::setChunk(void* data, VstInt32 byteSize, bool)
|
||||
{
|
||||
if (byteSize < 14)
|
||||
return 0;
|
||||
|
||||
wchar_t* path = reinterpret_cast<wchar_t*>(data);
|
||||
uint32_t* intVals = reinterpret_cast<uint32_t*>(path + wcslen(path) + 1);
|
||||
std::wstring targetPath = m_userDir + L'\\' + path;
|
||||
uint32_t groupIdx = intVals[1];
|
||||
uint32_t pageIdx = intVals[2];
|
||||
|
||||
size_t colIdx = 0;
|
||||
for (auto& collection : m_filePresenter.m_audioGroupCollections)
|
||||
{
|
||||
size_t fileIdx = 0;
|
||||
for (auto& file : collection.second->m_groups)
|
||||
{
|
||||
if (!file.second->m_path.compare(targetPath))
|
||||
{
|
||||
m_editor.selectCollection(LPARAM(0x80000000 | (colIdx << 16) | fileIdx));
|
||||
m_editor.selectGroup(groupIdx);
|
||||
m_editor.selectPage(pageIdx);
|
||||
m_editor._reselectColumns();
|
||||
}
|
||||
++fileIdx;
|
||||
}
|
||||
++colIdx;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AudioEffect* createEffectInstance(audioMasterCallback audioMaster)
|
||||
{
|
||||
return new amuse::VSTBackend(audioMaster);
|
||||
}
|
||||
78
VST/VSTBackend.hpp
Normal file
78
VST/VSTBackend.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef __AMUSE_VSTBACKEND_HPP__
|
||||
#define __AMUSE_VSTBACKEND_HPP__
|
||||
|
||||
#include "audioeffectx.h"
|
||||
#include "VSTEditor.hpp"
|
||||
#include <memory>
|
||||
#include "optional.hpp"
|
||||
|
||||
#include "amuse/BooBackend.hpp"
|
||||
#include "amuse/Engine.hpp"
|
||||
#include "amuse/IBackendVoice.hpp"
|
||||
#include "amuse/IBackendSubmix.hpp"
|
||||
#include "amuse/IBackendVoiceAllocator.hpp"
|
||||
#include "AudioGroupFilePresenter.hpp"
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
class VSTBackend;
|
||||
|
||||
/** Backend voice allocator implementation for AudioUnit mixer */
|
||||
class VSTBackendVoiceAllocator : public BooBackendVoiceAllocator
|
||||
{
|
||||
public:
|
||||
VSTBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine)
|
||||
: BooBackendVoiceAllocator(booEngine) {}
|
||||
};
|
||||
|
||||
/** Actual plugin implementation class */
|
||||
class VSTBackend : public AudioEffectX
|
||||
{
|
||||
std::mutex m_lock;
|
||||
std::unique_ptr<boo::IAudioVoiceEngine> m_booBackend;
|
||||
std::experimental::optional<amuse::VSTBackendVoiceAllocator> m_voxAlloc;
|
||||
std::experimental::optional<amuse::Engine> m_engine;
|
||||
std::shared_ptr<amuse::Sequencer> m_curSeq;
|
||||
int m_reqGroup = -1;
|
||||
int m_curGroup = -1;
|
||||
const AudioGroupDataCollection* m_curData = nullptr;
|
||||
size_t m_curFrame = 0;
|
||||
std::wstring m_userDir;
|
||||
int m_routeChannel = -1;
|
||||
AudioGroupFilePresenter m_filePresenter;
|
||||
VSTEditor m_editor;
|
||||
public:
|
||||
VSTBackend(audioMasterCallback cb);
|
||||
~VSTBackend();
|
||||
AEffEditor* getEditor();
|
||||
VstInt32 processEvents(VstEvents* events);
|
||||
void processReplacing(float** inputs, float** outputs, VstInt32 sampleFrames);
|
||||
VstInt32 canDo(char* text);
|
||||
VstPlugCategory getPlugCategory();
|
||||
bool getEffectName(char* text);
|
||||
bool getProductString(char* text);
|
||||
bool getVendorString(char* text);
|
||||
bool getProgramNameIndexed(VstInt32 category, VstInt32 index, char* text);
|
||||
bool getOutputProperties(VstInt32 index, VstPinProperties* properties);
|
||||
VstInt32 getNumMidiInputChannels();
|
||||
void setSampleRate(float sampleRate);
|
||||
void setBlockSize(VstInt32 blockSize);
|
||||
|
||||
amuse::Engine& getAmuseEngine() {return *m_engine;}
|
||||
const std::wstring& getUserDir() const {return m_userDir;}
|
||||
AudioGroupFilePresenter& getFilePresenter() {return m_filePresenter;}
|
||||
|
||||
void loadGroupFile(int collectionIdx, int fileIdx);
|
||||
void setGroup(int groupIdx, bool immediate);
|
||||
void _setNormalProgram(int programNo);
|
||||
void setNormalProgram(int programNo);
|
||||
void _setDrumProgram(int programNo);
|
||||
void setDrumProgram(int programNo);
|
||||
|
||||
VstInt32 getChunk(void** data, bool isPreset);
|
||||
VstInt32 setChunk(void* data, VstInt32 byteSize, bool isPreset);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __AMUSE_VSTBACKEND_HPP__
|
||||
481
VST/VSTEditor.cpp
Normal file
481
VST/VSTEditor.cpp
Normal file
@@ -0,0 +1,481 @@
|
||||
#include "VSTEditor.hpp"
|
||||
#include "VSTBackend.hpp"
|
||||
#include "FileOpenDialog.hpp"
|
||||
#include <Windowsx.h>
|
||||
#include <shellapi.h>
|
||||
#include <algorithm>
|
||||
#include <Shlwapi.h>
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
extern void* hInstance;
|
||||
static WNDPROC OriginalListViewProc = 0;
|
||||
static HBRUSH gGreyBorderBrush;
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
VSTEditor::VSTEditor(VSTBackend& backend)
|
||||
: AEffEditor(&backend), m_backend(backend)
|
||||
{
|
||||
}
|
||||
|
||||
bool VSTEditor::getRect(ERect** rect)
|
||||
{
|
||||
*rect = &m_windowRect;
|
||||
return true;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK VSTEditor::WindowProc(HWND hwnd,
|
||||
UINT uMsg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam)
|
||||
{
|
||||
VSTEditor& editor = *reinterpret_cast<VSTEditor*>(GetWindowLongPtrW(hwnd, 0));
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_NOTIFY:
|
||||
{
|
||||
NMHDR& itemAct = *reinterpret_cast<LPNMHDR>(lParam);
|
||||
switch (itemAct.code)
|
||||
{
|
||||
case HDN_BEGINTRACK:
|
||||
return TRUE;
|
||||
case NM_CLICK:
|
||||
{
|
||||
NMITEMACTIVATE& itemAct = *reinterpret_cast<LPNMITEMACTIVATE>(lParam);
|
||||
if (itemAct.hdr.hwndFrom == editor.m_groupListView)
|
||||
editor.selectGroup(itemAct.iItem);
|
||||
else if (itemAct.hdr.hwndFrom == editor.m_pageListView)
|
||||
editor.selectPage(itemAct.iItem);
|
||||
return 0;
|
||||
}
|
||||
case TVN_SELCHANGED:
|
||||
{
|
||||
if (editor.m_deferredCollectionSel)
|
||||
return 0;
|
||||
NMTREEVIEW& itemAct = *reinterpret_cast<LPNMTREEVIEW>(lParam);
|
||||
if (itemAct.hdr.hwndFrom == editor.m_collectionTree)
|
||||
editor.selectCollection(itemAct.itemNew.lParam);
|
||||
return 0;
|
||||
}
|
||||
case TVN_GETDISPINFO:
|
||||
{
|
||||
NMTVDISPINFO& treeDispInfo = *reinterpret_cast<LPNMTVDISPINFO>(lParam);
|
||||
if (treeDispInfo.item.mask & TVIF_CHILDREN)
|
||||
{
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
case WM_COMMAND:
|
||||
{
|
||||
switch (HIWORD(wParam))
|
||||
{
|
||||
case BN_CLICKED:
|
||||
{
|
||||
HWND button = HWND(lParam);
|
||||
if (button == editor.m_collectionAdd)
|
||||
editor.addAction();
|
||||
else if (button == editor.m_collectionRemove)
|
||||
editor.removeAction();
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
{
|
||||
RECT rect;
|
||||
GetClientRect(hwnd, &rect);
|
||||
FillRect(HDC(wParam), &rect, gGreyBorderBrush);
|
||||
return 1;
|
||||
}
|
||||
default:
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK VSTEditor::ColHeaderWindowProc(HWND hwnd,
|
||||
UINT uMsg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam)
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_SETCURSOR:
|
||||
return TRUE;
|
||||
case WM_LBUTTONDBLCLK:
|
||||
return 0;
|
||||
case WM_PAINT:
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC dc = BeginPaint(hwnd, &ps);
|
||||
RECT rect;
|
||||
GetClientRect(hwnd, &rect);
|
||||
|
||||
TRIVERTEX verts[] =
|
||||
{
|
||||
{rect.left, rect.top, 0x6000, 0x6000, 0x7000, 0xff00},
|
||||
{rect.right, rect.bottom, 0x2000, 0x2000, 0x2800, 0xff00}
|
||||
};
|
||||
GRADIENT_RECT grect = {0, 1};
|
||||
GradientFill(dc, verts, 2, &grect, 1, GRADIENT_FILL_RECT_V);
|
||||
|
||||
SetTextColor(dc, RGB(255,255,255));
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
SelectObject(dc, GetStockObject(ANSI_VAR_FONT));
|
||||
rect.left += 6;
|
||||
|
||||
LPWSTR str = LPWSTR(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||
DrawText(dc, str, -1, &rect, DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS);
|
||||
|
||||
EndPaint(hwnd, &ps);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return CallWindowProc(OriginalListViewProc, hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
void VSTEditor::_reselectColumns()
|
||||
{
|
||||
if (m_deferredCollectionSel)
|
||||
{
|
||||
TreeView_SelectItem(m_collectionTree, m_deferredCollectionSel);
|
||||
m_deferredCollectionSel = 0;
|
||||
}
|
||||
if (m_selGroupIdx != -1)
|
||||
ListView_SetItemState(m_groupListView, m_selGroupIdx, LVIS_FOCUSED | LVIS_SELECTED, 0xf);
|
||||
if (m_selPageIdx != -1)
|
||||
ListView_SetItemState(m_pageListView, m_selPageIdx, LVIS_FOCUSED | LVIS_SELECTED, 0xf);
|
||||
}
|
||||
|
||||
bool VSTEditor::open(void* ptr)
|
||||
{
|
||||
AEffEditor::open(ptr);
|
||||
HWND hostView = HWND(ptr);
|
||||
gGreyBorderBrush = CreateSolidBrush(RGB(100,100,100));
|
||||
|
||||
WNDCLASSW notifyCls =
|
||||
{
|
||||
CS_HREDRAW | CS_VREDRAW,
|
||||
WindowProc,
|
||||
0,
|
||||
8,
|
||||
HINSTANCE(hInstance),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
L"VSTNotify"
|
||||
};
|
||||
RegisterClassW(¬ifyCls);
|
||||
|
||||
m_rootView = CreateWindowW(L"VSTNotify",
|
||||
L"",
|
||||
WS_CHILD,
|
||||
0, 0,
|
||||
m_windowRect.right,
|
||||
m_windowRect.bottom,
|
||||
hostView,
|
||||
nullptr,
|
||||
HINSTANCE(hInstance),
|
||||
nullptr);
|
||||
SetWindowLongPtrW(m_rootView, 0, LONG_PTR(this));
|
||||
ShowWindow(m_rootView, SW_SHOW);
|
||||
|
||||
TVINSERTSTRUCT treeItem = {};
|
||||
treeItem.hParent = TVI_ROOT;
|
||||
treeItem.hInsertAfter = TVI_LAST;
|
||||
|
||||
treeItem.item.mask = TVIF_CHILDREN | TVIF_TEXT;
|
||||
treeItem.item.cChildren = 1;
|
||||
treeItem.item.pszText = L"Root A";
|
||||
|
||||
LVCOLUMN column = {};
|
||||
column.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH;
|
||||
column.fmt = LVCFMT_LEFT | LVCFMT_FIXED_WIDTH;
|
||||
column.cx = 199;
|
||||
|
||||
m_collectionTree = CreateWindowW(WC_TREEVIEW,
|
||||
L"",
|
||||
WS_CHILD | WS_CLIPSIBLINGS | TVS_SHOWSELALWAYS | TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS,
|
||||
1, 25,
|
||||
199,
|
||||
m_windowRect.bottom - m_windowRect.top - 26,
|
||||
m_rootView,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
TreeView_SetBkColor(m_collectionTree, RGB(64,64,64));
|
||||
TreeView_SetTextColor(m_collectionTree, RGB(255,255,255));
|
||||
HTREEITEM rootItemA = TreeView_InsertItem(m_collectionTree, &treeItem);
|
||||
treeItem.item.pszText = L"Root B";
|
||||
HTREEITEM rootItemB = TreeView_InsertItem(m_collectionTree, &treeItem);
|
||||
treeItem.hParent = rootItemA;
|
||||
treeItem.item.cChildren = 0;
|
||||
treeItem.item.pszText = L"Child A";
|
||||
TreeView_InsertItem(m_collectionTree, &treeItem);
|
||||
treeItem.item.pszText = L"Child B";
|
||||
TreeView_InsertItem(m_collectionTree, &treeItem);
|
||||
treeItem.hParent = rootItemB;
|
||||
treeItem.item.pszText = L"Child A";
|
||||
TreeView_InsertItem(m_collectionTree, &treeItem);
|
||||
treeItem.item.pszText = L"Child B";
|
||||
TreeView_InsertItem(m_collectionTree, &treeItem);
|
||||
ShowWindow(m_collectionTree, SW_SHOW);
|
||||
|
||||
HWND cHeader = CreateWindowW(WC_HEADER,
|
||||
L"",
|
||||
WS_CHILD,
|
||||
1, 1,
|
||||
199,
|
||||
24,
|
||||
m_rootView,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
SetWindowLongPtrW(cHeader, GWLP_USERDATA, LONG_PTR(L"Collection"));
|
||||
OriginalListViewProc = WNDPROC(SetWindowLongPtr(cHeader, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc)));
|
||||
ShowWindow(cHeader, SW_SHOW);
|
||||
|
||||
HWND gHeader = CreateWindowW(WC_HEADER,
|
||||
L"",
|
||||
WS_CHILD,
|
||||
201, 1,
|
||||
199,
|
||||
24,
|
||||
m_rootView,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
SetWindowLongPtrW(gHeader, GWLP_USERDATA, LONG_PTR(L"Group"));
|
||||
OriginalListViewProc = WNDPROC(SetWindowLongPtr(gHeader, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc)));
|
||||
ShowWindow(gHeader, SW_SHOW);
|
||||
|
||||
HWND pHeader = CreateWindowW(WC_HEADER,
|
||||
L"",
|
||||
WS_CHILD,
|
||||
401, 1,
|
||||
198,
|
||||
24,
|
||||
m_rootView,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
SetWindowLongPtrW(pHeader, GWLP_USERDATA, LONG_PTR(L"Page"));
|
||||
OriginalListViewProc = WNDPROC(SetWindowLongPtr(pHeader, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc)));
|
||||
ShowWindow(pHeader, SW_SHOW);
|
||||
|
||||
m_collectionAdd = CreateWindowW(WC_BUTTON,
|
||||
L"+",
|
||||
WS_CHILD | WS_CLIPSIBLINGS | BS_PUSHBUTTON,
|
||||
1, m_windowRect.bottom - m_windowRect.top - 25,
|
||||
25, 24,
|
||||
m_rootView,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
SetWindowFont(m_collectionAdd, GetStockObject(ANSI_FIXED_FONT), FALSE);
|
||||
Button_Enable(m_collectionAdd, TRUE);
|
||||
SetWindowPos(m_collectionAdd, HWND_TOP, 1, m_windowRect.bottom - m_windowRect.top - 25, 25, 24, SWP_SHOWWINDOW);
|
||||
|
||||
m_collectionRemove = CreateWindowW(WC_BUTTON,
|
||||
L"-",
|
||||
WS_CHILD | WS_CLIPSIBLINGS | BS_PUSHBUTTON,
|
||||
26, m_windowRect.bottom - m_windowRect.top - 25,
|
||||
25, 24,
|
||||
m_rootView,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
SetWindowFont(m_collectionRemove, GetStockObject(ANSI_FIXED_FONT), FALSE);
|
||||
Button_Enable(m_collectionRemove, FALSE);
|
||||
SetWindowPos(m_collectionRemove, HWND_TOP, 26, m_windowRect.bottom - m_windowRect.top - 25, 25, 24, SWP_SHOWWINDOW);
|
||||
|
||||
|
||||
m_groupListView = CreateWindowW(WC_LISTVIEW,
|
||||
L"",
|
||||
WS_CHILD | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER,
|
||||
201, 25,
|
||||
199,
|
||||
m_windowRect.bottom - m_windowRect.top - 26,
|
||||
m_rootView,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
column.pszText = L"Group";
|
||||
HWND header = ListView_GetHeader(m_groupListView);
|
||||
SetWindowLongPtrW(header, GWLP_USERDATA, LONG_PTR(column.pszText));
|
||||
SetWindowLongPtr(header, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc));
|
||||
ListView_SetBkColor(m_groupListView, RGB(64,64,64));
|
||||
ListView_SetTextBkColor(m_groupListView, CLR_NONE);
|
||||
ListView_SetTextColor(m_groupListView, RGB(255,255,255));
|
||||
ListView_InsertColumn(m_groupListView, 0, &column);
|
||||
ShowWindow(m_groupListView, SW_SHOW);
|
||||
|
||||
m_pageListView = CreateWindowW(WC_LISTVIEW,
|
||||
L"",
|
||||
WS_CHILD | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER,
|
||||
401, 25,
|
||||
198,
|
||||
m_windowRect.bottom - m_windowRect.top - 26,
|
||||
m_rootView,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
column.pszText = L"Page";
|
||||
column.cx = 198 - GetSystemMetrics(SM_CXVSCROLL);
|
||||
header = ListView_GetHeader(m_pageListView);
|
||||
SetWindowLongPtrW(header, GWLP_USERDATA, LONG_PTR(column.pszText));
|
||||
SetWindowLongPtr(header, GWLP_WNDPROC, LONG_PTR(ColHeaderWindowProc));
|
||||
ListView_SetBkColor(m_pageListView, RGB(64,64,64));
|
||||
ListView_SetTextBkColor(m_pageListView, CLR_NONE);
|
||||
ListView_SetTextColor(m_pageListView, RGB(255,255,255));
|
||||
ListView_InsertColumn(m_pageListView, 0, &column);
|
||||
ShowWindow(m_pageListView, SW_SHOW);
|
||||
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
void VSTEditor::close()
|
||||
{
|
||||
AEffEditor::close();
|
||||
UnregisterClassW(L"VSTNotify", HINSTANCE(hInstance));
|
||||
}
|
||||
|
||||
void VSTEditor::update()
|
||||
{
|
||||
m_backend.getFilePresenter().populateCollectionColumn(*this);
|
||||
m_backend.loadGroupFile(m_selCollectionIdx, m_selFileIdx);
|
||||
m_backend.getFilePresenter().populateGroupColumn(*this, m_selCollectionIdx, m_selFileIdx);
|
||||
m_backend.setGroup(m_selGroupIdx, true);
|
||||
m_backend.getFilePresenter().populatePageColumn(*this, m_selCollectionIdx, m_selFileIdx, m_selGroupIdx);
|
||||
selectPage(m_selPageIdx);
|
||||
_reselectColumns();
|
||||
}
|
||||
|
||||
void VSTEditor::addAction()
|
||||
{
|
||||
std::wstring path = openDB();
|
||||
if (path.size())
|
||||
{
|
||||
amuse::ContainerRegistry::Type containerType;
|
||||
std::vector<std::pair<std::wstring, amuse::IntrusiveAudioGroupData>> data =
|
||||
amuse::ContainerRegistry::LoadContainer(path.c_str(), containerType);
|
||||
if (data.empty())
|
||||
{
|
||||
wchar_t msg[512];
|
||||
SNPrintf(msg, 512, L"Unable to load Audio Groups from %s", path.c_str());
|
||||
MessageBoxW(nullptr, msg, L"Invalid Data File", MB_OK | MB_ICONERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
SystemString name(amuse::ContainerRegistry::TypeToName(containerType));
|
||||
if (containerType == amuse::ContainerRegistry::Type::Raw4)
|
||||
{
|
||||
size_t dotpos = path.rfind(L'.');
|
||||
if (dotpos != std::string::npos)
|
||||
name.assign(path.cbegin(), path.cbegin() + dotpos);
|
||||
size_t slashpos = name.rfind(L'\\');
|
||||
size_t fslashpos = name.rfind(L'/');
|
||||
if (slashpos == std::string::npos)
|
||||
slashpos = fslashpos;
|
||||
else if (fslashpos != std::string::npos)
|
||||
slashpos = std::max(slashpos, fslashpos);
|
||||
if (slashpos != std::string::npos)
|
||||
name.assign(name.cbegin() + slashpos + 1, name.cend());
|
||||
}
|
||||
|
||||
m_backend.getFilePresenter().addCollection(name, std::move(data));
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void VSTEditor::removeAction()
|
||||
{
|
||||
if (m_selCollectionIdx == -1)
|
||||
return;
|
||||
m_backend.getFilePresenter().removeCollection(m_selCollectionIdx);
|
||||
m_backend.getFilePresenter().populateCollectionColumn(*this);
|
||||
m_selCollectionIdx = -1;
|
||||
m_selFileIdx = -1;
|
||||
m_selGroupIdx = -1;
|
||||
m_backend.getFilePresenter().populateGroupColumn(*this, m_selCollectionIdx, m_selFileIdx);
|
||||
m_backend.getFilePresenter().populatePageColumn(*this, m_selCollectionIdx, m_selFileIdx, m_selGroupIdx);
|
||||
Button_Enable(m_collectionRemove, FALSE);
|
||||
}
|
||||
|
||||
void VSTEditor::selectCollection(LPARAM idx)
|
||||
{
|
||||
if (0x80000000 & idx)
|
||||
{
|
||||
/* Sub-item */
|
||||
int rootIdx = (idx >> 16) & 0x7fff;
|
||||
int subIdx = idx & 0xffff;
|
||||
Button_Enable(m_collectionRemove, FALSE);
|
||||
m_selCollectionIdx = rootIdx;
|
||||
m_selFileIdx = subIdx;
|
||||
m_backend.loadGroupFile(m_selCollectionIdx, m_selFileIdx);
|
||||
m_backend.getFilePresenter().populateGroupColumn(*this, rootIdx, subIdx);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Root-item */
|
||||
int rootIdx = (idx >> 16) & 0x7fff;
|
||||
m_selCollectionIdx = rootIdx;
|
||||
Button_Enable(m_collectionRemove, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void VSTEditor::selectGroup(int idx)
|
||||
{
|
||||
m_selGroupIdx = idx;
|
||||
m_backend.setGroup(m_selGroupIdx, false);
|
||||
m_backend.getFilePresenter().populatePageColumn(*this, m_selCollectionIdx, m_selFileIdx, m_selGroupIdx);
|
||||
m_lastLParam = -1;
|
||||
}
|
||||
|
||||
void VSTEditor::selectPage(int idx)
|
||||
{
|
||||
m_selPageIdx = idx;
|
||||
LV_ITEM item = {};
|
||||
item.mask = LVIF_PARAM;
|
||||
item.iItem = idx;
|
||||
ListView_GetItem(m_pageListView, &item);
|
||||
m_lastLParam = item.lParam;
|
||||
if (item.lParam & 0x80000000)
|
||||
selectDrumPage(item.lParam & 0x7fffffff);
|
||||
else
|
||||
selectNormalPage(item.lParam & 0x7fffffff);
|
||||
}
|
||||
|
||||
void VSTEditor::reselectPage()
|
||||
{
|
||||
if (m_lastLParam != -1)
|
||||
{
|
||||
if (m_lastLParam & 0x80000000)
|
||||
m_backend._setDrumProgram(m_lastLParam & 0x7fffffff);
|
||||
else
|
||||
m_backend._setNormalProgram(m_lastLParam & 0x7fffffff);
|
||||
}
|
||||
}
|
||||
|
||||
void VSTEditor::selectNormalPage(int idx)
|
||||
{
|
||||
m_backend.setNormalProgram(idx);
|
||||
}
|
||||
|
||||
void VSTEditor::selectDrumPage(int idx)
|
||||
{
|
||||
m_backend.setDrumProgram(idx);
|
||||
}
|
||||
|
||||
}
|
||||
74
VST/VSTEditor.hpp
Normal file
74
VST/VSTEditor.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef __AMUSE_VSTEDITOR_HPP__
|
||||
#define __AMUSE_VSTEDITOR_HPP__
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <commctrl.h>
|
||||
|
||||
#include "aeffeditor.h"
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
class VSTBackend;
|
||||
|
||||
/** Editor UI class */
|
||||
class VSTEditor : public AEffEditor
|
||||
{
|
||||
friend class VSTBackend;
|
||||
friend class AudioGroupFilePresenter;
|
||||
friend struct AudioGroupCollection;
|
||||
|
||||
VSTBackend& m_backend;
|
||||
ERect m_windowRect = {0, 0, 420, 600};
|
||||
|
||||
HWND m_rootView = 0;
|
||||
HWND m_collectionTree = 0;
|
||||
HWND m_collectionAdd = 0;
|
||||
HWND m_collectionRemove = 0;
|
||||
HWND m_groupListView = 0;
|
||||
HWND m_pageListView = 0;
|
||||
|
||||
int m_selCollectionIdx = -1;
|
||||
int m_selFileIdx = -1;
|
||||
int m_selGroupIdx = -1;
|
||||
int m_selPageIdx = -1;
|
||||
int m_lastLParam = -1;
|
||||
|
||||
HTREEITEM m_deferredCollectionSel = 0;
|
||||
|
||||
static LRESULT CALLBACK WindowProc(
|
||||
_In_ HWND hwnd,
|
||||
_In_ UINT uMsg,
|
||||
_In_ WPARAM wParam,
|
||||
_In_ LPARAM lParam
|
||||
);
|
||||
static LRESULT CALLBACK ColHeaderWindowProc(
|
||||
_In_ HWND hwnd,
|
||||
_In_ UINT uMsg,
|
||||
_In_ WPARAM wParam,
|
||||
_In_ LPARAM lParam
|
||||
);
|
||||
|
||||
void _reselectColumns();
|
||||
public:
|
||||
VSTEditor(VSTBackend& backend);
|
||||
|
||||
bool getRect(ERect** rect);
|
||||
bool open(void* ptr);
|
||||
void close();
|
||||
void update();
|
||||
|
||||
void addAction();
|
||||
void removeAction();
|
||||
|
||||
void selectCollection(LPARAM idx);
|
||||
void selectGroup(int idx);
|
||||
void selectPage(int idx);
|
||||
void reselectPage();
|
||||
void selectNormalPage(int idx);
|
||||
void selectDrumPage(int idx);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __AMUSE_VSTEDITOR_HPP__
|
||||
217
driver/amuseconv.cpp
Normal file
217
driver/amuseconv.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "amuse/amuse.hpp"
|
||||
#include "athena/FileReader.hpp"
|
||||
#include "athena/DNAYaml.hpp"
|
||||
#include "logvisor/logvisor.hpp"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static logvisor::Module Log("amuseconv");
|
||||
|
||||
enum ConvType
|
||||
{
|
||||
ConvN64,
|
||||
ConvGCN,
|
||||
ConvPC
|
||||
};
|
||||
|
||||
static void ReportConvType(ConvType tp)
|
||||
{
|
||||
switch (tp)
|
||||
{
|
||||
case ConvN64:
|
||||
Log.report(logvisor::Info, _S("using N64 format"));
|
||||
break;
|
||||
case ConvPC:
|
||||
Log.report(logvisor::Info, _S("using PC format"));
|
||||
break;
|
||||
case ConvGCN:
|
||||
default:
|
||||
Log.report(logvisor::Info, _S("using GameCube format"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool BuildAudioGroup(const amuse::SystemString& groupBase, const amuse::SystemString& targetPath)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ExtractAudioGroup(const amuse::SystemString& inPath, const amuse::SystemString& targetPath)
|
||||
{
|
||||
amuse::ContainerRegistry::Type type;
|
||||
auto groups = amuse::ContainerRegistry::LoadContainer(inPath.c_str(), type);
|
||||
|
||||
if (groups.size())
|
||||
{
|
||||
Log.report(logvisor::Info, _S("Found '%s'"), amuse::ContainerRegistry::TypeToName(type));
|
||||
|
||||
amuse::Mkdir(targetPath.c_str(), 0755);
|
||||
Log.report(logvisor::Info, _S("Established directory at %s"), targetPath.c_str());
|
||||
|
||||
for (auto& group : groups)
|
||||
{
|
||||
Log.report(logvisor::Info, _S("Extracting %s"), group.first.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto songs = amuse::ContainerRegistry::LoadSongs(inPath.c_str());
|
||||
amuse::SystemString songsDir = targetPath + _S("/midifiles");
|
||||
bool madeDir = false;
|
||||
for (auto& pair : songs)
|
||||
{
|
||||
if (!madeDir)
|
||||
{
|
||||
amuse::Mkdir(targetPath.c_str(), 0755);
|
||||
amuse::Mkdir(songsDir.c_str(), 0755);
|
||||
madeDir = true;
|
||||
}
|
||||
|
||||
amuse::SystemString songPath = songsDir + _S('/') + pair.first + _S(".mid");
|
||||
FILE* fp = amuse::FOpen(songPath.c_str(), _S("wb"));
|
||||
if (fp)
|
||||
{
|
||||
Log.report(logvisor::Info, _S("Extracting %s"), pair.first.c_str());
|
||||
int extractedVersion;
|
||||
bool isBig;
|
||||
std::vector<uint8_t> mid = amuse::SongConverter::SongToMIDI(pair.second.m_data.get(), extractedVersion, isBig);
|
||||
fwrite(mid.data(), 1, mid.size(), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool BuildSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath, int version, bool big)
|
||||
{
|
||||
FILE* fp = amuse::FOpen(inPath.c_str(), _S("rb"));
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
long sz = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
std::vector<uint8_t> data(sz, 0);
|
||||
fread(&data[0], 1, sz, fp);
|
||||
fclose(fp);
|
||||
|
||||
std::vector<uint8_t> out = amuse::SongConverter::MIDIToSong(data, version, big);
|
||||
if (out.empty())
|
||||
return false;
|
||||
|
||||
fp = amuse::FOpen(targetPath.c_str(), _S("wb"));
|
||||
fwrite(out.data(), 1, out.size(), fp);
|
||||
fclose(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ExtractSNG(const amuse::SystemString& inPath, const amuse::SystemString& targetPath)
|
||||
{
|
||||
FILE* fp = amuse::FOpen(inPath.c_str(), _S("rb"));
|
||||
if (!fp)
|
||||
return false;
|
||||
|
||||
fseek(fp, 0, SEEK_END);
|
||||
long sz = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
std::vector<uint8_t> data(sz, 0);
|
||||
fread(&data[0], 1, sz, fp);
|
||||
fclose(fp);
|
||||
|
||||
int extractedVersion;
|
||||
bool isBig;
|
||||
std::vector<uint8_t> out = amuse::SongConverter::SongToMIDI(data.data(), extractedVersion, isBig);
|
||||
if (out.empty())
|
||||
return false;
|
||||
|
||||
fp = amuse::FOpen(targetPath.c_str(), _S("wb"));
|
||||
fwrite(out.data(), 1, out.size(), fp);
|
||||
fclose(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
int wmain(int argc, const amuse::SystemChar** argv)
|
||||
#else
|
||||
int main(int argc, const amuse::SystemChar** argv)
|
||||
#endif
|
||||
{
|
||||
logvisor::RegisterConsoleLogger();
|
||||
|
||||
if (argc < 3)
|
||||
{
|
||||
printf("Usage: amuseconv <in-file> <out-file> [n64|pc|gcn]\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ConvType type = ConvGCN;
|
||||
if (argc >= 4)
|
||||
{
|
||||
if (!amuse::CompareCaseInsensitive(argv[3], _S("n64")))
|
||||
type = ConvN64;
|
||||
else if (!amuse::CompareCaseInsensitive(argv[3], _S("gcn")))
|
||||
type = ConvGCN;
|
||||
else if (!amuse::CompareCaseInsensitive(argv[3], _S("pc")))
|
||||
type = ConvPC;
|
||||
else
|
||||
{
|
||||
Log.report(logvisor::Error, _S("unrecognized format: %s"), argv[3]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool good = false;
|
||||
FILE* fin = amuse::FOpen(argv[1], _S("rb"));
|
||||
if (fin)
|
||||
{
|
||||
fclose(fin);
|
||||
amuse::SystemString barePath(argv[1]);
|
||||
size_t dotPos = barePath.rfind(_S('.'));
|
||||
const amuse::SystemChar* dot = barePath.c_str() + dotPos;
|
||||
if (dotPos != amuse::SystemString::npos)
|
||||
{
|
||||
if (!amuse::CompareCaseInsensitive(dot, _S(".mid")) ||
|
||||
!amuse::CompareCaseInsensitive(dot, _S(".midi")))
|
||||
{
|
||||
ReportConvType(type);
|
||||
good = BuildSNG(barePath, argv[2], 1, true);
|
||||
}
|
||||
else if (!amuse::CompareCaseInsensitive(dot, _S(".son")) ||
|
||||
!amuse::CompareCaseInsensitive(dot, _S(".sng")))
|
||||
{
|
||||
good = ExtractSNG(argv[1], argv[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
good = ExtractAudioGroup(argv[1], argv[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
amuse::Sstat theStat;
|
||||
if (!amuse::Stat(argv[1], &theStat) && S_ISDIR(theStat.st_mode))
|
||||
{
|
||||
amuse::SystemString projectPath(argv[1]);
|
||||
projectPath += _S("/project.yaml");
|
||||
fin = amuse::FOpen(projectPath.c_str(), _S("rb"));
|
||||
if (fin)
|
||||
{
|
||||
fclose(fin);
|
||||
ReportConvType(type);
|
||||
good = BuildAudioGroup(argv[1], argv[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!good)
|
||||
{
|
||||
Log.report(logvisor::Error, _S("unable to convert %s to %s"), argv[1], argv[2]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -639,23 +639,16 @@ struct AppCallback : boo::IApplicationCallback
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
char utf8Path[1024];
|
||||
WideCharToMultiByte(CP_UTF8, 0, m_argv[1], -1, utf8Path, 1024, nullptr, nullptr);
|
||||
#else
|
||||
const char* utf8Path = m_argv[1];
|
||||
#endif
|
||||
|
||||
amuse::ContainerRegistry::Type cType = amuse::ContainerRegistry::DetectContainerType(utf8Path);
|
||||
amuse::ContainerRegistry::Type cType = amuse::ContainerRegistry::DetectContainerType(m_argv[1]);
|
||||
if (cType == amuse::ContainerRegistry::Type::Invalid)
|
||||
{
|
||||
Log.report(logvisor::Error, "invalid/no data at path argument");
|
||||
exit(1);
|
||||
}
|
||||
Log.report(logvisor::Info, "Found '%s' Audio Group data", amuse::ContainerRegistry::TypeToName(cType));
|
||||
Log.report(logvisor::Info, _S("Found '%s' Audio Group data"), amuse::ContainerRegistry::TypeToName(cType));
|
||||
|
||||
std::vector<std::pair<std::string, amuse::IntrusiveAudioGroupData>> data =
|
||||
amuse::ContainerRegistry::LoadContainer(utf8Path);
|
||||
std::vector<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>> data =
|
||||
amuse::ContainerRegistry::LoadContainer(m_argv[1]);
|
||||
if (data.empty())
|
||||
{
|
||||
Log.report(logvisor::Error, "invalid/no data at path argument");
|
||||
@@ -663,8 +656,8 @@ struct AppCallback : boo::IApplicationCallback
|
||||
}
|
||||
|
||||
std::list<amuse::AudioGroupProject> m_projs;
|
||||
std::map<int, std::pair<std::pair<std::string, amuse::IntrusiveAudioGroupData>*, const amuse::SongGroupIndex*>> allSongGroups;
|
||||
std::map<int, std::pair<std::pair<std::string, amuse::IntrusiveAudioGroupData>*, const amuse::SFXGroupIndex*>> allSFXGroups;
|
||||
std::map<int, std::pair<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>*, const amuse::SongGroupIndex*>> allSongGroups;
|
||||
std::map<int, std::pair<std::pair<amuse::SystemString, amuse::IntrusiveAudioGroupData>*, const amuse::SFXGroupIndex*>> allSFXGroups;
|
||||
size_t totalGroups = 0;
|
||||
|
||||
for (auto& grp : data)
|
||||
@@ -687,19 +680,11 @@ struct AppCallback : boo::IApplicationCallback
|
||||
m_setupId = -1;
|
||||
|
||||
/* Attempt loading song */
|
||||
std::vector<std::pair<std::string, amuse::ContainerRegistry::SongData>> songs;
|
||||
std::vector<std::pair<amuse::SystemString, amuse::ContainerRegistry::SongData>> songs;
|
||||
if (m_argc > 2)
|
||||
{
|
||||
#if _WIN32
|
||||
char utf8Path[1024];
|
||||
WideCharToMultiByte(CP_UTF8, 0, m_argv[2], -1, utf8Path, 1024, nullptr, nullptr);
|
||||
#else
|
||||
const char* utf8Path = m_argv[2];
|
||||
#endif
|
||||
songs = amuse::ContainerRegistry::LoadSongs(utf8Path);
|
||||
}
|
||||
songs = amuse::ContainerRegistry::LoadSongs(m_argv[2]);
|
||||
else
|
||||
songs = amuse::ContainerRegistry::LoadSongs(utf8Path);
|
||||
songs = amuse::ContainerRegistry::LoadSongs(m_argv[1]);
|
||||
|
||||
if (songs.size())
|
||||
{
|
||||
@@ -739,8 +724,8 @@ struct AppCallback : boo::IApplicationCallback
|
||||
int idx = 0;
|
||||
for (const auto& pair : songs)
|
||||
{
|
||||
printf(" %d %s (Group %d, Setup %d)\n", idx++,
|
||||
pair.first.c_str(), pair.second.m_groupId, pair.second.m_setupId);
|
||||
amuse::Printf(_S(" %d %s (Group %d, Setup %d)\n"), idx++,
|
||||
pair.first.c_str(), pair.second.m_groupId, pair.second.m_setupId);
|
||||
}
|
||||
|
||||
int userSel = 0;
|
||||
@@ -791,15 +776,17 @@ struct AppCallback : boo::IApplicationCallback
|
||||
printf("Multiple Audio Groups discovered:\n");
|
||||
for (const auto& pair : allSFXGroups)
|
||||
{
|
||||
printf(" %d %s (SFXGroup) %" PRISize " sfx-entries\n",
|
||||
pair.first, pair.second.first->first.c_str(),
|
||||
pair.second.second->m_sfxEntries.size());
|
||||
amuse::Printf(_S(" %d %s (SFXGroup) %" PRISize " sfx-entries\n"),
|
||||
pair.first, pair.second.first->first.c_str(),
|
||||
pair.second.second->m_sfxEntries.size());
|
||||
}
|
||||
for (const auto& pair : allSongGroups)
|
||||
{
|
||||
printf(" %d %s (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages\n",
|
||||
pair.first, pair.second.first->first.c_str(),
|
||||
pair.second.second->m_normPages.size(), pair.second.second->m_drumPages.size());
|
||||
amuse::Printf(_S(" %d %s (SongGroup) %" PRISize " normal-pages, %" PRISize " drum-pages, %" PRISize " MIDI-setups\n"),
|
||||
pair.first, pair.second.first->first.c_str(),
|
||||
pair.second.second->m_normPages.size(),
|
||||
pair.second.second->m_drumPages.size(),
|
||||
pair.second.second->m_midiSetups.size());
|
||||
}
|
||||
|
||||
int userSel = 0;
|
||||
@@ -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;}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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;}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "IBackendVoiceAllocator.hpp"
|
||||
#include <mutex>
|
||||
#include <list>
|
||||
#include <chrono>
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
@@ -55,6 +54,7 @@ class BooBackendSubmix : public IBackendSubmix
|
||||
const boo::ChannelMap& chanMap, double sampleRate) const;
|
||||
void applyEffect(float* audio, size_t frameCount,
|
||||
const boo::ChannelMap& chanMap, double sampleRate) const;
|
||||
void resetOutputSampleRate(double sampleRate);
|
||||
SubmixCallback(BooBackendSubmix& parent) : m_parent(parent) {}
|
||||
} m_cb;
|
||||
std::unique_ptr<boo::IAudioSubmix> m_booSubmix;
|
||||
@@ -75,13 +75,14 @@ class BooBackendMIDIReader : public IMIDIReader, public boo::IMIDIReader
|
||||
std::unique_ptr<boo::IMIDIIn> m_midiIn;
|
||||
boo::MIDIDecoder m_decoder;
|
||||
|
||||
std::list<std::pair<std::chrono::steady_clock::time_point, std::vector<uint8_t>>> m_queue;
|
||||
bool m_useLock;
|
||||
std::list<std::pair<double, std::vector<uint8_t>>> m_queue;
|
||||
std::mutex m_midiMutex;
|
||||
void _MIDIReceive(std::vector<uint8_t>&& bytes);
|
||||
void _MIDIReceive(std::vector<uint8_t>&& bytes, double time);
|
||||
|
||||
public:
|
||||
~BooBackendMIDIReader();
|
||||
BooBackendMIDIReader(Engine& engine, const char* name);
|
||||
BooBackendMIDIReader(Engine& engine, const char* name, bool useLock);
|
||||
|
||||
std::string description();
|
||||
void pumpReader(double dt);
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <strings.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
namespace amuse
|
||||
@@ -21,10 +25,37 @@ namespace amuse
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
using SystemString = std::wstring;
|
||||
using SystemChar = wchar_t;
|
||||
# ifndef _S
|
||||
# define _S(val) L ## val
|
||||
# endif
|
||||
typedef struct _stat Sstat;
|
||||
static inline int Mkdir(const wchar_t* path, int) {return _wmkdir(path);}
|
||||
static inline int Stat(const wchar_t* path, Sstat* statout) {return _wstat(path, statout);}
|
||||
#else
|
||||
using SystemString = std::string;
|
||||
using SystemChar = char;
|
||||
# ifndef _S
|
||||
# define _S(val) val
|
||||
# endif
|
||||
typedef struct stat Sstat;
|
||||
static inline int Mkdir(const char* path, mode_t mode) {return mkdir(path, mode);}
|
||||
static inline int Stat(const char* path, Sstat* statout) {return stat(path, statout);}
|
||||
#endif
|
||||
|
||||
#if _WIN32
|
||||
static inline int CompareCaseInsensitive(const char* a, const char* b)
|
||||
{
|
||||
#if _WIN32
|
||||
return _stricmp(a, b);
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline int CompareCaseInsensitive(const SystemChar* a, const SystemChar* b)
|
||||
{
|
||||
#if _WIN32
|
||||
return _wcsicmp(a, b);
|
||||
#else
|
||||
return strcasecmp(a, b);
|
||||
#endif
|
||||
@@ -59,10 +90,6 @@ inline int32_t ClampFull<int32_t>(float in)
|
||||
template <>
|
||||
inline float ClampFull<float>(float in)
|
||||
{
|
||||
if (in < -1.f)
|
||||
return -1.f;
|
||||
else if (in > 1.f)
|
||||
return 1.f;
|
||||
return in;
|
||||
}
|
||||
|
||||
@@ -70,6 +97,54 @@ inline float ClampFull<float>(float in)
|
||||
#define M_PIF 3.14159265358979323846f /* pi */
|
||||
#endif
|
||||
|
||||
#if __GNUC__
|
||||
__attribute__((__format__ (__printf__, 1, 2)))
|
||||
#endif
|
||||
static inline void Printf(const SystemChar* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
#if _WIN32
|
||||
vwprintf(fmt, args);
|
||||
#else
|
||||
vprintf(fmt, args);
|
||||
#endif
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#if __GNUC__
|
||||
__attribute__((__format__ (__printf__, 3, 4)))
|
||||
#endif
|
||||
static inline void SNPrintf(SystemChar* str, size_t maxlen, const SystemChar* format, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, format);
|
||||
#if _WIN32
|
||||
_vsnwprintf(str, maxlen, format, va);
|
||||
#else
|
||||
vsnprintf(str, maxlen, format, va);
|
||||
#endif
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
static inline const SystemChar* StrRChr(const SystemChar* str, SystemChar ch)
|
||||
{
|
||||
#if _WIN32
|
||||
return wcsrchr(str, ch);
|
||||
#else
|
||||
return strrchr(str, ch);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline SystemChar* StrRChr(SystemChar* str, SystemChar ch)
|
||||
{
|
||||
#if _WIN32
|
||||
return wcsrchr(str, ch);
|
||||
#else
|
||||
return strrchr(str, ch);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int FSeek(FILE* fp, int64_t offset, int whence)
|
||||
{
|
||||
#if _WIN32
|
||||
@@ -92,6 +167,20 @@ static inline int64_t FTell(FILE* fp)
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline FILE* FOpen(const SystemChar* path, const SystemChar* mode)
|
||||
{
|
||||
#if _WIN32
|
||||
FILE* fp = _wfopen(path, mode);
|
||||
if (!fp)
|
||||
return nullptr;
|
||||
#else
|
||||
FILE* fp = fopen(path, mode);
|
||||
if (!fp)
|
||||
return nullptr;
|
||||
#endif
|
||||
return fp;
|
||||
}
|
||||
|
||||
#undef bswap16
|
||||
#undef bswap32
|
||||
#undef bswap64
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define __AMUSE_CONTAINERREGISTRY_HPP__
|
||||
|
||||
#include "AudioGroupData.hpp"
|
||||
#include "Common.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
@@ -34,10 +35,11 @@ public:
|
||||
SongData(std::unique_ptr<uint8_t[]>&& data, size_t size, int16_t groupId, int16_t setupId)
|
||||
: m_data(std::move(data)), m_size(size), m_groupId(groupId), m_setupId(setupId) {}
|
||||
};
|
||||
static const char* TypeToName(Type tp);
|
||||
static Type DetectContainerType(const char* path);
|
||||
static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadContainer(const char* path);
|
||||
static std::vector<std::pair<std::string, SongData>> LoadSongs(const char* path);
|
||||
static const SystemChar* TypeToName(Type tp);
|
||||
static Type DetectContainerType(const SystemChar* path);
|
||||
static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadContainer(const SystemChar* path);
|
||||
static std::vector<std::pair<SystemString, IntrusiveAudioGroupData>> LoadContainer(const SystemChar* path, Type& typeOut);
|
||||
static std::vector<std::pair<SystemString, SongData>> LoadSongs(const SystemChar* path);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
74
include/amuse/DirectoryEnumerator.hpp
Normal file
74
include/amuse/DirectoryEnumerator.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef __AMUSE_DIRECTORY_ENUMERATOR__
|
||||
#define __AMUSE_DIRECTORY_ENUMERATOR__
|
||||
|
||||
#include "Common.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
struct CaseInsensitiveCompare
|
||||
{
|
||||
bool operator()(const std::string& lhs, const std::string& rhs) const
|
||||
{
|
||||
#if _WIN32
|
||||
if (_stricmp(lhs.c_str(), rhs.c_str()) < 0)
|
||||
#else
|
||||
if (strcasecmp(lhs.c_str(), rhs.c_str()) < 0)
|
||||
#endif
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
#if _WIN32
|
||||
bool operator()(const std::wstring& lhs, const std::wstring& rhs) const
|
||||
{
|
||||
if (_wcsicmp(lhs.c_str(), rhs.c_str()) < 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class DirectoryEnumerator
|
||||
{
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
Native,
|
||||
DirsSorted,
|
||||
FilesSorted,
|
||||
DirsThenFilesSorted
|
||||
};
|
||||
struct Entry
|
||||
{
|
||||
SystemString m_path;
|
||||
SystemString m_name;
|
||||
size_t m_fileSz;
|
||||
bool m_isDir;
|
||||
|
||||
private:
|
||||
friend class DirectoryEnumerator;
|
||||
Entry(SystemString&& path, const SystemChar* name, size_t sz, bool isDir)
|
||||
: m_path(std::move(path)), m_name(name), m_fileSz(sz), m_isDir(isDir) {}
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<Entry> m_entries;
|
||||
|
||||
public:
|
||||
DirectoryEnumerator(const SystemString& path, Mode mode=Mode::DirsThenFilesSorted,
|
||||
bool sizeSort=false, bool reverse=false, bool noHidden=false)
|
||||
: DirectoryEnumerator(path.c_str(), mode, sizeSort, reverse, noHidden) {}
|
||||
DirectoryEnumerator(const SystemChar* path, Mode mode=Mode::DirsThenFilesSorted,
|
||||
bool sizeSort=false, bool reverse=false, bool noHidden=false);
|
||||
|
||||
operator bool() const {return m_entries.size() != 0;}
|
||||
size_t size() const {return m_entries.size();}
|
||||
std::vector<Entry>::const_iterator begin() const {return m_entries.cbegin();}
|
||||
std::vector<Entry>::const_iterator end() const {return m_entries.cend();}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __AMUSE_DIRECTORY_ENUMERATOR__
|
||||
@@ -12,6 +12,7 @@ class EffectBaseTypeless
|
||||
{
|
||||
public:
|
||||
virtual ~EffectBaseTypeless() = default;
|
||||
virtual void resetOutputSampleRate(double sampleRate)=0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
template <typename T>
|
||||
class EffectChorusImp : public EffectBase<T>, public EffectChorus
|
||||
{
|
||||
T* x0_lastChans[8][AMUSE_CHORUS_NUM_BLOCKS]; /**< Evenly-allocated pointer-table for each channel's delay */
|
||||
T* x0_lastChans[8][AMUSE_CHORUS_NUM_BLOCKS] = {}; /**< Evenly-allocated pointer-table for each channel's delay */
|
||||
|
||||
uint8_t x24_currentLast = 1; /**< Last 5ms block-idx to be processed */
|
||||
T x28_oldChans[8][4] = {}; /**< Unprocessed history of previous 4 samples */
|
||||
@@ -84,12 +84,14 @@ class EffectChorusImp : public EffectBase<T>, public EffectChorus
|
||||
uint32_t m_sampsPerMs; /**< canonical count of samples per ms for the current backend */
|
||||
uint32_t m_blockSamples; /**< count of samples in a 5ms block */
|
||||
|
||||
void _setup(double sampleRate);
|
||||
void _update();
|
||||
|
||||
public:
|
||||
~EffectChorusImp();
|
||||
EffectChorusImp(uint32_t baseDelay, uint32_t variation, uint32_t period, double sampleRate);
|
||||
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
|
||||
void resetOutputSampleRate(double sampleRate) {_setup(sampleRate);}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -80,10 +80,12 @@ class EffectDelayImp : public EffectBase<T>, public EffectDelay
|
||||
|
||||
uint32_t m_sampsPerMs; /**< canonical count of samples per ms for the current backend */
|
||||
uint32_t m_blockSamples; /**< count of samples in a 5ms block */
|
||||
void _setup(double sampleRate);
|
||||
void _update();
|
||||
public:
|
||||
EffectDelayImp(uint32_t initDelay, uint32_t initFeedback, uint32_t initOutput, double sampleRate);
|
||||
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
|
||||
void resetOutputSampleRate(double sampleRate) {_setup(sampleRate);}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -115,11 +115,13 @@ class EffectReverbStdImp : public EffectBase<T>, public EffectReverbStd
|
||||
float* x130_preDelayPtr[8] = {}; /**< Current pre-delay pointers */
|
||||
|
||||
double m_sampleRate; /**< copy of sample rate */
|
||||
void _setup(double sampleRate);
|
||||
void _update();
|
||||
public:
|
||||
EffectReverbStdImp(float coloration, float mix, float time,
|
||||
float damping, float preDelay, double sampleRate);
|
||||
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
|
||||
void resetOutputSampleRate(double sampleRate) {_setup(sampleRate);}
|
||||
};
|
||||
|
||||
/** High-quality 3-stage reverb with per-channel low-pass and crosstalk */
|
||||
@@ -140,6 +142,7 @@ class EffectReverbHiImp : public EffectBase<T>, public EffectReverbHi
|
||||
float x1a8_internalCrosstalk = 0.f;
|
||||
|
||||
double m_sampleRate; /**< copy of sample rate */
|
||||
void _setup(double sampleRate);
|
||||
void _update();
|
||||
void _handleReverb(T* audio, int chanIdx, int chanCount, int sampleCount);
|
||||
void _doCrosstalk(T* audio, float wet, float dry, int chanCount, int sampleCount);
|
||||
@@ -147,6 +150,7 @@ public:
|
||||
EffectReverbHiImp(float coloration, float mix, float time,
|
||||
float damping, float preDelay, float crosstalk, double sampleRate);
|
||||
void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap);
|
||||
void resetOutputSampleRate(double sampleRate) {_setup(sampleRate);}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
class Voice;
|
||||
|
||||
/** Per-sample state tracker for ADSR envelope data */
|
||||
class Envelope
|
||||
@@ -26,12 +27,16 @@ private:
|
||||
double m_releaseTime = 0.02; /**< Time of release in seconds */
|
||||
double m_releaseStartFactor = 0.0; /**< Level at whenever release event occurs */
|
||||
double m_curTime = 0.0; /**< Current time of envelope stage in seconds */
|
||||
bool m_adsrSet = false;
|
||||
public:
|
||||
void reset(const ADSR* adsr);
|
||||
void reset(const ADSRDLS* adsr, int8_t note, int8_t vel);
|
||||
void keyOff(const Voice& vox);
|
||||
void keyOff();
|
||||
float advance(double dt, const Voice& vox);
|
||||
float advance(double dt);
|
||||
bool isComplete() const {return m_phase == State::Complete;}
|
||||
bool isAdsrSet() const {return m_adsrSet;}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ enum class SequencerState
|
||||
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* m_songGroup = nullptr; /**< Quick access to song group project index */
|
||||
const SongGroupIndex::MIDISetup* m_midiSetup = nullptr; /**< Selected MIDI setup (may be null) */
|
||||
const SFXGroupIndex* m_sfxGroup = nullptr; /**< SFX Groups are alternatively referenced here */
|
||||
std::vector<const SFXGroupIndex::SFXEntry*> m_sfxMappings; /**< SFX entries are mapped to MIDI keys this via this */
|
||||
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 +46,7 @@ class Sequencer : public Entity
|
||||
{
|
||||
Sequencer& m_parent;
|
||||
uint8_t m_chanId;
|
||||
const SongGroupIndex::MIDISetup& m_setup;
|
||||
const SongGroupIndex::MIDISetup* m_setup = nullptr; /* Channel defaults to program 0 if null */
|
||||
const SongGroupIndex::PageEntry* m_page = nullptr;
|
||||
~ChannelState();
|
||||
ChannelState(Sequencer& parent, uint8_t chanId);
|
||||
@@ -52,6 +54,7 @@ class Sequencer : public Entity
|
||||
/** Voices corresponding to currently-pressed keys in channel */
|
||||
std::unordered_map<uint8_t, std::shared_ptr<Voice>> m_chanVoxs;
|
||||
std::unordered_set<std::shared_ptr<Voice>> m_keyoffVoxs;
|
||||
std::weak_ptr<Voice> m_lastVoice;
|
||||
int8_t m_ctrlVals[128] = {}; /**< MIDI controller values */
|
||||
float m_curPitchWheel = 0.f; /**< MIDI pitch-wheel */
|
||||
int8_t m_curProgram = 0; /**< MIDI program number */
|
||||
@@ -78,10 +81,13 @@ class Sequencer : public Entity
|
||||
|
||||
void _bringOutYourDead();
|
||||
void _destroy();
|
||||
|
||||
public:
|
||||
~Sequencer();
|
||||
Sequencer(Engine& engine, const AudioGroup& group, int groupId,
|
||||
const SongGroupIndex& songGroup, int setupId, Submix* smx);
|
||||
const SongGroupIndex* songGroup, int setupId, Submix* smx);
|
||||
Sequencer(Engine& engine, const AudioGroup& group, int groupId,
|
||||
const SFXGroupIndex* sfxGroup, Submix* smx);
|
||||
|
||||
/** Advance current song data (if any) */
|
||||
void advance(double dt);
|
||||
|
||||
19
include/amuse/SongConverter.hpp
Normal file
19
include/amuse/SongConverter.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef __AMUSE_SONGCONVERTER_HPP__
|
||||
#define __AMUSE_SONGCONVERTER_HPP__
|
||||
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
class SongConverter
|
||||
{
|
||||
public:
|
||||
static std::vector<uint8_t> SongToMIDI(const unsigned char* data, int& versionOut, bool& isBig);
|
||||
static std::vector<uint8_t> MIDIToSong(const std::vector<uint8_t>& data, int version, bool big);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __AMUSE_SONGCONVERTER_HPP__
|
||||
@@ -22,34 +22,30 @@ enum class SongPlayState
|
||||
class SongState
|
||||
{
|
||||
friend class Voice;
|
||||
friend class SongConverter;
|
||||
|
||||
/** Song header */
|
||||
struct Header
|
||||
{
|
||||
uint32_t m_version;
|
||||
uint32_t m_chanIdxOff;
|
||||
uint32_t m_trackIdxOff;
|
||||
uint32_t m_regionIdxOff;
|
||||
uint32_t m_chanMapOff;
|
||||
uint32_t m_tempoTableOff;
|
||||
uint32_t m_initialTempo;
|
||||
uint32_t m_unkOff;
|
||||
uint32_t m_chanOffs[64];
|
||||
void swapBig();
|
||||
} m_header;
|
||||
|
||||
/** Channel header */
|
||||
struct ChanHeader
|
||||
/** Track region ('clip' in an NLA representation) */
|
||||
struct TrackRegion
|
||||
{
|
||||
uint32_t m_startTick;
|
||||
uint16_t m_unk1;
|
||||
uint8_t m_progNum;
|
||||
uint8_t m_unk1;
|
||||
uint16_t m_unk2;
|
||||
uint16_t m_dataIndex;
|
||||
uint16_t m_unk3;
|
||||
uint32_t m_startTick2;
|
||||
uint16_t m_unk4;
|
||||
uint16_t m_unk5;
|
||||
uint16_t m_unk6;
|
||||
uint16_t m_unk7;
|
||||
void swapBig();
|
||||
int16_t m_regionIndex;
|
||||
int16_t m_unk3;
|
||||
bool indexValid(bool bigEndian) const;
|
||||
};
|
||||
|
||||
/** Tempo change entry */
|
||||
@@ -60,8 +56,12 @@ class SongState
|
||||
void swapBig();
|
||||
};
|
||||
|
||||
/** State of a single channel within arrangement */
|
||||
struct Channel
|
||||
const unsigned char* m_songData = nullptr; /**< Base pointer to active song */
|
||||
int m_sngVersion; /**< Detected song revision, 1 has RLE-compressed delta-times */
|
||||
bool m_bigEndian; /**< True if loaded song is big-endian data */
|
||||
|
||||
/** State of a single track within arrangement */
|
||||
struct Track
|
||||
{
|
||||
struct Header
|
||||
{
|
||||
@@ -73,25 +73,28 @@ class SongState
|
||||
|
||||
SongState& m_parent;
|
||||
uint8_t m_midiChan; /**< MIDI channel number of song channel */
|
||||
uint32_t m_startTick; /**< Tick to start execution of channel commands */
|
||||
const TrackRegion* m_curRegion; /**< Pointer to currently-playing track region */
|
||||
const TrackRegion* m_nextRegion; /**< Pointer to next-queued track region */
|
||||
|
||||
const unsigned char* m_dataBase; /**< Base pointer to command data */
|
||||
const unsigned char* m_data; /**< Pointer to upcoming command data */
|
||||
const unsigned char* m_data = nullptr; /**< Pointer to upcoming command data */
|
||||
const unsigned char* m_pitchWheelData = nullptr; /**< Pointer to upcoming pitch data */
|
||||
const unsigned char* m_modWheelData = nullptr; /**< Pointer to upcoming modulation data */
|
||||
uint32_t m_lastPitchTick = 0; /**< Last position of pitch wheel change */
|
||||
int32_t m_lastPitchVal = 0; /**< Last value of pitch */
|
||||
uint32_t m_lastModTick = 0; /**< Last position of mod wheel change */
|
||||
int32_t m_lastModVal = 0; /**< Last value of mod */
|
||||
std::array<uint16_t, 128> m_remNoteLengths = {}; /**< Remaining ticks per note */
|
||||
std::array<int, 128> m_remNoteLengths; /**< Remaining ticks per note */
|
||||
|
||||
int32_t m_waitCountdown = 0; /**< Current wait in ticks */
|
||||
int32_t m_eventWaitCountdown = 0; /**< Current wait in ticks */
|
||||
int32_t m_lastN64EventTick = 0; /**< Last command time on this channel (for computing delta times from absolute times in N64 songs) */
|
||||
|
||||
Channel(SongState& parent, uint8_t midiChan, uint32_t startTick,
|
||||
const unsigned char* song, const unsigned char* chan);
|
||||
Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions);
|
||||
void setRegion(Sequencer* seq, const TrackRegion* region);
|
||||
void advanceRegion(Sequencer* seq);
|
||||
bool advance(Sequencer& seq, int32_t ticks);
|
||||
};
|
||||
std::array<std::experimental::optional<Channel>, 64> m_channels;
|
||||
std::array<std::experimental::optional<Track>, 64> m_tracks;
|
||||
const uint32_t* m_regionIdx; /**< Table of offsets to song-region data */
|
||||
|
||||
/** Current pointer to tempo control, iterated over playback */
|
||||
const TempoChange* m_tempoPtr = nullptr;
|
||||
@@ -102,13 +105,21 @@ class SongState
|
||||
double m_curDt = 0.f; /**< Cumulative dt value for time-remainder tracking */
|
||||
|
||||
public:
|
||||
/** Determine SNG version
|
||||
* @param isBig returns true if big-endian SNG
|
||||
* @return 0 for initial version, 1 for delta-time revision, -1 for non-SNG */
|
||||
static int DetectVersion(const unsigned char* ptr, bool& isBig);
|
||||
|
||||
/** initialize state for Song data at `ptr` */
|
||||
void initialize(const unsigned char* ptr);
|
||||
bool initialize(const unsigned char* ptr);
|
||||
|
||||
/** advances `dt` seconds worth of commands in the Song
|
||||
* @return `true` if END reached
|
||||
*/
|
||||
bool advance(Sequencer& seq, double dt);
|
||||
|
||||
/** Get current song tempo in BPM */
|
||||
uint32_t getTempo() const {return m_tempo;}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ class Voice;
|
||||
class SoundMacroState
|
||||
{
|
||||
friend class Voice;
|
||||
friend class Envelope;
|
||||
|
||||
/** SoundMacro header */
|
||||
struct Header
|
||||
@@ -145,9 +146,9 @@ class SoundMacroState
|
||||
uint8_t m_midiSustain; /**< Sustain MIDI controller */
|
||||
uint8_t m_midiRelease; /**< Release MIDI controller */
|
||||
|
||||
uint8_t m_portamentoMode; /**< (0: Off, 1: On, 2: MIDI specified) */
|
||||
uint8_t m_portamentoType; /**< (0: New key pressed while old key pressed, 1: Always) */
|
||||
float m_portamentoTime; /**< portamento transition time, 0.f will perform legato */
|
||||
uint8_t m_portamentoMode = 2; /**< (0: Off, 1: On, 2: MIDI specified) */
|
||||
uint8_t m_portamentoType = 0; /**< (0: New key pressed while old key pressed, 1: Always) */
|
||||
float m_portamentoTime = 0.5f; /**< portamento transition time, 0.f will perform legato */
|
||||
|
||||
/** Used to build a multi-component formula for overriding controllers */
|
||||
struct Evaluator
|
||||
|
||||
@@ -99,6 +99,9 @@ public:
|
||||
/** in/out transformation entry for audio effect */
|
||||
void applyEffect(float* audio, size_t frameCount, const ChannelMap& chanMap) const;
|
||||
|
||||
/** advice effects of changing sample rate */
|
||||
void resetOutputSampleRate(double sampleRate);
|
||||
|
||||
Engine& getEngine() {return m_root;}
|
||||
};
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ class Voice : public Entity
|
||||
friend class Engine;
|
||||
friend class Sequencer;
|
||||
friend class SoundMacroState;
|
||||
friend class Envelope;
|
||||
int m_vid; /**< VoiceID of this voice instance */
|
||||
bool m_emitter; /**< Voice is part of an Emitter */
|
||||
Submix* m_submix = nullptr; /**< Submix this voice outputs to (or NULL for the main output mix) */
|
||||
@@ -71,7 +72,8 @@ class Voice : public Entity
|
||||
bool m_sustainKeyOff = false; /**< Keyoff event occured while sustained */
|
||||
uint8_t m_curAftertouch = 0; /**< Aftertouch value (key pressure when 'bottoming out') */
|
||||
|
||||
float m_userVol = 1.f; /**< User volume of voice */
|
||||
float m_targetUserVol = 1.f; /**< Target user volume of voice (slewed to prevent audible aliasing) */
|
||||
float m_curUserVol = 1.f; /**< Current user volume of voice */
|
||||
float m_curVol = 1.f; /**< Current volume of voice */
|
||||
float m_curReverbVol = 0.f; /**< Current reverb volume of voice */
|
||||
float m_userPan = 0.f; /**< User pan of voice */
|
||||
@@ -79,14 +81,14 @@ class Voice : public Entity
|
||||
float m_userSpan = 0.f; /**< User span of voice */
|
||||
float m_curSpan = 0.f; /**< Current surround pan of voice */
|
||||
float m_curPitchWheel = 0.f; /**< Current normalized wheel value for control */
|
||||
int32_t m_pitchWheelUp; /**< Up range for pitchwheel control in cents */
|
||||
int32_t m_pitchWheelDown; /**< Down range for pitchwheel control in cents */
|
||||
int32_t m_pitchWheelVal; /**< Current resolved pitchwheel delta for control */
|
||||
int32_t m_pitchWheelUp = 600; /**< Up range for pitchwheel control in cents */
|
||||
int32_t m_pitchWheelDown = 600; /**< Down range for pitchwheel control in cents */
|
||||
int32_t m_pitchWheelVal = 0; /**< Current resolved pitchwheel delta for control */
|
||||
int32_t m_curPitch; /**< Current base pitch in cents */
|
||||
bool m_pitchDirty; /**< m_curPitch has been updated and needs sending to voice */
|
||||
bool m_pitchDirty = true; /**< m_curPitch has been updated and needs sending to voice */
|
||||
|
||||
Envelope m_volAdsr; /**< Volume envelope */
|
||||
double m_envelopeTime; /**< time since last ENVELOPE command, -1 for no active volume-sweep */
|
||||
double m_envelopeTime = -1.f; /**< time since last ENVELOPE command, -1 for no active volume-sweep */
|
||||
double m_envelopeDur; /**< requested duration of last ENVELOPE command */
|
||||
float m_envelopeStart; /**< initial value for last ENVELOPE command */
|
||||
float m_envelopeEnd; /**< final value for last ENVELOPE command */
|
||||
@@ -96,39 +98,41 @@ class Voice : public Entity
|
||||
Envelope m_pitchAdsr; /**< Pitch envelope for SETPITCHADSR */
|
||||
int32_t m_pitchEnvRange; /**< Pitch delta for SETPITCHADSR (in cents) */
|
||||
|
||||
uint32_t m_pitchSweep1; /**< Current value of PITCHSWEEP1 controller (in cents) */
|
||||
uint32_t m_pitchSweep2; /**< Current value of PITCHSWEEP2 controller (in cents) */
|
||||
int16_t m_pitchSweep1Add; /**< Value to add to PITCHSWEEP1 controller each cycle */
|
||||
int16_t m_pitchSweep2Add; /**< Value to add to PITCHSWEEP2 controller each cycle */
|
||||
uint8_t m_pitchSweep1Times; /**< Remaining times to advance PITCHSWEEP1 controller */
|
||||
uint8_t m_pitchSweep2Times; /**< Remaining times to advance PITCHSWEEP2 controller */
|
||||
uint8_t m_pitchSweep1It; /**< Current iteration of PITCHSWEEP1 controller */
|
||||
uint8_t m_pitchSweep2It; /**< Current iteration of PITCHSWEEP2 controller */
|
||||
float m_portamentoTime = -1.f; /**< time since last portamento invocation, -1 for no active portamento-glide */
|
||||
int32_t m_portamentoTarget; /**< destination pitch for latest portamento invocation */
|
||||
|
||||
float m_panningTime; /**< time since last PANNING command, -1 for no active pan-sweep */
|
||||
uint32_t m_pitchSweep1 = 0; /**< Current value of PITCHSWEEP1 controller (in cents) */
|
||||
uint32_t m_pitchSweep2 = 0; /**< Current value of PITCHSWEEP2 controller (in cents) */
|
||||
int16_t m_pitchSweep1Add = 0; /**< Value to add to PITCHSWEEP1 controller each cycle */
|
||||
int16_t m_pitchSweep2Add = 0; /**< Value to add to PITCHSWEEP2 controller each cycle */
|
||||
uint8_t m_pitchSweep1Times = 0; /**< Remaining times to advance PITCHSWEEP1 controller */
|
||||
uint8_t m_pitchSweep2Times = 0; /**< Remaining times to advance PITCHSWEEP2 controller */
|
||||
uint8_t m_pitchSweep1It = 0; /**< Current iteration of PITCHSWEEP1 controller */
|
||||
uint8_t m_pitchSweep2It = 0; /**< Current iteration of PITCHSWEEP2 controller */
|
||||
|
||||
float m_panningTime = -1.f; /**< time since last PANNING command, -1 for no active pan-sweep */
|
||||
float m_panningDur; /**< requested duration of last PANNING command */
|
||||
uint8_t m_panPos; /**< initial pan value of last PANNING command */
|
||||
int8_t m_panWidth; /**< delta pan value to target of last PANNING command */
|
||||
|
||||
float m_spanningTime; /**< time since last SPANNING command, -1 for no active span-sweep */
|
||||
float m_spanningTime = -1.f; /**< time since last SPANNING command, -1 for no active span-sweep */
|
||||
float m_spanningDur; /**< requested duration of last SPANNING command */
|
||||
uint8_t m_spanPos; /**< initial pan value of last SPANNING command */
|
||||
int8_t m_spanWidth; /**< delta pan value to target of last SPANNING command */
|
||||
|
||||
int32_t m_vibratoLevel; /**< scale of vibrato effect (in cents) */
|
||||
int32_t m_vibratoModLevel; /**< scale of vibrato mod-wheel influence (in cents) */
|
||||
float m_vibratoPeriod; /**< vibrato wave period-time, 0.f will disable vibrato */
|
||||
bool m_vibratoModWheel; /**< vibrato scaled with mod-wheel if set */
|
||||
int32_t m_vibratoLevel = 0; /**< scale of vibrato effect (in cents) */
|
||||
int32_t m_vibratoModLevel = 0; /**< scale of vibrato mod-wheel influence (in cents) */
|
||||
float m_vibratoPeriod = 0.f; /**< vibrato wave period-time, 0.f will disable vibrato */
|
||||
bool m_vibratoModWheel = false; /**< vibrato scaled with mod-wheel if set */
|
||||
|
||||
float m_tremoloScale; /**< minimum volume factor produced via LFO */
|
||||
float m_tremoloModScale; /**< minimum volume factor produced via LFO, scaled via mod wheel */
|
||||
float m_tremoloScale = 0.f; /**< minimum volume factor produced via LFO */
|
||||
float m_tremoloModScale = 0.f; /**< minimum volume factor produced via LFO, scaled via mod wheel */
|
||||
|
||||
float m_lfoPeriods[2]; /**< time-periods for LFO1 and LFO2 */
|
||||
float m_lfoPeriods[2] = {}; /**< time-periods for LFO1 and LFO2 */
|
||||
std::unique_ptr<int8_t[]> m_ctrlValsSelf; /**< Self-owned MIDI Controller values */
|
||||
int8_t* m_extCtrlVals = nullptr; /**< MIDI Controller values (external storage) */
|
||||
|
||||
void _destroy();
|
||||
void _reset();
|
||||
bool _checkSamplePos(bool& looped);
|
||||
void _doKeyOff();
|
||||
void _macroKeyOff();
|
||||
@@ -155,6 +159,7 @@ class Voice : public Entity
|
||||
|
||||
void _setPan(float pan);
|
||||
void _setSurroundPan(float span);
|
||||
void _setPitchWheel(float pitchWheel);
|
||||
void _notifyCtrlChange(uint8_t ctrl, int8_t val);
|
||||
public:
|
||||
~Voice();
|
||||
@@ -275,6 +280,9 @@ public:
|
||||
/** Get note played on voice */
|
||||
uint8_t getLastNote() const {return m_state.m_initKey;}
|
||||
|
||||
/** Do portamento glide; returns `false` if portamento disabled */
|
||||
bool doPortamento(uint8_t newNote);
|
||||
|
||||
/** Get MIDI Controller value on voice */
|
||||
int8_t getCtrlValue(uint8_t ctrl) const
|
||||
{
|
||||
@@ -300,9 +308,6 @@ public:
|
||||
_notifyCtrlChange(ctrl, val);
|
||||
}
|
||||
|
||||
/** Get ModWheel value on voice */
|
||||
int8_t getModWheel() const {return getCtrlValue(1);}
|
||||
|
||||
/** 'install' external MIDI controller storage */
|
||||
void installCtrlValues(int8_t* cvs)
|
||||
{
|
||||
@@ -311,7 +316,7 @@ public:
|
||||
}
|
||||
|
||||
/** Get MIDI pitch wheel value on voice */
|
||||
int8_t getPitchWheel() const {return m_curPitchWheel * 127;}
|
||||
float getPitchWheel() const {return m_curPitchWheel;}
|
||||
|
||||
/** Get MIDI aftertouch value on voice */
|
||||
int8_t getAftertouch() const {return m_curAftertouch;}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "Listener.hpp"
|
||||
#include "Sequencer.hpp"
|
||||
#include "SoundMacroState.hpp"
|
||||
#include "SongConverter.hpp"
|
||||
#include "SongState.hpp"
|
||||
#include "Submix.hpp"
|
||||
#include "Voice.hpp"
|
||||
|
||||
|
||||
@@ -7,15 +7,17 @@ IntrusiveAudioGroupData::~IntrusiveAudioGroupData()
|
||||
{
|
||||
if (m_owns)
|
||||
{
|
||||
delete m_pool;
|
||||
delete m_proj;
|
||||
delete m_sdir;
|
||||
delete m_samp;
|
||||
delete[] m_pool;
|
||||
delete[] m_proj;
|
||||
delete[] m_sdir;
|
||||
delete[] m_samp;
|
||||
}
|
||||
}
|
||||
|
||||
IntrusiveAudioGroupData::IntrusiveAudioGroupData(IntrusiveAudioGroupData&& other)
|
||||
: AudioGroupData(other.m_proj, other.m_pool, other.m_sdir, other.m_samp, other.m_fmt, other.m_absOffs)
|
||||
: AudioGroupData(other.m_proj, other.m_projSz, other.m_pool, other.m_poolSz,
|
||||
other.m_sdir, other.m_sdirSz, other.m_samp, other.m_sampSz,
|
||||
other.m_fmt, other.m_absOffs)
|
||||
{
|
||||
m_owns = other.m_owns;
|
||||
other.m_owns = false;
|
||||
@@ -25,10 +27,10 @@ IntrusiveAudioGroupData& IntrusiveAudioGroupData::operator=(IntrusiveAudioGroupD
|
||||
{
|
||||
if (m_owns)
|
||||
{
|
||||
delete m_pool;
|
||||
delete m_proj;
|
||||
delete m_sdir;
|
||||
delete m_samp;
|
||||
delete[] m_pool;
|
||||
delete[] m_proj;
|
||||
delete[] m_sdir;
|
||||
delete[] m_samp;
|
||||
}
|
||||
|
||||
m_owns = other.m_owns;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -77,6 +77,11 @@ void BooBackendSubmix::SubmixCallback::applyEffect(float* audio, size_t frameCou
|
||||
return m_parent.m_clientSmx.applyEffect(audio, frameCount, reinterpret_cast<const ChannelMap&>(chanMap));
|
||||
}
|
||||
|
||||
void BooBackendSubmix::SubmixCallback::resetOutputSampleRate(double sampleRate)
|
||||
{
|
||||
m_parent.m_clientSmx.resetOutputSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
BooBackendSubmix::BooBackendSubmix(boo::IAudioVoiceEngine& engine, Submix& clientSmx)
|
||||
: m_clientSmx(clientSmx), m_cb(*this), m_booSubmix(engine.allocateNewSubmix(&m_cb))
|
||||
{}
|
||||
@@ -114,8 +119,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)
|
||||
@@ -125,40 +130,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;
|
||||
}
|
||||
@@ -169,6 +180,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);
|
||||
}
|
||||
@@ -178,12 +198,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*/)
|
||||
@@ -303,7 +333,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;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
287
lib/DirectoryEnumerator.cpp
Normal file
287
lib/DirectoryEnumerator.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
|
||||
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
||||
#endif
|
||||
|
||||
#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)
|
||||
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "amuse/DirectoryEnumerator.hpp"
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
DirectoryEnumerator::DirectoryEnumerator(const SystemChar* path, Mode mode,
|
||||
bool sizeSort, bool reverse, bool noHidden)
|
||||
{
|
||||
Sstat theStat;
|
||||
if (Stat(path, &theStat) || !S_ISDIR(theStat.st_mode))
|
||||
return;
|
||||
|
||||
#if _WIN32
|
||||
SystemString wc(path);
|
||||
wc += _S("/*");
|
||||
WIN32_FIND_DATAW d;
|
||||
HANDLE dir = FindFirstFileW(wc.c_str(), &d);
|
||||
if (dir == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
switch (mode)
|
||||
{
|
||||
case Mode::Native:
|
||||
do
|
||||
{
|
||||
if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S("..")))
|
||||
continue;
|
||||
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
|
||||
continue;
|
||||
SystemString fp(path);
|
||||
fp += _S('/');
|
||||
fp += d.cFileName;
|
||||
Sstat st;
|
||||
if (Stat(fp.c_str(), &st))
|
||||
continue;
|
||||
|
||||
size_t sz = 0;
|
||||
bool isDir = false;
|
||||
if (S_ISDIR(st.st_mode))
|
||||
isDir = true;
|
||||
else if (S_ISREG(st.st_mode))
|
||||
sz = st.st_size;
|
||||
else
|
||||
continue;
|
||||
|
||||
m_entries.push_back(std::move(Entry(std::move(fp), d.cFileName, sz, isDir)));
|
||||
} while (FindNextFileW(dir, &d));
|
||||
break;
|
||||
case Mode::DirsThenFilesSorted:
|
||||
case Mode::DirsSorted:
|
||||
{
|
||||
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;
|
||||
do
|
||||
{
|
||||
if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S("..")))
|
||||
continue;
|
||||
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
|
||||
continue;
|
||||
SystemString fp(path);
|
||||
fp +=_S('/');
|
||||
fp += d.cFileName;
|
||||
Sstat st;
|
||||
if (Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode))
|
||||
continue;
|
||||
sort.emplace(std::make_pair(d.cFileName, Entry(std::move(fp), d.cFileName, 0, true)));
|
||||
} while (FindNextFileW(dir, &d));
|
||||
|
||||
if (reverse)
|
||||
for (auto it=sort.crbegin() ; it != sort.crend() ; ++it)
|
||||
m_entries.push_back(std::move(it->second));
|
||||
else
|
||||
for (auto& e : sort)
|
||||
m_entries.push_back(std::move(e.second));
|
||||
|
||||
if (mode == Mode::DirsSorted)
|
||||
break;
|
||||
FindClose(dir);
|
||||
dir = FindFirstFileW(wc.c_str(), &d);
|
||||
}
|
||||
case Mode::FilesSorted:
|
||||
{
|
||||
if (mode == Mode::FilesSorted)
|
||||
m_entries.clear();
|
||||
|
||||
if (sizeSort)
|
||||
{
|
||||
std::multimap<size_t, Entry> sort;
|
||||
do
|
||||
{
|
||||
if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S("..")))
|
||||
continue;
|
||||
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
|
||||
continue;
|
||||
SystemString fp(path);
|
||||
fp += _S('/');
|
||||
fp += d.cFileName;
|
||||
Sstat st;
|
||||
if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
|
||||
continue;
|
||||
sort.emplace(std::make_pair(st.st_size, Entry(std::move(fp), d.cFileName, st.st_size, false)));
|
||||
} while (FindNextFileW(dir, &d));
|
||||
|
||||
if (reverse)
|
||||
for (auto it=sort.crbegin() ; it != sort.crend() ; ++it)
|
||||
m_entries.push_back(std::move(it->second));
|
||||
else
|
||||
for (auto& e : sort)
|
||||
m_entries.push_back(std::move(e.second));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;
|
||||
do
|
||||
{
|
||||
if (!wcscmp(d.cFileName, _S(".")) || !wcscmp(d.cFileName, _S("..")))
|
||||
continue;
|
||||
if (noHidden && (d.cFileName[0] == L'.' || (d.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0))
|
||||
continue;
|
||||
SystemString fp(path);
|
||||
fp += _S('/');
|
||||
fp += d.cFileName;
|
||||
Sstat st;
|
||||
if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
|
||||
continue;
|
||||
sort.emplace(std::make_pair(d.cFileName, Entry(std::move(fp), d.cFileName, st.st_size, false)));
|
||||
} while (FindNextFileW(dir, &d));
|
||||
|
||||
if (reverse)
|
||||
for (auto it=sort.crbegin() ; it != sort.crend() ; ++it)
|
||||
m_entries.push_back(std::move(it->second));
|
||||
else
|
||||
for (auto& e : sort)
|
||||
m_entries.push_back(std::move(e.second));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
FindClose(dir);
|
||||
|
||||
#else
|
||||
|
||||
DIR* dir = opendir(path);
|
||||
if (!dir)
|
||||
return;
|
||||
const dirent* d;
|
||||
switch (mode)
|
||||
{
|
||||
case Mode::Native:
|
||||
while ((d = readdir(dir)))
|
||||
{
|
||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||
continue;
|
||||
if (noHidden && d->d_name[0] == '.')
|
||||
continue;
|
||||
SystemString fp(path);
|
||||
fp += '/';
|
||||
fp += d->d_name;
|
||||
Sstat st;
|
||||
if (Stat(fp.c_str(), &st))
|
||||
continue;
|
||||
|
||||
size_t sz = 0;
|
||||
bool isDir = false;
|
||||
if (S_ISDIR(st.st_mode))
|
||||
isDir = true;
|
||||
else if (S_ISREG(st.st_mode))
|
||||
sz = st.st_size;
|
||||
else
|
||||
continue;
|
||||
|
||||
m_entries.push_back(std::move(Entry(std::move(fp), d->d_name, sz, isDir)));
|
||||
}
|
||||
break;
|
||||
case Mode::DirsThenFilesSorted:
|
||||
case Mode::DirsSorted:
|
||||
{
|
||||
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;
|
||||
while ((d = readdir(dir)))
|
||||
{
|
||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||
continue;
|
||||
if (noHidden && d->d_name[0] == '.')
|
||||
continue;
|
||||
SystemString fp(path);
|
||||
fp += '/';
|
||||
fp += d->d_name;
|
||||
Sstat st;
|
||||
if (Stat(fp.c_str(), &st) || !S_ISDIR(st.st_mode))
|
||||
continue;
|
||||
sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, 0, true)));
|
||||
}
|
||||
|
||||
if (reverse)
|
||||
for (auto it=sort.crbegin() ; it != sort.crend() ; ++it)
|
||||
m_entries.push_back(std::move(it->second));
|
||||
else
|
||||
for (auto& e : sort)
|
||||
m_entries.push_back(std::move(e.second));
|
||||
|
||||
if (mode == Mode::DirsSorted)
|
||||
break;
|
||||
rewinddir(dir);
|
||||
}
|
||||
case Mode::FilesSorted:
|
||||
{
|
||||
if (mode == Mode::FilesSorted)
|
||||
m_entries.clear();
|
||||
|
||||
if (sizeSort)
|
||||
{
|
||||
std::multimap<size_t, Entry> sort;
|
||||
while ((d = readdir(dir)))
|
||||
{
|
||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||
continue;
|
||||
if (noHidden && d->d_name[0] == '.')
|
||||
continue;
|
||||
SystemString fp(path);
|
||||
fp += '/';
|
||||
fp += d->d_name;
|
||||
Sstat st;
|
||||
if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
|
||||
continue;
|
||||
sort.emplace(std::make_pair(st.st_size, Entry(std::move(fp), d->d_name, st.st_size, false)));
|
||||
}
|
||||
|
||||
if (reverse)
|
||||
for (auto it=sort.crbegin() ; it != sort.crend() ; ++it)
|
||||
m_entries.push_back(std::move(it->second));
|
||||
else
|
||||
for (auto& e : sort)
|
||||
m_entries.push_back(std::move(e.second));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::map<SystemString, Entry, CaseInsensitiveCompare> sort;
|
||||
while ((d = readdir(dir)))
|
||||
{
|
||||
if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
|
||||
continue;
|
||||
if (noHidden && d->d_name[0] == '.')
|
||||
continue;
|
||||
SystemString fp(path);
|
||||
fp += '/';
|
||||
fp += d->d_name;
|
||||
Sstat st;
|
||||
if (Stat(fp.c_str(), &st) || !S_ISREG(st.st_mode))
|
||||
continue;
|
||||
sort.emplace(std::make_pair(d->d_name, Entry(std::move(fp), d->d_name, st.st_size, false)));
|
||||
}
|
||||
|
||||
if (reverse)
|
||||
for (auto it=sort.crbegin() ; it != sort.crend() ; ++it)
|
||||
m_entries.push_back(std::move(it->second));
|
||||
else
|
||||
for (auto& e : sort)
|
||||
m_entries.push_back(std::move(e.second));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -148,10 +148,19 @@ EffectChorus::EffectChorus(uint32_t baseDelay, uint32_t variation, uint32_t peri
|
||||
template <typename T>
|
||||
EffectChorusImp<T>::EffectChorusImp(uint32_t baseDelay, uint32_t variation,
|
||||
uint32_t period, double sampleRate)
|
||||
: EffectChorus(baseDelay, variation, period),
|
||||
m_sampsPerMs(std::ceil(sampleRate / 1000.0)),
|
||||
m_blockSamples(m_sampsPerMs * 5)
|
||||
: EffectChorus(baseDelay, variation, period)
|
||||
{
|
||||
_setup(sampleRate);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void EffectChorusImp<T>::_setup(double sampleRate)
|
||||
{
|
||||
m_sampsPerMs = std::ceil(sampleRate / 1000.0);
|
||||
m_blockSamples = m_sampsPerMs * 5;
|
||||
|
||||
delete[] x0_lastChans[0][0];
|
||||
|
||||
T* buf = new T[m_blockSamples * AMUSE_CHORUS_NUM_BLOCKS * 8];
|
||||
memset(buf, 0, m_blockSamples * AMUSE_CHORUS_NUM_BLOCKS * 8 * sizeof(T));
|
||||
size_t chanPitch = m_blockSamples * AMUSE_CHORUS_NUM_BLOCKS;
|
||||
@@ -161,6 +170,8 @@ EffectChorusImp<T>::EffectChorusImp(uint32_t baseDelay, uint32_t variation,
|
||||
x0_lastChans[c][i] = buf + chanPitch * c + m_blockSamples * i;
|
||||
|
||||
x6c_src.x88_trigger = chanPitch;
|
||||
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -10,8 +10,6 @@ namespace amuse
|
||||
template <typename T>
|
||||
EffectDelayImp<T>::EffectDelayImp(uint32_t initDelay, uint32_t initFeedback,
|
||||
uint32_t initOutput, double sampleRate)
|
||||
: m_sampsPerMs(std::ceil(sampleRate / 1000.0)),
|
||||
m_blockSamples(m_sampsPerMs * 5)
|
||||
{
|
||||
initDelay = clamp(10u, initDelay, 5000u);
|
||||
initFeedback = clamp(0u, initFeedback, 100u);
|
||||
@@ -24,6 +22,15 @@ EffectDelayImp<T>::EffectDelayImp(uint32_t initDelay, uint32_t initFeedback,
|
||||
x54_output[i] = initOutput;
|
||||
}
|
||||
|
||||
_setup(sampleRate);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void EffectDelayImp<T>::_setup(double sampleRate)
|
||||
{
|
||||
m_sampsPerMs = std::ceil(sampleRate / 1000.0);
|
||||
m_blockSamples = m_sampsPerMs * 5;
|
||||
|
||||
_update();
|
||||
}
|
||||
|
||||
|
||||
@@ -71,9 +71,17 @@ EffectReverbHi::EffectReverbHi(float coloration, float mix, float time,
|
||||
template <typename T>
|
||||
EffectReverbStdImp<T>::EffectReverbStdImp(float coloration, float mix, float time,
|
||||
float damping, float preDelay, double sampleRate)
|
||||
: EffectReverbStd(coloration, mix, time, damping, preDelay),
|
||||
m_sampleRate(sampleRate)
|
||||
{}
|
||||
: EffectReverbStd(coloration, mix, time, damping, preDelay)
|
||||
{
|
||||
_setup(sampleRate);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void EffectReverbStdImp<T>::_setup(double sampleRate)
|
||||
{
|
||||
m_sampleRate = sampleRate;
|
||||
_update();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void EffectReverbStdImp<T>::_update()
|
||||
@@ -238,9 +246,15 @@ template <typename T>
|
||||
EffectReverbHiImp<T>::EffectReverbHiImp(float coloration, float mix, float time,
|
||||
float damping, float preDelay, float crosstalk,
|
||||
double sampleRate)
|
||||
: EffectReverbHi(coloration, mix, time, damping, preDelay, crosstalk),
|
||||
m_sampleRate(sampleRate)
|
||||
: EffectReverbHi(coloration, mix, time, damping, preDelay, crosstalk)
|
||||
{
|
||||
_setup(sampleRate);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void EffectReverbHiImp<T>::_setup(double sampleRate)
|
||||
{
|
||||
m_sampleRate = sampleRate;
|
||||
_update();
|
||||
}
|
||||
|
||||
|
||||
@@ -73,11 +73,20 @@ Engine::_allocateSequencer(const AudioGroup& group, int groupId,
|
||||
int setupId, Submix* smx)
|
||||
{
|
||||
const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId);
|
||||
if (!songGroup)
|
||||
return {};
|
||||
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
|
||||
new Sequencer(*this, group, groupId, *songGroup, setupId, smx));
|
||||
return it;
|
||||
if (songGroup)
|
||||
{
|
||||
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
|
||||
new Sequencer(*this, group, groupId, songGroup, setupId, smx));
|
||||
return it;
|
||||
}
|
||||
const SFXGroupIndex* sfxGroup = group.getProj().getSFXGroupIndex(groupId);
|
||||
if (sfxGroup)
|
||||
{
|
||||
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
|
||||
new Sequencer(*this, group, groupId, sfxGroup, smx));
|
||||
return it;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::list<Submix>::iterator Engine::_allocateSubmix(Submix* smx)
|
||||
@@ -407,16 +416,27 @@ std::shared_ptr<Sequencer> Engine::seqPlay(int groupId, int songId,
|
||||
const unsigned char* arrData, Submix* smx)
|
||||
{
|
||||
std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId);
|
||||
if (!songGrp.second)
|
||||
return {};
|
||||
if (songGrp.second)
|
||||
{
|
||||
std::list<std::shared_ptr<Sequencer>>::iterator ret = _allocateSequencer(*songGrp.first, groupId, songId, smx);
|
||||
if (!*ret)
|
||||
return {};
|
||||
|
||||
std::list<std::shared_ptr<Sequencer>>::iterator ret = _allocateSequencer(*songGrp.first, groupId, songId, smx);
|
||||
if (!*ret)
|
||||
return {};
|
||||
if (arrData)
|
||||
(*ret)->playSong(arrData);
|
||||
return *ret;
|
||||
}
|
||||
|
||||
if (arrData)
|
||||
(*ret)->playSong(arrData);
|
||||
return *ret;
|
||||
std::pair<AudioGroup*, const SFXGroupIndex*> sfxGrp = _findSFXGroup(groupId);
|
||||
if (sfxGrp.second)
|
||||
{
|
||||
std::list<std::shared_ptr<Sequencer>>::iterator ret = _allocateSequencer(*sfxGrp.first, groupId, songId, smx);
|
||||
if (!*ret)
|
||||
return {};
|
||||
return *ret;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/** Find voice from VoiceId */
|
||||
|
||||
153
lib/Envelope.cpp
153
lib/Envelope.cpp
@@ -1,8 +1,26 @@
|
||||
#include "amuse/Envelope.hpp"
|
||||
#include "amuse/Voice.hpp"
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
static int32_t MIDItoTIME[104] =
|
||||
{ /* [0..103] -> milliseconds */
|
||||
0, 10, 20, 30, 40, 50, 60, 70,
|
||||
80, 90, 100, 110, 110, 120, 130, 140,
|
||||
150, 160, 170, 190, 200, 220, 230, 250,
|
||||
270, 290, 310, 330, 350, 380, 410, 440,
|
||||
470, 500, 540, 580, 620, 660, 710, 760,
|
||||
820, 880, 940, 1000, 1000, 1100, 1200, 1300,
|
||||
1400, 1500, 1600, 1700, 1800, 2000, 2100, 2300,
|
||||
2400, 2600, 2800, 3000, 3200, 3500, 3700, 4000,
|
||||
4300, 4600, 4900, 5300, 5700, 6100, 6500, 7000,
|
||||
7500, 8100, 8600, 9300, 9900, 10000, 11000, 12000,
|
||||
13000, 14000, 15000, 16000, 17000, 18000, 19000, 21000,
|
||||
22000, 24000, 26000, 28000, 30000, 32000, 34000, 37000,
|
||||
39000, 42000, 45000, 49000, 50000, 55000, 60000, 65000
|
||||
};
|
||||
|
||||
void Envelope::reset(const ADSR* adsr)
|
||||
{
|
||||
m_phase = State::Attack;
|
||||
@@ -12,6 +30,7 @@ void Envelope::reset(const ADSR* adsr)
|
||||
m_sustainFactor = adsr->getSustain();
|
||||
m_releaseTime = adsr->getRelease();
|
||||
m_releaseStartFactor = 0.0;
|
||||
m_adsrSet = true;
|
||||
}
|
||||
|
||||
void Envelope::reset(const ADSRDLS* adsr, int8_t note, int8_t vel)
|
||||
@@ -23,6 +42,17 @@ void Envelope::reset(const ADSRDLS* adsr, int8_t note, int8_t vel)
|
||||
m_sustainFactor = adsr->getSustain();
|
||||
m_releaseTime = adsr->getRelease();
|
||||
m_releaseStartFactor = 0.0;
|
||||
m_adsrSet = true;
|
||||
}
|
||||
|
||||
void Envelope::keyOff(const Voice& vox)
|
||||
{
|
||||
double releaseTime = m_releaseTime;
|
||||
if (vox.m_state.m_useAdsrControllers)
|
||||
releaseTime = MIDItoTIME[clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiRelease)), 103)] / 1000.0;
|
||||
|
||||
m_phase = (releaseTime != 0.0) ? State::Release : State::Complete;
|
||||
m_curTime = 0.0;
|
||||
}
|
||||
|
||||
void Envelope::keyOff()
|
||||
@@ -31,7 +61,7 @@ void Envelope::keyOff()
|
||||
m_curTime = 0.0;
|
||||
}
|
||||
|
||||
float Envelope::advance(double dt)
|
||||
float Envelope::advance(double dt, const Voice& vox)
|
||||
{
|
||||
double thisTime = m_curTime;
|
||||
m_curTime += dt;
|
||||
@@ -40,14 +70,18 @@ float Envelope::advance(double dt)
|
||||
{
|
||||
case State::Attack:
|
||||
{
|
||||
if (m_attackTime == 0.0)
|
||||
double attackTime = m_attackTime;
|
||||
if (vox.m_state.m_useAdsrControllers)
|
||||
attackTime = MIDItoTIME[clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiAttack)), 103)] / 1000.0;
|
||||
|
||||
if (attackTime == 0.0)
|
||||
{
|
||||
m_phase = State::Decay;
|
||||
m_curTime = 0.0;
|
||||
m_releaseStartFactor = 1.f;
|
||||
return 1.f;
|
||||
}
|
||||
double attackFac = thisTime / m_attackTime;
|
||||
double attackFac = thisTime / attackTime;
|
||||
if (attackFac >= 1.0)
|
||||
{
|
||||
m_phase = State::Decay;
|
||||
@@ -60,22 +94,115 @@ float Envelope::advance(double dt)
|
||||
}
|
||||
case State::Decay:
|
||||
{
|
||||
if (m_decayTime == 0.0)
|
||||
double decayTime = m_decayTime;
|
||||
if (vox.m_state.m_useAdsrControllers)
|
||||
decayTime = MIDItoTIME[clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiDecay)), 103)] / 1000.0;
|
||||
|
||||
double sustainFactor = m_sustainFactor;
|
||||
if (vox.m_state.m_useAdsrControllers)
|
||||
sustainFactor = clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiSustain)), 127) / 127.0;
|
||||
|
||||
if (decayTime == 0.0)
|
||||
{
|
||||
m_phase = State::Sustain;
|
||||
m_curTime = 0.0;
|
||||
m_releaseStartFactor = m_sustainFactor;
|
||||
return m_sustainFactor;
|
||||
m_releaseStartFactor = sustainFactor;
|
||||
return sustainFactor;
|
||||
}
|
||||
double decayFac = thisTime / m_decayTime;
|
||||
double decayFac = thisTime / decayTime;
|
||||
if (decayFac >= 1.0)
|
||||
{
|
||||
m_phase = State::Sustain;
|
||||
m_curTime = 0.0;
|
||||
m_releaseStartFactor = m_sustainFactor;
|
||||
return m_sustainFactor;
|
||||
m_releaseStartFactor = sustainFactor;
|
||||
return sustainFactor;
|
||||
}
|
||||
m_releaseStartFactor = (1.0 - decayFac) + decayFac * m_sustainFactor;
|
||||
m_releaseStartFactor = (1.0 - decayFac) + decayFac * sustainFactor;
|
||||
return m_releaseStartFactor;
|
||||
}
|
||||
case State::Sustain:
|
||||
{
|
||||
double sustainFactor = m_sustainFactor;
|
||||
if (vox.m_state.m_useAdsrControllers)
|
||||
sustainFactor = clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiSustain)), 127) / 127.0;
|
||||
|
||||
return sustainFactor;
|
||||
}
|
||||
case State::Release:
|
||||
{
|
||||
double releaseTime = m_releaseTime;
|
||||
if (vox.m_state.m_useAdsrControllers)
|
||||
releaseTime = MIDItoTIME[clamp(0, int(vox.getCtrlValue(vox.m_state.m_midiRelease)), 103)] / 1000.0;
|
||||
|
||||
if (releaseTime == 0.0)
|
||||
{
|
||||
m_phase = State::Complete;
|
||||
return 0.f;
|
||||
}
|
||||
double releaseFac = thisTime / releaseTime;
|
||||
if (releaseFac >= 1.0)
|
||||
{
|
||||
m_phase = State::Complete;
|
||||
return 0.f;
|
||||
}
|
||||
return std::min(m_releaseStartFactor, 1.0 - releaseFac);
|
||||
}
|
||||
case State::Complete:
|
||||
default:
|
||||
return 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
float Envelope::advance(double dt)
|
||||
{
|
||||
double thisTime = m_curTime;
|
||||
m_curTime += dt;
|
||||
|
||||
switch (m_phase)
|
||||
{
|
||||
case State::Attack:
|
||||
{
|
||||
double attackTime = m_attackTime;
|
||||
|
||||
if (attackTime == 0.0)
|
||||
{
|
||||
m_phase = State::Decay;
|
||||
m_curTime = 0.0;
|
||||
m_releaseStartFactor = 1.f;
|
||||
return 1.f;
|
||||
}
|
||||
double attackFac = thisTime / attackTime;
|
||||
if (attackFac >= 1.0)
|
||||
{
|
||||
m_phase = State::Decay;
|
||||
m_curTime = 0.0;
|
||||
m_releaseStartFactor = 1.f;
|
||||
return 1.f;
|
||||
}
|
||||
m_releaseStartFactor = attackFac;
|
||||
return attackFac;
|
||||
}
|
||||
case State::Decay:
|
||||
{
|
||||
double decayTime = m_decayTime;
|
||||
double sustainFactor = m_sustainFactor;
|
||||
|
||||
if (decayTime == 0.0)
|
||||
{
|
||||
m_phase = State::Sustain;
|
||||
m_curTime = 0.0;
|
||||
m_releaseStartFactor = sustainFactor;
|
||||
return sustainFactor;
|
||||
}
|
||||
double decayFac = thisTime / decayTime;
|
||||
if (decayFac >= 1.0)
|
||||
{
|
||||
m_phase = State::Sustain;
|
||||
m_curTime = 0.0;
|
||||
m_releaseStartFactor = sustainFactor;
|
||||
return sustainFactor;
|
||||
}
|
||||
m_releaseStartFactor = (1.0 - decayFac) + decayFac * sustainFactor;
|
||||
return m_releaseStartFactor;
|
||||
}
|
||||
case State::Sustain:
|
||||
@@ -84,12 +211,14 @@ float Envelope::advance(double dt)
|
||||
}
|
||||
case State::Release:
|
||||
{
|
||||
if (m_releaseTime == 0.0)
|
||||
double releaseTime = m_releaseTime;
|
||||
|
||||
if (releaseTime == 0.0)
|
||||
{
|
||||
m_phase = State::Complete;
|
||||
return 0.f;
|
||||
}
|
||||
double releaseFac = thisTime / m_releaseTime;
|
||||
double releaseFac = thisTime / releaseTime;
|
||||
if (releaseFac >= 1.0)
|
||||
{
|
||||
m_phase = State::Complete;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "amuse/Submix.hpp"
|
||||
#include "amuse/Voice.hpp"
|
||||
#include "amuse/Engine.hpp"
|
||||
#include <map>
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
@@ -50,7 +51,7 @@ void Sequencer::_destroy()
|
||||
{
|
||||
m_engine.removeSubmix(m_submix);
|
||||
m_submix = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Sequencer::~Sequencer()
|
||||
@@ -63,15 +64,31 @@ Sequencer::~Sequencer()
|
||||
}
|
||||
|
||||
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId,
|
||||
const SongGroupIndex& songGroup, int setupId, Submix* smx)
|
||||
const SongGroupIndex* songGroup, int setupId, Submix* smx)
|
||||
: Entity(engine, group, groupId), m_songGroup(songGroup)
|
||||
{
|
||||
auto it = m_songGroup.m_midiSetups.find(setupId);
|
||||
if (it != m_songGroup.m_midiSetups.cend())
|
||||
auto it = m_songGroup->m_midiSetups.find(setupId);
|
||||
if (it != m_songGroup->m_midiSetups.cend())
|
||||
m_midiSetup = it->second->data();
|
||||
|
||||
m_submix = m_engine.addSubmix(smx);
|
||||
m_submix->makeReverbHi(0.2f, 1.f, 1.f, 0.5f, 0.f, 0.f);
|
||||
m_submix->makeReverbHi(0.2f, 0.3f, 1.f, 0.5f, 0.f, 0.f);
|
||||
}
|
||||
|
||||
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId,
|
||||
const SFXGroupIndex* sfxGroup, Submix* smx)
|
||||
: Entity(engine, group, groupId), m_sfxGroup(sfxGroup)
|
||||
{
|
||||
//m_submix = m_engine.addSubmix(smx);
|
||||
//m_submix->makeReverbHi(0.2f, 0.3f, 1.f, 0.5f, 0.f, 0.f);
|
||||
|
||||
std::map<uint16_t, const SFXGroupIndex::SFXEntry*> sortSFX;
|
||||
for (const auto& sfx : sfxGroup->m_sfxEntries)
|
||||
sortSFX[sfx.first] = sfx.second;
|
||||
|
||||
m_sfxMappings.reserve(sortSFX.size());
|
||||
for (const auto& sfx : sortSFX)
|
||||
m_sfxMappings.push_back(sfx.second);
|
||||
}
|
||||
|
||||
Sequencer::ChannelState::~ChannelState()
|
||||
@@ -79,25 +96,63 @@ 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_songGroup)
|
||||
{
|
||||
auto it = m_parent.m_songGroup.m_drumPages.find(m_setup.programNo);
|
||||
if (it != m_parent.m_songGroup.m_drumPages.cend())
|
||||
m_page = it->second;
|
||||
if (m_parent.m_midiSetup)
|
||||
{
|
||||
m_setup = &m_parent.m_midiSetup[chanId];
|
||||
|
||||
if (chanId == 9)
|
||||
{
|
||||
auto it = m_parent.m_songGroup->m_drumPages.find(m_setup->programNo);
|
||||
if (it != m_parent.m_songGroup->m_drumPages.cend())
|
||||
m_page = it->second;
|
||||
}
|
||||
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
|
||||
{
|
||||
if (chanId == 9)
|
||||
{
|
||||
auto it = m_parent.m_songGroup->m_drumPages.find(0);
|
||||
if (it != m_parent.m_songGroup->m_drumPages.cend())
|
||||
m_page = it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = m_parent.m_songGroup->m_normPages.find(0);
|
||||
if (it != m_parent.m_songGroup->m_normPages.cend())
|
||||
m_page = it->second;
|
||||
}
|
||||
|
||||
m_curVol = 1.f;
|
||||
m_curPan = 0.f;
|
||||
m_ctrlVals[0x5b] = 0;
|
||||
m_ctrlVals[0x5d] = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (m_parent.m_sfxGroup)
|
||||
{
|
||||
auto it = m_parent.m_songGroup.m_normPages.find(m_setup.programNo);
|
||||
if (it != m_parent.m_songGroup.m_normPages.cend())
|
||||
m_page = it->second;
|
||||
m_curVol = 1.f;
|
||||
m_curPan = 0.f;
|
||||
m_ctrlVals[0x5b] = 0;
|
||||
m_ctrlVals[0x5d] = 0;
|
||||
}
|
||||
|
||||
m_curVol = m_setup.volume / 127.f;
|
||||
m_curPan = m_setup.panning / 64.f - 1.f;
|
||||
m_ctrlVals[0x5b] = m_setup.reverb;
|
||||
m_ctrlVals[0x5d] = m_setup.chorus;
|
||||
m_ctrlVals[7] = 127;
|
||||
m_ctrlVals[10] = 64;
|
||||
}
|
||||
|
||||
void Sequencer::advance(double dt)
|
||||
@@ -132,13 +187,27 @@ size_t Sequencer::getVoiceCount() const
|
||||
|
||||
std::shared_ptr<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity)
|
||||
{
|
||||
if (!m_page)
|
||||
if (m_parent.m_songGroup && !m_page)
|
||||
return {};
|
||||
|
||||
/* If portamento is enabled for voice, pre-empt spawning new voices */
|
||||
if (std::shared_ptr<Voice> lastVoice = m_lastVoice.lock())
|
||||
{
|
||||
uint8_t lastNote = lastVoice->getLastNote();
|
||||
if (lastVoice->doPortamento(note))
|
||||
{
|
||||
m_chanVoxs.erase(lastNote);
|
||||
m_chanVoxs[note] = lastVoice;
|
||||
return lastVoice;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure keyoff sent first */
|
||||
auto keySearch = m_chanVoxs.find(note);
|
||||
if (keySearch != m_chanVoxs.cend())
|
||||
{
|
||||
if (keySearch->second == m_lastVoice.lock())
|
||||
m_lastVoice.reset();
|
||||
keySearch->second->keyOff();
|
||||
keySearch->second->setPedal(false);
|
||||
m_keyoffVoxs.emplace(std::move(keySearch->second));
|
||||
@@ -154,8 +223,20 @@ std::shared_ptr<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velo
|
||||
m_chanVoxs[note] = *ret;
|
||||
(*ret)->installCtrlValues(m_ctrlVals);
|
||||
|
||||
ObjectId oid = (m_parent.m_audioGroup.getDataFormat() == DataFormat::PC) ? m_page->objId : SBig(m_page->objId);
|
||||
if (!(*ret)->loadSoundObject(oid, 0, 1000.f, note, velocity, m_ctrlVals[1]))
|
||||
ObjectId oid;
|
||||
if (m_parent.m_songGroup)
|
||||
oid = (m_parent.m_audioGroup.getDataFormat() == DataFormat::PC) ? m_page->objId : SBig(m_page->objId);
|
||||
else if (m_parent.m_sfxMappings.size())
|
||||
{
|
||||
size_t lookupIdx = note % m_parent.m_sfxMappings.size();
|
||||
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent.m_sfxMappings[lookupIdx];
|
||||
oid = (m_parent.m_audioGroup.getDataFormat() == DataFormat::PC) ? sfxEntry->objId : SBig(sfxEntry->objId);
|
||||
note = sfxEntry->defKey;
|
||||
}
|
||||
else
|
||||
return {};
|
||||
|
||||
if (!(*ret)->loadSoundObject(oid, 0, m_parent.m_ticksPerSec, note, velocity, m_ctrlVals[1]))
|
||||
{
|
||||
m_parent.m_engine._destroyVoice(ret);
|
||||
return {};
|
||||
@@ -167,6 +248,8 @@ std::shared_ptr<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velo
|
||||
|
||||
if (m_ctrlVals[64] > 64)
|
||||
(*ret)->setPedal(true);
|
||||
|
||||
m_lastVoice = *ret;
|
||||
}
|
||||
|
||||
return *ret;
|
||||
@@ -189,6 +272,8 @@ void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
|
||||
if (keySearch == m_chanVoxs.cend())
|
||||
return;
|
||||
|
||||
if (keySearch->second == m_lastVoice.lock())
|
||||
m_lastVoice.reset();
|
||||
keySearch->second->keyOff();
|
||||
m_keyoffVoxs.emplace(std::move(keySearch->second));
|
||||
m_chanVoxs.erase(keySearch);
|
||||
@@ -224,24 +309,27 @@ void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
|
||||
|
||||
bool Sequencer::ChannelState::programChange(int8_t prog)
|
||||
{
|
||||
if (m_chanId == 9)
|
||||
if (m_parent.m_songGroup)
|
||||
{
|
||||
auto it = m_parent.m_songGroup.m_drumPages.find(prog);
|
||||
if (it != m_parent.m_songGroup.m_drumPages.cend())
|
||||
if (m_chanId == 9)
|
||||
{
|
||||
m_page = it->second;
|
||||
m_curProgram = prog;
|
||||
return true;
|
||||
auto it = m_parent.m_songGroup->m_drumPages.find(prog);
|
||||
if (it != m_parent.m_songGroup->m_drumPages.cend())
|
||||
{
|
||||
m_page = it->second;
|
||||
m_curProgram = prog;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = m_parent.m_songGroup.m_normPages.find(prog);
|
||||
if (it != m_parent.m_songGroup.m_normPages.cend())
|
||||
else
|
||||
{
|
||||
m_page = it->second;
|
||||
m_curProgram = prog;
|
||||
return true;
|
||||
auto it = m_parent.m_songGroup->m_normPages.find(prog);
|
||||
if (it != m_parent.m_songGroup->m_normPages.cend())
|
||||
{
|
||||
m_page = it->second;
|
||||
m_curProgram = prog;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -303,6 +391,8 @@ void Sequencer::ChannelState::allOff()
|
||||
{
|
||||
for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;)
|
||||
{
|
||||
if (it->second == m_lastVoice.lock())
|
||||
m_lastVoice.reset();
|
||||
it->second->keyOff();
|
||||
m_keyoffVoxs.emplace(std::move(it->second));
|
||||
it = m_chanVoxs.erase(it);
|
||||
@@ -355,6 +445,8 @@ void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
|
||||
Voice* vox = it->second.get();
|
||||
if (vox->m_keygroup == kg)
|
||||
{
|
||||
if (it->second == m_lastVoice.lock())
|
||||
m_lastVoice.reset();
|
||||
if (now)
|
||||
{
|
||||
it = m_chanVoxs.erase(it);
|
||||
@@ -443,6 +535,7 @@ void Sequencer::playSong(const unsigned char* arrData, bool dieOnEnd)
|
||||
m_arrData = arrData;
|
||||
m_dieOnEnd = dieOnEnd;
|
||||
m_songState.initialize(arrData);
|
||||
setTempo(m_songState.getTempo() * 384 / 60);
|
||||
m_state = SequencerState::Playing;
|
||||
}
|
||||
|
||||
|
||||
1484
lib/SongConverter.cpp
Normal file
1484
lib/SongConverter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,10 @@ static uint32_t DecodeRLE(const unsigned char*& data)
|
||||
++data;
|
||||
thisPart = thisPart * 256 + *data;
|
||||
if (thisPart == 0)
|
||||
{
|
||||
++data;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (thisPart == 32767)
|
||||
@@ -68,28 +71,17 @@ static uint32_t DecodeTimeRLE(const unsigned char*& data)
|
||||
|
||||
void SongState::Header::swapBig()
|
||||
{
|
||||
m_version = SBig(m_version);
|
||||
m_chanIdxOff = SBig(m_chanIdxOff);
|
||||
m_trackIdxOff = SBig(m_trackIdxOff);
|
||||
m_regionIdxOff = SBig(m_regionIdxOff);
|
||||
m_chanMapOff = SBig(m_chanMapOff);
|
||||
m_tempoTableOff = SBig(m_tempoTableOff);
|
||||
m_initialTempo = SBig(m_initialTempo);
|
||||
m_unkOff = SBig(m_unkOff);
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
m_chanOffs[i] = SBig(m_chanOffs[i]);
|
||||
}
|
||||
|
||||
void SongState::ChanHeader::swapBig()
|
||||
bool SongState::TrackRegion::indexValid(bool bigEndian) const
|
||||
{
|
||||
m_startTick = SBig(m_startTick);
|
||||
m_unk1 = SBig(m_unk1);
|
||||
m_unk2 = SBig(m_unk2);
|
||||
m_dataIndex = SBig(m_dataIndex);
|
||||
m_unk3 = SBig(m_unk3);
|
||||
m_startTick2 = SBig(m_startTick2);
|
||||
m_unk4 = SBig(m_unk4);
|
||||
m_unk5 = SBig(m_unk5);
|
||||
m_unk6 = SBig(m_unk6);
|
||||
m_unk7 = SBig(m_unk7);
|
||||
return (bigEndian ? SBig(m_regionIndex) : m_regionIndex) >= 0;
|
||||
}
|
||||
|
||||
void SongState::TempoChange::swapBig()
|
||||
@@ -98,57 +90,262 @@ void SongState::TempoChange::swapBig()
|
||||
m_tempo = SBig(m_tempo);
|
||||
}
|
||||
|
||||
void SongState::Channel::Header::swapBig()
|
||||
void SongState::Track::Header::swapBig()
|
||||
{
|
||||
m_type = SBig(m_type);
|
||||
m_pitchOff = SBig(m_pitchOff);
|
||||
m_modOff = SBig(m_modOff);
|
||||
}
|
||||
|
||||
SongState::Channel::Channel(SongState& parent, uint8_t midiChan, uint32_t startTick,
|
||||
const unsigned char* song, const unsigned char* chan)
|
||||
: m_parent(parent), m_midiChan(midiChan), m_startTick(startTick), m_dataBase(chan + 12)
|
||||
SongState::Track::Track(SongState& parent, uint8_t midiChan, const TrackRegion* regions)
|
||||
: m_parent(parent), m_midiChan(midiChan), m_curRegion(nullptr), m_nextRegion(regions)
|
||||
{
|
||||
m_data = m_dataBase;
|
||||
|
||||
Header header = *reinterpret_cast<const Header*>(chan);
|
||||
header.swapBig();
|
||||
if (header.m_type != 8)
|
||||
{
|
||||
m_data = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header.m_pitchOff)
|
||||
m_pitchWheelData = song + header.m_pitchOff;
|
||||
if (header.m_modOff)
|
||||
m_modWheelData = song + header.m_modOff;
|
||||
|
||||
m_waitCountdown = startTick;
|
||||
m_lastPitchTick = startTick;
|
||||
m_lastModTick = startTick;
|
||||
m_waitCountdown += int32_t(DecodeTimeRLE(m_data));
|
||||
for (int i=0 ; i<128 ; ++i)
|
||||
m_remNoteLengths[i] = INT_MIN;
|
||||
}
|
||||
|
||||
void SongState::initialize(const unsigned char* ptr)
|
||||
void SongState::Track::setRegion(Sequencer* seq, const TrackRegion* region)
|
||||
{
|
||||
m_header = *reinterpret_cast<const Header*>(ptr);
|
||||
m_header.swapBig();
|
||||
m_curRegion = region;
|
||||
uint32_t regionIdx = (m_parent.m_bigEndian ? SBig(m_curRegion->m_regionIndex) :
|
||||
m_curRegion->m_regionIndex);
|
||||
m_nextRegion = &m_curRegion[1];
|
||||
|
||||
/* Initialize all channels */
|
||||
m_data = m_parent.m_songData + (m_parent.m_bigEndian ? SBig(m_parent.m_regionIdx[regionIdx]) :
|
||||
m_parent.m_regionIdx[regionIdx]);
|
||||
|
||||
Header header = *reinterpret_cast<const Header*>(m_data);
|
||||
if (m_parent.m_bigEndian)
|
||||
header.swapBig();
|
||||
m_data += 12;
|
||||
|
||||
if (header.m_pitchOff)
|
||||
m_pitchWheelData = m_parent.m_songData + header.m_pitchOff;
|
||||
if (header.m_modOff)
|
||||
m_modWheelData = m_parent.m_songData + header.m_modOff;
|
||||
|
||||
m_eventWaitCountdown = 0;
|
||||
m_lastPitchTick = m_parent.m_curTick;
|
||||
m_lastPitchVal = 0;
|
||||
m_lastModTick = m_parent.m_curTick;
|
||||
m_lastModVal = 0;
|
||||
if (seq)
|
||||
{
|
||||
seq->setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 32768.f, 1.f));
|
||||
seq->setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127));
|
||||
}
|
||||
if (m_parent.m_sngVersion == 1)
|
||||
m_eventWaitCountdown = int32_t(DecodeTimeRLE(m_data));
|
||||
else
|
||||
{
|
||||
int32_t absTick = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data)) :
|
||||
*reinterpret_cast<const int32_t*>(m_data));
|
||||
m_eventWaitCountdown = absTick;
|
||||
m_lastN64EventTick = absTick;
|
||||
m_data += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void SongState::Track::advanceRegion(Sequencer* seq)
|
||||
{
|
||||
setRegion(seq, m_nextRegion);
|
||||
}
|
||||
|
||||
int SongState::DetectVersion(const unsigned char* ptr, bool& isBig)
|
||||
{
|
||||
isBig = ptr[0] == 0;
|
||||
Header header = *reinterpret_cast<const Header*>(ptr);
|
||||
if (isBig)
|
||||
header.swapBig();
|
||||
const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + header.m_trackIdxOff);
|
||||
const uint32_t* regionIdxTable = reinterpret_cast<const uint32_t*>(ptr + header.m_regionIdxOff);
|
||||
|
||||
/* First determine maximum index of MIDI regions across all tracks */
|
||||
uint32_t maxRegionIdx = 0;
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
{
|
||||
if (m_header.m_chanOffs[i])
|
||||
if (trackIdx[i])
|
||||
{
|
||||
ChanHeader cHeader = *reinterpret_cast<const ChanHeader*>(ptr + m_header.m_chanOffs[i]);
|
||||
cHeader.swapBig();
|
||||
const uint32_t* chanIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_chanIdxOff);
|
||||
const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff);
|
||||
m_channels[i].emplace(*this, chanMap[i], cHeader.m_startTick, ptr,
|
||||
ptr + SBig(chanIdx[cHeader.m_dataIndex]));
|
||||
const TrackRegion* region = nullptr;
|
||||
const TrackRegion* nextRegion = reinterpret_cast<const TrackRegion*>(ptr + (isBig ? SBig(trackIdx[i]) : trackIdx[i]));
|
||||
|
||||
/* Iterate all regions */
|
||||
while (nextRegion->indexValid(isBig))
|
||||
{
|
||||
region = nextRegion;
|
||||
uint32_t regionIdx = (isBig ? SBig(region->m_regionIndex) :
|
||||
region->m_regionIndex);
|
||||
maxRegionIdx = std::max(maxRegionIdx, regionIdx);
|
||||
nextRegion = ®ion[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform 2 trials, first assuming revised format (more likely) */
|
||||
int v=1;
|
||||
for (; v>=0 ; --v)
|
||||
{
|
||||
bool bad = false;
|
||||
|
||||
/* Validate all tracks */
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
{
|
||||
if (trackIdx[i])
|
||||
{
|
||||
const TrackRegion* region = nullptr;
|
||||
const TrackRegion* nextRegion = reinterpret_cast<const TrackRegion*>(ptr + (isBig ? SBig(trackIdx[i]) : trackIdx[i]));
|
||||
|
||||
/* Iterate all regions */
|
||||
while (nextRegion->indexValid(isBig))
|
||||
{
|
||||
region = nextRegion;
|
||||
uint32_t regionIdx = (isBig ? SBig(region->m_regionIndex) :
|
||||
region->m_regionIndex);
|
||||
nextRegion = ®ion[1];
|
||||
|
||||
const unsigned char* data = ptr + (isBig ? SBig(regionIdxTable[regionIdx]) :
|
||||
regionIdxTable[regionIdx]);
|
||||
|
||||
/* Can't reliably validate final region */
|
||||
if (regionIdx == maxRegionIdx)
|
||||
continue;
|
||||
|
||||
/* Expected end pointer (next region) */
|
||||
const unsigned char* expectedEnd = ptr + (isBig ? SBig(regionIdxTable[regionIdx+1]) :
|
||||
regionIdxTable[regionIdx+1]);
|
||||
|
||||
Track::Header header = *reinterpret_cast<const Track::Header*>(data);
|
||||
if (isBig)
|
||||
header.swapBig();
|
||||
data += 12;
|
||||
|
||||
/* continuous pitch data */
|
||||
if (header.m_pitchOff)
|
||||
{
|
||||
const unsigned char* dptr = ptr + header.m_pitchOff;
|
||||
while (DecodeRLE(dptr) != 0xffffffff) {DecodeContinuousRLE(dptr);}
|
||||
if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* continuous modulation data */
|
||||
if (header.m_modOff)
|
||||
{
|
||||
const unsigned char* dptr = ptr + header.m_modOff;
|
||||
while (DecodeRLE(dptr) != 0xffffffff) {DecodeContinuousRLE(dptr);}
|
||||
if (dptr >= (expectedEnd - 4) && (dptr <= expectedEnd))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
if (v == 1)
|
||||
{
|
||||
/* Revised */
|
||||
while (true)
|
||||
{
|
||||
/* Delta time */
|
||||
DecodeTimeRLE(data);
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(data) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
data += 2;
|
||||
break;
|
||||
}
|
||||
else if (data[0] & 0x80 && data[1] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
data += 2;
|
||||
}
|
||||
else if (data[0] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
data += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Note */
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
/* Delta-time */
|
||||
data += 4;
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(&data[2]) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
data += 4;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((data[2] & 0x80) != 0x80)
|
||||
{
|
||||
/* Note */
|
||||
}
|
||||
else if (data[2] & 0x80 && data[3] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
}
|
||||
else if (data[2] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
}
|
||||
data += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data < (expectedEnd - 4) || (data > expectedEnd))
|
||||
{
|
||||
bad = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bad)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bad)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
bool SongState::initialize(const unsigned char* ptr)
|
||||
{
|
||||
m_sngVersion = DetectVersion(ptr, m_bigEndian);
|
||||
if (m_sngVersion < 0)
|
||||
return false;
|
||||
|
||||
m_songData = ptr;
|
||||
m_header = *reinterpret_cast<const Header*>(ptr);
|
||||
if (m_bigEndian)
|
||||
m_header.swapBig();
|
||||
const uint32_t* trackIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_trackIdxOff);
|
||||
m_regionIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_regionIdxOff);
|
||||
const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff);
|
||||
|
||||
/* Initialize all tracks */
|
||||
for (int i=0 ; i<64 ; ++i)
|
||||
{
|
||||
if (trackIdx[i])
|
||||
{
|
||||
const TrackRegion* region = reinterpret_cast<const TrackRegion*>(ptr + (m_bigEndian ? SBig(trackIdx[i]) : trackIdx[i]));
|
||||
m_tracks[i].emplace(*this, chanMap[i], region);
|
||||
}
|
||||
else
|
||||
m_channels[i] = std::experimental::nullopt;
|
||||
m_tracks[i] = std::experimental::nullopt;
|
||||
}
|
||||
|
||||
/* Initialize tempo */
|
||||
@@ -160,15 +357,42 @@ void SongState::initialize(const unsigned char* ptr)
|
||||
m_tempo = m_header.m_initialTempo;
|
||||
m_curTick = 0;
|
||||
m_songState = SongPlayState::Playing;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
|
||||
bool SongState::Track::advance(Sequencer& seq, int32_t ticks)
|
||||
{
|
||||
if (!m_data)
|
||||
return true;
|
||||
|
||||
int32_t endTick = m_parent.m_curTick + ticks;
|
||||
|
||||
/* Advance region if needed */
|
||||
while (m_nextRegion->indexValid(m_parent.m_bigEndian))
|
||||
{
|
||||
uint32_t nextRegTick = (m_parent.m_bigEndian ? SBig(m_nextRegion->m_startTick) :
|
||||
m_nextRegion->m_startTick);
|
||||
if (endTick > nextRegTick)
|
||||
advanceRegion(&seq);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
/* Stop finished notes */
|
||||
for (int i=0 ; i<128 ; ++i)
|
||||
{
|
||||
if (m_remNoteLengths[i] != INT_MIN)
|
||||
{
|
||||
m_remNoteLengths[i] -= ticks;
|
||||
if (m_remNoteLengths[i] <= 0)
|
||||
{
|
||||
seq.keyOff(m_midiChan, i, 0);
|
||||
m_remNoteLengths[i] = INT_MIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_data)
|
||||
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
|
||||
|
||||
/* Update continuous pitch data */
|
||||
if (m_pitchWheelData)
|
||||
{
|
||||
@@ -224,7 +448,7 @@ bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
|
||||
m_lastModTick = nextTick;
|
||||
remModTicks -= (nextTick - modTick);
|
||||
modTick = nextTick;
|
||||
seq.setCtrlValue(m_midiChan, 1, clamp(0, (m_lastModVal + 8192) * 128 / 16384, 127));
|
||||
seq.setCtrlValue(m_midiChan, 1, clamp(0, m_lastModVal * 128 / 16384, 127));
|
||||
continue;
|
||||
}
|
||||
remModTicks -= (nextTick - modTick);
|
||||
@@ -235,61 +459,115 @@ bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
|
||||
}
|
||||
}
|
||||
|
||||
/* Stop finished notes */
|
||||
for (int i=0 ; i<128 ; ++i)
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
if (m_parent.m_sngVersion == 1)
|
||||
{
|
||||
if (m_remNoteLengths[i])
|
||||
/* Revision */
|
||||
while (true)
|
||||
{
|
||||
if (m_remNoteLengths[i] <= ticks)
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_eventWaitCountdown)
|
||||
{
|
||||
seq.keyOff(m_midiChan, i, 0);
|
||||
m_remNoteLengths[i] = 0;
|
||||
m_eventWaitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_eventWaitCountdown > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
|
||||
}
|
||||
else if (m_data[0] & 0x80 && m_data[1] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
uint8_t val = m_data[0] & 0x7f;
|
||||
uint8_t ctrl = m_data[1] & 0x7f;
|
||||
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||
m_data += 2;
|
||||
}
|
||||
else if (m_data[0] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
uint8_t prog = m_data[0] & 0x7f;
|
||||
seq.setChanProgram(m_midiChan, prog);
|
||||
m_data += 2;
|
||||
}
|
||||
else
|
||||
m_remNoteLengths[i] -= ticks;
|
||||
{
|
||||
/* Note */
|
||||
uint8_t note = m_data[0] & 0x7f;
|
||||
uint8_t vel = m_data[1] & 0x7f;
|
||||
uint16_t length = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data + 2)) :
|
||||
*reinterpret_cast<const uint16_t*>(m_data + 2));
|
||||
seq.keyOn(m_midiChan, note, vel);
|
||||
m_remNoteLengths[note] = length;
|
||||
m_data += 4;
|
||||
}
|
||||
|
||||
/* Set next delta-time */
|
||||
m_eventWaitCountdown += int32_t(DecodeTimeRLE(m_data));
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop through as many commands as we can for this time period */
|
||||
while (true)
|
||||
else
|
||||
{
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_waitCountdown)
|
||||
/* Legacy */
|
||||
while (true)
|
||||
{
|
||||
m_waitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_waitCountdown > 0)
|
||||
return false;
|
||||
}
|
||||
/* Advance wait timer if active, returning if waiting */
|
||||
if (m_eventWaitCountdown)
|
||||
{
|
||||
m_eventWaitCountdown -= ticks;
|
||||
ticks = 0;
|
||||
if (m_eventWaitCountdown > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
return true;
|
||||
}
|
||||
else if (m_data[0] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
uint8_t val = m_data[0] & 0x7f;
|
||||
uint8_t ctrl = m_data[1] & 0x7f;
|
||||
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||
m_data += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Note */
|
||||
uint8_t note = m_data[0] & 0x7f;
|
||||
uint8_t vel = m_data[1] & 0x7f;
|
||||
uint16_t length = SBig(*reinterpret_cast<const uint16_t*>(m_data + 2));
|
||||
seq.keyOn(m_midiChan, note, vel);
|
||||
m_remNoteLengths[note] = length;
|
||||
/* Load next command */
|
||||
if (*reinterpret_cast<const uint16_t*>(&m_data[2]) == 0xffff)
|
||||
{
|
||||
/* End of channel */
|
||||
m_data = nullptr;
|
||||
return !m_nextRegion->indexValid(m_parent.m_bigEndian);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((m_data[2] & 0x80) != 0x80)
|
||||
{
|
||||
/* Note */
|
||||
uint16_t length = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const uint16_t*>(m_data)) :
|
||||
*reinterpret_cast<const uint16_t*>(m_data));
|
||||
uint8_t note = m_data[2] & 0x7f;
|
||||
uint8_t vel = m_data[3] & 0x7f;
|
||||
seq.keyOn(m_midiChan, note, vel);
|
||||
m_remNoteLengths[note] = length;
|
||||
}
|
||||
else if (m_data[2] & 0x80 && m_data[3] & 0x80)
|
||||
{
|
||||
/* Control change */
|
||||
uint8_t val = m_data[2] & 0x7f;
|
||||
uint8_t ctrl = m_data[3] & 0x7f;
|
||||
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||
}
|
||||
else if (m_data[2] & 0x80)
|
||||
{
|
||||
/* Program change */
|
||||
uint8_t prog = m_data[2] & 0x7f;
|
||||
seq.setChanProgram(m_midiChan, prog);
|
||||
}
|
||||
m_data += 4;
|
||||
}
|
||||
|
||||
/* Set next delta-time */
|
||||
int32_t absTick = (m_parent.m_bigEndian ? SBig(*reinterpret_cast<const int32_t*>(m_data)) :
|
||||
*reinterpret_cast<const int32_t*>(m_data));
|
||||
m_eventWaitCountdown += absTick - m_lastN64EventTick;
|
||||
m_lastN64EventTick = absTick;
|
||||
m_data += 4;
|
||||
}
|
||||
|
||||
/* Set next delta-time */
|
||||
m_waitCountdown += int32_t(DecodeTimeRLE(m_data));
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -317,7 +595,8 @@ bool SongState::advance(Sequencer& seq, double dt)
|
||||
if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
|
||||
{
|
||||
TempoChange change = *m_tempoPtr;
|
||||
change.swapBig();
|
||||
if (m_bigEndian)
|
||||
change.swapBig();
|
||||
|
||||
if (m_curTick + remTicks > change.m_tick)
|
||||
remTicks = change.m_tick - m_curTick;
|
||||
@@ -326,15 +605,16 @@ bool SongState::advance(Sequencer& seq, double dt)
|
||||
{
|
||||
/* Turn over tempo */
|
||||
m_tempo = change.m_tempo;
|
||||
seq.setTempo(m_tempo * 384 / 60);
|
||||
++m_tempoPtr;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Advance all channels */
|
||||
for (std::experimental::optional<Channel>& chan : m_channels)
|
||||
if (chan)
|
||||
done &= chan->advance(seq, remTicks);
|
||||
/* Advance all tracks */
|
||||
for (std::experimental::optional<Track>& trk : m_tracks)
|
||||
if (trk)
|
||||
done &= trk->advance(seq, remTicks);
|
||||
|
||||
m_curTick += remTicks;
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "amuse/AudioGroupPool.hpp"
|
||||
#include <string.h>
|
||||
|
||||
#undef SendMessage
|
||||
#undef GetMessage
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
||||
@@ -49,7 +52,7 @@ float SoundMacroState::Evaluator::evaluate(const Voice& vox, const SoundMacroSta
|
||||
break;
|
||||
case 129:
|
||||
/* Aftertouch */
|
||||
thisValue = vox.getAftertouch();
|
||||
thisValue = vox.getAftertouch() * (2.f / 127.f);
|
||||
break;
|
||||
case 130:
|
||||
/* LFO1 */
|
||||
@@ -63,7 +66,7 @@ float SoundMacroState::Evaluator::evaluate(const Voice& vox, const SoundMacroSta
|
||||
break;
|
||||
case 132:
|
||||
/* Surround panning */
|
||||
thisValue = vox.m_curSpan * 64.f + 64.f;
|
||||
thisValue = vox.m_curSpan;
|
||||
break;
|
||||
case 133:
|
||||
/* Macro-starting key */
|
||||
@@ -75,10 +78,13 @@ float SoundMacroState::Evaluator::evaluate(const Voice& vox, const SoundMacroSta
|
||||
break;
|
||||
case 135:
|
||||
/* Time since macro-start (ms) */
|
||||
thisValue = st.m_execTime * 1000.f;
|
||||
thisValue = clamp(0.f, float(st.m_execTime * 1000.f), 16383.f);
|
||||
break;
|
||||
default:
|
||||
thisValue = vox.getCtrlValue(comp.m_midiCtrl);
|
||||
if (comp.m_midiCtrl == 10) /* Centered pan computation */
|
||||
thisValue = vox.getCtrlValue(comp.m_midiCtrl) * (2.f / 127.f) - 1.f;
|
||||
else
|
||||
thisValue = vox.getCtrlValue(comp.m_midiCtrl) * (2.f / 127.f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -135,7 +141,8 @@ void SoundMacroState::initialize(const unsigned char* ptr, int step, double tick
|
||||
m_loopCountdown = -1;
|
||||
m_lastPlayMacroVid = -1;
|
||||
m_useAdsrControllers = false;
|
||||
m_portamentoMode = 0;
|
||||
m_portamentoMode = 2;
|
||||
m_portamentoTime = 0.5f;
|
||||
m_header = *reinterpret_cast<const Header*>(ptr);
|
||||
if (swapData)
|
||||
m_header.swapBig();
|
||||
@@ -215,7 +222,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
|
||||
m_pc.back().second = macroStep;
|
||||
else
|
||||
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
|
||||
m_initKey, m_initVel, m_initMod);
|
||||
m_initKey, m_initVel, m_initMod);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -299,7 +306,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
|
||||
m_pc.back().second = macroStep;
|
||||
else
|
||||
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
|
||||
m_initKey, m_initVel, m_initMod);
|
||||
m_initKey, m_initVel, m_initMod);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -388,7 +395,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
|
||||
m_pc.back().second = macroStep;
|
||||
else
|
||||
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
|
||||
m_initKey, m_initVel, m_initMod);
|
||||
m_initKey, m_initVel, m_initMod);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -511,7 +518,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
|
||||
m_pc.back().second = macroStep;
|
||||
else
|
||||
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
|
||||
m_initKey, m_initVel, m_initMod);
|
||||
m_initKey, m_initVel, m_initMod);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -768,7 +775,7 @@ bool SoundMacroState::advance(Voice& vox, double dt)
|
||||
m_pc.push_back({m_pc.back().first, macroStep});
|
||||
else
|
||||
vox.loadSoundObject(macroId, macroStep, m_ticksPerSec,
|
||||
m_initKey, m_initVel, m_initMod, true);
|
||||
m_initKey, m_initVel, m_initMod, true);
|
||||
|
||||
m_header = *reinterpret_cast<const Header*>(m_pc.back().first);
|
||||
if (vox.getAudioGroup().getDataFormat() != DataFormat::PC)
|
||||
|
||||
@@ -52,4 +52,10 @@ void Submix::applyEffect(float* audio, size_t frameCount, const ChannelMap& chan
|
||||
((EffectBase<float>&)*effect).applyEffect(audio, frameCount, chanMap);
|
||||
}
|
||||
|
||||
void Submix::resetOutputSampleRate(double sampleRate)
|
||||
{
|
||||
for (const std::unique_ptr<EffectBaseTypeless>& effect : m_effectStack)
|
||||
effect->resetOutputSampleRate(sampleRate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
208
lib/Voice.cpp
208
lib/Voice.cpp
@@ -39,31 +39,6 @@ Voice::Voice(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid,
|
||||
//fprintf(stderr, "ALLOC %d\n", m_vid);
|
||||
}
|
||||
|
||||
void Voice::_reset()
|
||||
{
|
||||
m_curAftertouch = 0;
|
||||
m_pitchWheelUp = 600;
|
||||
m_pitchWheelDown = 600;
|
||||
m_pitchWheelVal = 0;
|
||||
m_pitchDirty = true;
|
||||
m_pitchSweep1 = 0;
|
||||
m_pitchSweep1Times = 0;
|
||||
m_pitchSweep1It = 0;
|
||||
m_pitchSweep2 = 0;
|
||||
m_pitchSweep2Times = 0;
|
||||
m_pitchSweep2It = 0;
|
||||
m_envelopeTime = -1.f;
|
||||
m_panningTime = -1.f;
|
||||
m_spanningTime = -1.f;
|
||||
m_vibratoLevel = 0;
|
||||
m_vibratoModLevel = 0;
|
||||
m_vibratoPeriod = 0.f;
|
||||
m_tremoloScale = 0.f;
|
||||
m_tremoloModScale = 0.f;
|
||||
m_lfoPeriods[0] = 0.f;
|
||||
m_lfoPeriods[1] = 0.f;
|
||||
}
|
||||
|
||||
void Voice::_macroSampleEnd()
|
||||
{
|
||||
if (m_sampleEndTrap.macroId != 0xffff)
|
||||
@@ -123,8 +98,18 @@ bool Voice::_checkSamplePos(bool& looped)
|
||||
|
||||
void Voice::_doKeyOff()
|
||||
{
|
||||
m_volAdsr.keyOff();
|
||||
m_pitchAdsr.keyOff();
|
||||
if (m_state.m_inWait && m_state.m_keyoffWait)
|
||||
{
|
||||
if (m_volAdsr.isAdsrSet() || m_state.m_useAdsrControllers)
|
||||
m_volAdsr.keyOff(*this);
|
||||
if (m_pitchAdsr.isAdsrSet())
|
||||
m_pitchAdsr.keyOff();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_volAdsr.keyOff(*this);
|
||||
m_pitchAdsr.keyOff();
|
||||
}
|
||||
m_state.keyoffNotify(*this);
|
||||
}
|
||||
|
||||
@@ -234,7 +219,7 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
{
|
||||
/* Lerp within 160-sample block */
|
||||
float t = rem / 160.f;
|
||||
float l = m_lastLevel * (1.f - t) + m_nextLevel * t;
|
||||
float l = clamp(0.f, m_lastLevel * (1.f - t) + m_nextLevel * t, 1.f);
|
||||
|
||||
/* Apply total volume to sample using decibel scale */
|
||||
ApplyVolume(l, samp);
|
||||
@@ -260,24 +245,92 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
t = (*m_envelopeCurve)[int(t*127.f)] / 127.f;
|
||||
m_curVol = clamp(0.f, (start * (1.0f - t)) + (end * t), 1.f);
|
||||
|
||||
//printf("%d %f\n", m_vid, m_curVol);
|
||||
|
||||
/* Done with envelope */
|
||||
if (m_envelopeTime > m_envelopeDur)
|
||||
m_envelopeTime = -1.f;
|
||||
}
|
||||
|
||||
/* Dynamically evaluate per-sample SoundMacro parameters */
|
||||
float evalVol = m_state.m_volumeSel ? (m_state.m_volumeSel.evaluate(*this, m_state) / 2.f * m_curVol) : m_curVol;
|
||||
|
||||
bool panDirty = false;
|
||||
if (m_state.m_panSel)
|
||||
{
|
||||
float evalPan = m_state.m_panSel.evaluate(*this, m_state);
|
||||
if (evalPan != m_curPan)
|
||||
{
|
||||
m_curPan = evalPan;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (m_state.m_spanSel)
|
||||
{
|
||||
float evalSpan = m_state.m_spanSel.evaluate(*this, m_state);
|
||||
if (evalSpan != m_curSpan)
|
||||
{
|
||||
m_curSpan = evalSpan;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (m_state.m_reverbSel)
|
||||
{
|
||||
float evalRev = m_state.m_reverbSel.evaluate(*this, m_state) / 2.f;
|
||||
if (evalRev != m_curReverbVol)
|
||||
{
|
||||
m_curReverbVol = evalRev;
|
||||
panDirty = true;
|
||||
}
|
||||
}
|
||||
if (panDirty)
|
||||
_setPan(m_curPan);
|
||||
|
||||
if (m_state.m_pitchWheelSel)
|
||||
_setPitchWheel(m_state.m_pitchWheelSel.evaluate(*this, m_state));
|
||||
|
||||
/* Process user volume slew */
|
||||
if (m_engine.m_ampMode == AmplitudeMode::PerSample)
|
||||
{
|
||||
if (m_targetUserVol != m_curUserVol)
|
||||
{
|
||||
float samplesPer5Ms = m_sampleRate * 5.f / 1000.f;
|
||||
if (samplesPer5Ms > 1.f)
|
||||
{
|
||||
float adjRate = 1.f / samplesPer5Ms;
|
||||
if (m_targetUserVol < m_curUserVol)
|
||||
{
|
||||
m_curUserVol -= adjRate;
|
||||
if (m_targetUserVol > m_curUserVol)
|
||||
m_curUserVol = m_targetUserVol;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_curUserVol += adjRate;
|
||||
if (m_targetUserVol < m_curUserVol)
|
||||
m_curUserVol = m_targetUserVol;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_curUserVol = m_targetUserVol;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_curUserVol = m_targetUserVol;
|
||||
|
||||
/* Factor in ADSR envelope state */
|
||||
float adsr = m_volAdsr.advance(dt);
|
||||
float adsr = m_volAdsr.advance(dt, *this);
|
||||
m_lastLevel = m_nextLevel;
|
||||
m_nextLevel = m_userVol * m_curVol * adsr * (m_state.m_curVel / 127.f);
|
||||
m_nextLevel = m_curUserVol * evalVol * adsr * (m_state.m_curVel / 127.f);
|
||||
|
||||
/* Apply tremolo */
|
||||
if (m_state.m_tremoloSel && (m_tremoloScale || m_tremoloModScale))
|
||||
{
|
||||
float t = m_state.m_tremoloSel.evaluate(*this, m_state);
|
||||
float t = m_state.m_tremoloSel.evaluate(*this, m_state) / 2.f;
|
||||
if (m_tremoloScale && m_tremoloModScale)
|
||||
{
|
||||
float fac = (1.0f - t) + (m_tremoloScale * t);
|
||||
float modT = getModWheel() / 127.f;
|
||||
float modT = m_state.m_modWheelSel ? (m_state.m_modWheelSel.evaluate(*this, m_state) / 2.f) : (getCtrlValue(1) / 127.f);
|
||||
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
|
||||
m_nextLevel *= fac * modFac;
|
||||
}
|
||||
@@ -288,13 +341,13 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
}
|
||||
else if (m_tremoloModScale)
|
||||
{
|
||||
float modT = getModWheel() / 127.f;
|
||||
float modT = m_state.m_modWheelSel ? (m_state.m_modWheelSel.evaluate(*this, m_state) / 2.f) : (getCtrlValue(1) / 127.f);
|
||||
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
|
||||
m_nextLevel *= modFac;
|
||||
}
|
||||
}
|
||||
|
||||
m_nextLevel = ClampFull<float>(m_nextLevel);
|
||||
m_nextLevel = clamp(0.f, m_nextLevel, 1.f);
|
||||
|
||||
/* Apply total volume to sample using decibel scale */
|
||||
ApplyVolume(m_nextLevel, samp);
|
||||
@@ -333,9 +386,24 @@ bool Voice::_advanceSample(int16_t& samp, int32_t& newPitch)
|
||||
newPitch = m_curPitch;
|
||||
refresh |= m_pitchDirty;
|
||||
m_pitchDirty = false;
|
||||
if (m_portamentoTime >= 0.f)
|
||||
{
|
||||
m_portamentoTime += dt;
|
||||
float t = std::max(0.f, std::min(1.f, m_portamentoTime / m_state.m_portamentoTime));
|
||||
|
||||
newPitch = (m_curPitch * (1.0f - t)) + (m_portamentoTarget * t);
|
||||
refresh = true;
|
||||
|
||||
/* Done with portamento */
|
||||
if (m_portamentoTime > m_state.m_portamentoTime)
|
||||
{
|
||||
m_portamentoTime = -1.f;
|
||||
m_curPitch = m_portamentoTarget;
|
||||
}
|
||||
}
|
||||
if (m_pitchEnv)
|
||||
{
|
||||
newPitch = m_curPitch * m_pitchAdsr.advance(dt);
|
||||
newPitch *= m_pitchAdsr.advance(dt);
|
||||
refresh = true;
|
||||
}
|
||||
|
||||
@@ -380,6 +448,14 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
|
||||
/* Process SoundMacro; bootstrapping sample if needed */
|
||||
bool dead = m_state.advance(*this, samples / m_sampleRate);
|
||||
|
||||
/* Process per-block evaluators here */
|
||||
if (m_state.m_pedalSel)
|
||||
{
|
||||
bool pedal = m_state.m_pedalSel.evaluate(*this, m_state) >= 1.f;
|
||||
if (pedal != m_sustained)
|
||||
setPedal(pedal);
|
||||
}
|
||||
|
||||
if (m_curSample)
|
||||
{
|
||||
uint32_t blockSampleCount = _GetBlockSampleCount(m_curFormat);
|
||||
@@ -427,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;
|
||||
}
|
||||
@@ -492,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;
|
||||
}
|
||||
@@ -538,6 +614,7 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
|
||||
m_voxState = VoiceState::Dead;
|
||||
m_backendVoice->stop();
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@@ -559,7 +636,7 @@ std::shared_ptr<Voice> Voice::_startChildMacro(ObjectId macroId, int macroStep,
|
||||
_destroyVoice(vox);
|
||||
return {};
|
||||
}
|
||||
(*vox)->setVolume(m_userVol);
|
||||
(*vox)->setVolume(m_targetUserVol);
|
||||
(*vox)->setPan(m_userPan);
|
||||
(*vox)->setSurroundPan(m_userSpan);
|
||||
return *vox;
|
||||
@@ -713,10 +790,10 @@ void Voice::startSample(int16_t sampId, int32_t offset)
|
||||
m_curSample = m_audioGroup.getSample(sampId);
|
||||
if (m_curSample)
|
||||
{
|
||||
_reset();
|
||||
m_sampleRate = m_curSample->first.m_sampleRate;
|
||||
m_curPitch = m_curSample->first.m_pitch;
|
||||
setPitchWheel(m_curPitchWheel);
|
||||
m_pitchDirty = true;
|
||||
_setPitchWheel(m_curPitchWheel);
|
||||
m_backendVoice->resetSampleRate(m_curSample->first.m_sampleRate);
|
||||
|
||||
int32_t numSamples = m_curSample->first.m_numSamples & 0xffffff;
|
||||
@@ -774,7 +851,7 @@ void Voice::stopSample()
|
||||
|
||||
void Voice::setVolume(float vol)
|
||||
{
|
||||
m_userVol = clamp(0.f, vol, 1.f);
|
||||
m_targetUserVol = clamp(0.f, vol, 1.f);
|
||||
for (std::shared_ptr<Voice>& vox : m_childVoices)
|
||||
vox->setVolume(vol);
|
||||
}
|
||||
@@ -865,7 +942,7 @@ void Voice::startFadeIn(double dur, float vol, const Curve* envCurve)
|
||||
|
||||
void Voice::startPanning(double dur, uint8_t panPos, int8_t panWidth)
|
||||
{
|
||||
m_panningTime = m_voiceTime;
|
||||
m_panningTime = 0.f;
|
||||
m_panningDur = dur;
|
||||
m_panPos = panPos;
|
||||
m_panWidth = panWidth;
|
||||
@@ -873,7 +950,7 @@ void Voice::startPanning(double dur, uint8_t panPos, int8_t panWidth)
|
||||
|
||||
void Voice::startSpanning(double dur, uint8_t spanPos, int8_t spanWidth)
|
||||
{
|
||||
m_spanningTime = m_voiceTime;
|
||||
m_spanningTime = 0.f;
|
||||
m_spanningDur = dur;
|
||||
m_spanPos = spanPos;
|
||||
m_spanWidth = spanWidth;
|
||||
@@ -953,7 +1030,7 @@ void Voice::setAdsr(ObjectId adsrId, bool dls)
|
||||
{
|
||||
m_volAdsr.reset(adsr, m_state.m_initKey, m_state.m_initVel);
|
||||
if (m_voxState == VoiceState::KeyOff)
|
||||
m_volAdsr.keyOff();
|
||||
m_volAdsr.keyOff(*this);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -963,7 +1040,7 @@ void Voice::setAdsr(ObjectId adsrId, bool dls)
|
||||
{
|
||||
m_volAdsr.reset(adsr);
|
||||
if (m_voxState == VoiceState::KeyOff)
|
||||
m_volAdsr.keyOff();
|
||||
m_volAdsr.keyOff(*this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -986,9 +1063,8 @@ void Voice::setPitchAdsr(ObjectId adsrId, int32_t cents)
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::setPitchWheel(float pitchWheel)
|
||||
void Voice::_setPitchWheel(float pitchWheel)
|
||||
{
|
||||
m_curPitchWheel = amuse::clamp(-1.f, pitchWheel, 1.f);
|
||||
if (pitchWheel > 0.f)
|
||||
m_pitchWheelVal = m_pitchWheelUp * m_curPitchWheel;
|
||||
else if (pitchWheel < 0.f)
|
||||
@@ -996,6 +1072,12 @@ void Voice::setPitchWheel(float pitchWheel)
|
||||
else
|
||||
m_pitchWheelVal = 0;
|
||||
m_pitchDirty = true;
|
||||
}
|
||||
|
||||
void Voice::setPitchWheel(float pitchWheel)
|
||||
{
|
||||
m_curPitchWheel = amuse::clamp(-1.f, pitchWheel, 1.f);
|
||||
_setPitchWheel(m_curPitchWheel);
|
||||
|
||||
for (std::shared_ptr<Voice>& vox : m_childVoices)
|
||||
vox->setPitchWheel(pitchWheel);
|
||||
@@ -1005,7 +1087,7 @@ void Voice::setPitchWheelRange(int8_t up, int8_t down)
|
||||
{
|
||||
m_pitchWheelUp = up * 100;
|
||||
m_pitchWheelDown = down * 100;
|
||||
setPitchWheel(m_curPitchWheel);
|
||||
_setPitchWheel(m_curPitchWheel);
|
||||
}
|
||||
|
||||
void Voice::setAftertouch(uint8_t aftertouch)
|
||||
@@ -1015,6 +1097,32 @@ void Voice::setAftertouch(uint8_t aftertouch)
|
||||
vox->setAftertouch(aftertouch);
|
||||
}
|
||||
|
||||
bool Voice::doPortamento(uint8_t newNote)
|
||||
{
|
||||
bool pState;
|
||||
switch (m_state.m_portamentoMode)
|
||||
{
|
||||
case 0:
|
||||
default:
|
||||
pState = false;
|
||||
break;
|
||||
case 1:
|
||||
pState = true;
|
||||
break;
|
||||
case 2:
|
||||
pState = m_state.m_portamentoSel ? (m_state.m_portamentoSel.evaluate(*this, m_state) >= 1.f) : (getCtrlValue(65) >= 64);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pState)
|
||||
return false;
|
||||
|
||||
m_portamentoTime = 0.f;
|
||||
m_portamentoTarget = newNote * 100;
|
||||
m_state.m_initKey = newNote;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Voice::_notifyCtrlChange(uint8_t ctrl, int8_t val)
|
||||
{
|
||||
if (ctrl == 0x40)
|
||||
@@ -1024,10 +1132,6 @@ void Voice::_notifyCtrlChange(uint8_t ctrl, int8_t val)
|
||||
else
|
||||
setPedal(false);
|
||||
}
|
||||
else if (ctrl == 0x41)
|
||||
{
|
||||
printf("PORTAMENTO %d\n", val);
|
||||
}
|
||||
else if (ctrl == 0x5b)
|
||||
{
|
||||
setReverbVol(val / 127.f);
|
||||
|
||||
Reference in New Issue
Block a user