diff --git a/AudioUnit/AmuseContainer.entitlements.in b/AudioUnit/AmuseContainer.entitlements.in index 44210a9..a0c9333 100644 --- a/AudioUnit/AmuseContainer.entitlements.in +++ b/AudioUnit/AmuseContainer.entitlements.in @@ -6,6 +6,10 @@ @APPLE_TEAM_ID@.@APPLE_BUNDLE_ID@ com.apple.developer.team-identifier @APPLE_TEAM_ID@ + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + com.apple.security.application-groups group.io.github.axiodl.Amuse.AudioGroups diff --git a/AudioUnit/AmuseContainerMainMenu.xib b/AudioUnit/AmuseContainerMainMenu.xib index 83ba70a..be4a6ce 100644 --- a/AudioUnit/AmuseContainerMainMenu.xib +++ b/AudioUnit/AmuseContainerMainMenu.xib @@ -17,7 +17,10 @@ + + + @@ -69,7 +72,7 @@ - + @@ -77,17 +80,15 @@ - + - + + +CA + - - - - - - + @@ -166,128 +167,86 @@ + - - + - - + + - + - - + + - - + + - + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -296,36 +255,34 @@ - - - - - + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -334,74 +291,39 @@ - - - - - - - - - - - - - - - - - + + + - - + + - - + + - + - + - + - + @@ -409,39 +331,39 @@ - - - + + + - + - - + + - + - + - + - + - + @@ -449,11 +371,11 @@ - - + + - + @@ -461,13 +383,13 @@ - + - - - - - - - - - - - - - - - - - - - + + + + @@ -504,74 +412,39 @@ - - - - - - - - - - - - - - - - - + + + - - + + - - + + - + - + - + - + @@ -579,39 +452,39 @@ - - - + + + - + - - + + - + - + - + - + - + @@ -619,11 +492,11 @@ - - + + - + @@ -631,13 +504,13 @@ - + - - - - - - - - - - - - - - - - - - - + + + + @@ -685,10 +544,10 @@ - + - + @@ -715,10 +574,10 @@ - - - - + + + + diff --git a/AudioUnit/AmuseContainingApp.hpp b/AudioUnit/AmuseContainingApp.hpp new file mode 100644 index 0000000..5441ba5 --- /dev/null +++ b/AudioUnit/AmuseContainingApp.hpp @@ -0,0 +1,61 @@ +#ifndef __AMUSE_AUDIOUNIT_CONTAININGAPP_HPP__ +#define __AMUSE_AUDIOUNIT_CONTAININGAPP_HPP__ + +#import +#import "AudioGroupFilePresenter.hpp" +#include +#include + +@interface DataOutlineView : NSOutlineView +{ +@public + IBOutlet NSButton* removeDataButton; + IBOutlet NSMenuItem* deleteMenuItem; +} +@end + +@interface SamplesTableController : NSObject +{ + AudioGroupFilePresenter* presenter; +} +- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present; +@end + +@interface SFXTableController : NSObject +{ + AudioGroupFilePresenter* presenter; +} +- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present; +@end + +@interface AppDelegate : NSObject +{ + 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 booEngine; + std::experimental::optional amuseAllocator; + std::experimental::optional amuseEngine; + std::shared_ptr 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__ \ No newline at end of file diff --git a/AudioUnit/AmuseContainingApp.mm b/AudioUnit/AmuseContainingApp.mm index 6667e68..bbd85b7 100644 --- a/AudioUnit/AmuseContainingApp.mm +++ b/AudioUnit/AmuseContainingApp.mm @@ -2,30 +2,47 @@ #import #import #import "AudioUnitViewController.hpp" +#import "AmuseContainingApp.hpp" +#include -@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 + +@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> 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 diff --git a/AudioUnit/AudioGroupFilePresenter.hpp b/AudioUnit/AudioGroupFilePresenter.hpp index ccfabfb..e15f64c 100644 --- a/AudioUnit/AudioGroupFilePresenter.hpp +++ b/AudioUnit/AudioGroupFilePresenter.hpp @@ -2,36 +2,158 @@ #define __AMUSE_AUDIOUNIT_AUDIOGROUPFILEPRESENTER_HPP__ #import +#import #include #include +#include "optional.hpp" +#include +#include @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 m_projData; + std::vector m_poolData; + std::vector m_sdirData; + std::vector 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 m_metaData; + + std::experimental::optional m_loadedData; + const amuse::AudioGroup* m_loadedGroup; + std::vector m_groupTokens; - bool invalidateURL(NSURL* url); void moveURL(NSURL* oldUrl, NSURL* newUrl); - std::unique_ptr _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 coordinateProjRead(AudioGroupFilePresenter* presenter, size_t& szOut); - std::unique_ptr coordinatePoolRead(AudioGroupFilePresenter* presenter, size_t& szOut); - std::unique_ptr coordinateSdirRead(AudioGroupFilePresenter* presenter, size_t& szOut); - std::unique_ptr 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 +struct AudioGroupCollection { - NSURL* m_groupURL; - NSOperationQueue* m_dataQueue; - std::map m_audioGroupCollections; -} + NSURL* m_url; + + AudioGroupCollectionToken* m_token; + std::map> m_groups; + std::vector>::iterator> m_filterGroups; + + AudioGroupCollection(NSURL* url); + void addCollection(AudioGroupFilePresenter* presenter, + std::vector>&& collection); + void update(AudioGroupFilePresenter* presenter); + bool doSearch(const std::string& str); + bool doActiveFilter(); + void addSFX(std::vector& vecOut); + void addSamples(std::vector& 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* m_sample; +} +- (id)initWithName:(NSAttributedString*)name samp:(const std::pair*)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 +{ +@public + id m_audioGroupClient; + NSURL* m_groupURL; + std::map> m_audioGroupCollections; + std::vector>::iterator> m_filterAudioGroupCollections; + NSOutlineView* m_lastOutlineView; + NSString* m_searchStr; + + std::vector m_sfxTableData; + std::vector m_sampleTableData; +} +- (id)initWithAudioGroupClient:(id)client; +- (BOOL)addCollectionName:(std::string&&)name items:(std::vector>&&)collection; +- (void)update; +- (void)resetIterators; +- (void)setSearchFilter:(NSString*)str; +- (void)removeSelectedItem; @end #endif // __AMUSE_AUDIOUNIT_AUDIOGROUPFILEPRESENTER_HPP__ diff --git a/AudioUnit/AudioGroupFilePresenter.mm b/AudioUnit/AudioGroupFilePresenter.mm index fd0e122..4febcc3 100644 --- a/AudioUnit/AudioGroupFilePresenter.mm +++ b/AudioUnit/AudioGroupFilePresenter.mm @@ -1,5 +1,75 @@ #include "AudioGroupFilePresenter.hpp" #include +#include +#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*)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>&& collection) { - bool valid = false; - if (m_proj) + for (std::pair& 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(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* 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(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& 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 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 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& 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*> 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 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 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 AudioGroupDataCollection::_coordinateRead(AudioGroupFilePresenter* presenter, size_t& szOut, NSURL* url) -{ - NSFileCoordinator* coord = [[NSFileCoordinator alloc] initWithFilePresenter:presenter]; - if (!coord) - return {}; - NSError* err; - __block std::unique_ptr 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 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& 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 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& 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 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& 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 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& 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& 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& 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& 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)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)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>&&)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(dir)).first->second; + insert.addCollection(self, std::move(collection)); + + [coord coordinateWritingItemAtURL:m_groupURL options:0 error:nil + byAccessor:^(NSURL* newUrl) + { + for (std::pair>& 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>& theMap = m_audioGroupCollections; + __block AudioGroupFilePresenter* presenter = self; + [coord coordinateReadingItemAtURL:m_groupURL options:NSFileCoordinatorReadingResolvesSymbolicLink error:&coordErr + byAccessor:^(NSURL* newUrl) + { + NSFileManager* fman = [NSFileManager defaultManager]; + NSArray* 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(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)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; } diff --git a/AudioUnit/AudioUnitBackend.hpp b/AudioUnit/AudioUnitBackend.hpp index ac90527..7dda518 100644 --- a/AudioUnit/AudioUnitBackend.hpp +++ b/AudioUnit/AudioUnitBackend.hpp @@ -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 { +@public + AudioUnitViewController* m_viewController; std::unique_ptr m_booBackend; std::experimental::optional m_voxAlloc; std::experimental::optional m_engine; + AudioGroupFilePresenter* m_filePresenter; + AUAudioUnitBus* m_outBus; AUAudioUnitBusArray* m_outs; } +- (nullable id)initWithComponentDescription:(AudioComponentDescription)componentDescription + error:(NSError * __nullable * __nonnull)outError + viewController:(AudioUnitViewController* __nonnull)vc; +- (void)requestAudioGroup:(AudioGroupToken*)group; @end #endif diff --git a/AudioUnit/AudioUnitBackend.mm b/AudioUnit/AudioUnitBackend.mm index 0c5f746..c52931f 100644 --- a/AudioUnit/AudioUnitBackend.mm +++ b/AudioUnit/AudioUnitBackend.mm @@ -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 m_interleavedBuf; std::vector> 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 newVirtualMIDIIn(boo::ReceiveFunctor&& receiver) { - m_midiReceiver = std::move(receiver); - return {}; + std::unique_ptr ret = std::make_unique(true, std::move(receiver)); + m_midiReceiver = &ret->m_receiver; + return ret; } std::unique_ptr 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 ; imNumberBuffers ; ++i) + for (size_t i=0 ; i& 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(auBuf.mData)); + for (size_t f=0 ; f(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(); 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(*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(*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(*m_booBackend); + voxEngine.m_renderBufs.clear(); } - (AUAudioUnitBusArray*)outputBusses @@ -184,11 +244,25 @@ struct AudioUnitVoiceEngine : boo::BaseAudioVoiceEngine { __block AudioUnitVoiceEngine& voxEngine = static_cast(*m_booBackend); __block amuse::Engine& amuseEngine = *m_engine; + __block std::shared_ptr 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(std::cbegin(event->data), - std::cbegin(event->data) + event->length)); + (*voxEngine.m_midiReceiver)(std::vector(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 diff --git a/AudioUnit/AudioUnitViewController.hpp b/AudioUnit/AudioUnitViewController.hpp index 77da5ce..a3b4831 100644 --- a/AudioUnit/AudioUnitViewController.hpp +++ b/AudioUnit/AudioUnitViewController.hpp @@ -2,9 +2,24 @@ #define __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__ #import +#import "AudioGroupFilePresenter.hpp" + +@class AmuseAudioUnit; + +@interface GroupBrowserDelegate : NSObject +{ + AmuseAudioUnit* m_audioUnit; +} +- (id)initWithAudioUnit:(AmuseAudioUnit*)au; +@end @interface AudioUnitViewController : AUViewController - +{ +@public + AmuseAudioUnit* m_audioUnit; + IBOutlet NSBrowser* m_groupBrowser; + GroupBrowserDelegate* m_groupBrowserDelegate; +} @end #endif // __AMUSE_AUDIOUNIT_VIEWCONTROLLER_HPP__ diff --git a/AudioUnit/AudioUnitViewController.mm b/AudioUnit/AudioUnitViewController.mm index 6cf6ef9..4957577 100644 --- a/AudioUnit/AudioUnitViewController.mm +++ b/AudioUnit/AudioUnitViewController.mm @@ -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 diff --git a/AudioUnit/AudioUnitViewController.xib b/AudioUnit/AudioUnitViewController.xib new file mode 100644 index 0000000..b4207d9 --- /dev/null +++ b/AudioUnit/AudioUnitViewController.xib @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/AudioUnit/CMakeLists.txt b/AudioUnit/CMakeLists.txt index 13b4e71..b7a7aed 100644 --- a/AudioUnit/CMakeLists.txt +++ b/AudioUnit/CMakeLists.txt @@ -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" + "$/../Resources/AudioUnitViewController.nib" COMMAND ${CMAKE_COMMAND} -E copy "${PROV_PROFILE}" "$/../embedded.provisionprofile" COMMAND ${CMAKE_COMMAND} -E remove_directory "$/../PlugIns/amuse-au.appex" COMMAND ${CMAKE_COMMAND} -E copy_directory "$/../.." diff --git a/AudioUnit/ContainerInfo.plist b/AudioUnit/ContainerInfo.plist index c8d7614..21fbe26 100644 --- a/AudioUnit/ContainerInfo.plist +++ b/AudioUnit/ContainerInfo.plist @@ -4,6 +4,17 @@ CFBundleDevelopmentRegion en + CFBundleDocumentTypes + + + CFBundleTypeRole + Viewer + LSItemContentTypes + + public.data + + + CFBundleExecutable amuse-au-container CFBundleIconFile diff --git a/include/amuse/AudioGroup.hpp b/include/amuse/AudioGroup.hpp index ac79f78..5735192 100644 --- a/include/amuse/AudioGroup.hpp +++ b/include/amuse/AudioGroup.hpp @@ -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;} }; diff --git a/include/amuse/AudioGroupData.hpp b/include/amuse/AudioGroupData.hpp index 4328d70..9a6d2ad 100644 --- a/include/amuse/AudioGroupData.hpp +++ b/include/amuse/AudioGroupData.hpp @@ -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;} }; } diff --git a/include/amuse/AudioGroupSampleDirectory.hpp b/include/amuse/AudioGroupSampleDirectory.hpp index 7da4978..2e4679f 100644 --- a/include/amuse/AudioGroupSampleDirectory.hpp +++ b/include/amuse/AudioGroupSampleDirectory.hpp @@ -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>& sampleEntries() const {return m_entries;} }; } diff --git a/include/amuse/BooBackend.hpp b/include/amuse/BooBackend.hpp index 9af8b7f..8485168 100644 --- a/include/amuse/BooBackend.hpp +++ b/include/amuse/BooBackend.hpp @@ -10,7 +10,6 @@ #include "IBackendVoiceAllocator.hpp" #include #include -#include namespace amuse { @@ -76,13 +75,14 @@ class BooBackendMIDIReader : public IMIDIReader, public boo::IMIDIReader std::unique_ptr m_midiIn; boo::MIDIDecoder m_decoder; - std::list>> m_queue; + bool m_useLock; + std::list>> m_queue; std::mutex m_midiMutex; - void _MIDIReceive(std::vector&& bytes); + void _MIDIReceive(std::vector&& 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); diff --git a/include/amuse/ContainerRegistry.hpp b/include/amuse/ContainerRegistry.hpp index ca670f8..a8fb8b5 100644 --- a/include/amuse/ContainerRegistry.hpp +++ b/include/amuse/ContainerRegistry.hpp @@ -37,6 +37,7 @@ public: static const char* TypeToName(Type tp); static Type DetectContainerType(const char* path); static std::vector> LoadContainer(const char* path); + static std::vector> LoadContainer(const char* path, Type& typeOut); static std::vector> LoadSongs(const char* path); }; diff --git a/include/amuse/Sequencer.hpp b/include/amuse/Sequencer.hpp index d512d1d..e5001a1 100644 --- a/include/amuse/Sequencer.hpp +++ b/include/amuse/Sequencer.hpp @@ -28,7 +28,7 @@ class Sequencer : public Entity { friend class Engine; const SongGroupIndex& m_songGroup; /**< Quick access to song group project index */ - const SongGroupIndex::MIDISetup* m_midiSetup = nullptr; /**< Selected MIDI setup */ + const SongGroupIndex::MIDISetup* m_midiSetup = nullptr; /**< Selected MIDI setup (may be null) */ Submix* m_submix = nullptr; /**< Submix this sequencer outputs to (or NULL for the main output mix) */ const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */ @@ -44,7 +44,7 @@ class Sequencer : public Entity { Sequencer& m_parent; uint8_t m_chanId; - const SongGroupIndex::MIDISetup& m_setup; + const SongGroupIndex::MIDISetup* m_setup = nullptr; /* Channel defaults to program 0 if null */ const SongGroupIndex::PageEntry* m_page = nullptr; ~ChannelState(); ChannelState(Sequencer& parent, uint8_t chanId); @@ -79,6 +79,7 @@ class Sequencer : public Entity void _bringOutYourDead(); void _destroy(); + public: ~Sequencer(); Sequencer(Engine& engine, const AudioGroup& group, int groupId, diff --git a/lib/AudioGroupData.cpp b/lib/AudioGroupData.cpp index a68a241..3b2083e 100644 --- a/lib/AudioGroupData.cpp +++ b/lib/AudioGroupData.cpp @@ -15,7 +15,9 @@ IntrusiveAudioGroupData::~IntrusiveAudioGroupData() } IntrusiveAudioGroupData::IntrusiveAudioGroupData(IntrusiveAudioGroupData&& other) -: AudioGroupData(other.m_proj, other.m_pool, other.m_sdir, other.m_samp, other.m_fmt, other.m_absOffs) +: AudioGroupData(other.m_proj, other.m_projSz, other.m_pool, other.m_poolSz, + other.m_sdir, other.m_sdirSz, other.m_samp, other.m_sampSz, + other.m_fmt, other.m_absOffs) { m_owns = other.m_owns; other.m_owns = false; diff --git a/lib/AudioGroupSampleDirectory.cpp b/lib/AudioGroupSampleDirectory.cpp index 7f9baff..6bed603 100644 --- a/lib/AudioGroupSampleDirectory.cpp +++ b/lib/AudioGroupSampleDirectory.cpp @@ -145,7 +145,7 @@ AudioGroupSampleDirectory::AudioGroupSampleDirectory(const unsigned char* data, std::pair& 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& 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; diff --git a/lib/BooBackend.cpp b/lib/BooBackend.cpp index a2fc95a..b69355d 100644 --- a/lib/BooBackend.cpp +++ b/lib/BooBackend.cpp @@ -2,6 +2,7 @@ #include "amuse/Voice.hpp" #include "amuse/Submix.hpp" #include "amuse/Engine.hpp" +#include namespace amuse { @@ -119,8 +120,8 @@ std::string BooBackendMIDIReader::description() BooBackendMIDIReader::~BooBackendMIDIReader() {} -BooBackendMIDIReader::BooBackendMIDIReader(Engine& engine, const char* name) -: m_engine(engine), m_decoder(*this) +BooBackendMIDIReader::BooBackendMIDIReader(Engine& engine, const char* name, bool useLock) +: m_engine(engine), m_decoder(*this), m_useLock(useLock) { BooBackendVoiceAllocator& voxAlloc = static_cast(engine.getBackend()); if (!name) @@ -130,40 +131,46 @@ BooBackendMIDIReader::BooBackendMIDIReader(Engine& engine, const char* name) { m_midiIn = voxAlloc.m_booEngine.newRealMIDIIn(dev.first.c_str(), std::bind(&BooBackendMIDIReader::_MIDIReceive, this, - std::placeholders::_1)); + std::placeholders::_1, std::placeholders::_2)); if (m_midiIn) return; } m_midiIn = voxAlloc.m_booEngine.newVirtualMIDIIn(std::bind(&BooBackendMIDIReader::_MIDIReceive, this, - std::placeholders::_1)); + std::placeholders::_1, std::placeholders::_2)); } else m_midiIn = voxAlloc.m_booEngine.newRealMIDIIn(name, std::bind(&BooBackendMIDIReader::_MIDIReceive, this, - std::placeholders::_1)); + std::placeholders::_1, std::placeholders::_2)); } -void BooBackendMIDIReader::_MIDIReceive(std::vector&& bytes) +void BooBackendMIDIReader::_MIDIReceive(std::vector&& bytes, double time) { - std::unique_lock lk(m_midiMutex); - m_queue.emplace_back(std::chrono::steady_clock::now(), std::move(bytes)); + std::unique_lock 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 lk(m_midiMutex); + std::unique_lock 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 - (periodEnd->first - startPt).count() / 1000000.0; + double delta = periodEnd->first - startPt; if (delta > dt) break; } @@ -174,6 +181,15 @@ void BooBackendMIDIReader::pumpReader(double dt) /* Dispatch buffers */ for (auto it = m_queue.begin() ; it != periodEnd ;) { +#if 0 + char str[64]; + sprintf(str, "MIDI %zu %f ", it->second.size(), it->first); + for (uint8_t byte : it->second) + sprintf(str + strlen(str), "%02X ", byte); + openlog("LogIt", (LOG_CONS|LOG_PERROR|LOG_PID), LOG_DAEMON); + syslog(LOG_EMERG, "%s\n", str); + closelog(); +#endif m_decoder.receiveBytes(it->second.cbegin(), it->second.cend()); it = m_queue.erase(it); } @@ -183,12 +199,22 @@ void BooBackendMIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity) { for (std::shared_ptr& 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& seq : m_engine.getActiveSequencers()) seq->keyOn(chan, key, velocity); +#if 0 + openlog("LogIt", (LOG_CONS|LOG_PERROR|LOG_PID), LOG_DAEMON); + syslog(LOG_EMERG, "NoteOn %d", key); + closelog(); +#endif } void BooBackendMIDIReader::notePressure(uint8_t /*chan*/, uint8_t /*key*/, uint8_t /*pressure*/) @@ -308,7 +334,7 @@ std::vector> BooBackendVoiceAllocator::enume std::unique_ptr BooBackendVoiceAllocator::allocateMIDIReader(Engine& engine, const char* name) { - std::unique_ptr ret = std::make_unique(engine, name); + std::unique_ptr ret = std::make_unique(engine, name, m_booEngine.useMIDILock()); if (!static_cast(*ret).m_midiIn) return {}; return ret; diff --git a/lib/ContainerRegistry.cpp b/lib/ContainerRegistry.cpp index 20141d6..a0b61f0 100644 --- a/lib/ContainerRegistry.cpp +++ b/lib/ContainerRegistry.cpp @@ -49,17 +49,17 @@ const char* ContainerRegistry::TypeToName(Type tp) case Type::MetroidPrime2: return "Metroid Prime 2 (GCN)"; case Type::RogueSquadronPC: - return "Star Wars: Rogue Squadron (PC)"; + return "Star Wars - Rogue Squadron (PC)"; case Type::RogueSquadronN64: - return "Star Wars: Rogue Squadron (N64)"; + return "Star Wars - Rogue Squadron (N64)"; case Type::BattleForNabooPC: - return "Star Wars Episode I: Battle for Naboo (PC)"; + return "Star Wars Episode I - Battle for Naboo (PC)"; case Type::BattleForNabooN64: - return "Star Wars Episode I: Battle for Naboo (N64)"; + return "Star Wars Episode I - Battle for Naboo (N64)"; case Type::RogueSquadron2: - return "Star Wars: Rogue Squadron 2 (GCN)"; + return "Star Wars - Rogue Squadron 2 (GCN)"; case Type::RogueSquadron3: - return "Star Wars: Rogue Squadron 3 (GCN)"; + return "Star Wars - Rogue Squadron 3 (GCN)"; } } @@ -119,7 +119,8 @@ static bool IsSongExtension(const char* path, const char*& dotOut) static bool ValidateMP1(FILE* fp) { - FileLength(fp); + if (FileLength(fp) > 40 * 1024 * 1024) + return false; uint32_t magic; fread(&magic, 1, 4, fp); @@ -228,29 +229,34 @@ static std::vector> LoadMP1(FILE ReadString(fp); std::string name = ReadString(fp); - uint32_t len; - fread(&len, 1, 4, fp); - len = SBig(len); - std::unique_ptr pool(new uint8_t[len]); - fread(pool.get(), 1, len, fp); + uint32_t poolLen; + fread(&poolLen, 1, 4, fp); + poolLen = SBig(poolLen); + std::unique_ptr pool(new uint8_t[poolLen]); + fread(pool.get(), 1, poolLen, fp); - fread(&len, 1, 4, fp); - len = SBig(len); - std::unique_ptr proj(new uint8_t[len]); - fread(proj.get(), 1, len, fp); + uint32_t projLen; + fread(&projLen, 1, 4, fp); + projLen = SBig(projLen); + std::unique_ptr proj(new uint8_t[projLen]); + fread(proj.get(), 1, projLen, fp); - fread(&len, 1, 4, fp); - len = SBig(len); - std::unique_ptr samp(new uint8_t[len]); - fread(samp.get(), 1, len, fp); + uint32_t sampLen; + fread(&sampLen, 1, 4, fp); + sampLen = SBig(sampLen); + std::unique_ptr samp(new uint8_t[sampLen]); + fread(samp.get(), 1, sampLen, fp); - fread(&len, 1, 4, fp); - len = SBig(len); - std::unique_ptr sdir(new uint8_t[len]); - fread(sdir.get(), 1, len, fp); + uint32_t sdirLen; + fread(&sdirLen, 1, 4, fp); + sdirLen = SBig(sdirLen); + std::unique_ptr sdir(new uint8_t[sdirLen]); + fread(sdir.get(), 1, sdirLen, fp); - ret.emplace_back(std::move(name), IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), GCNDataTag{}}); + ret.emplace_back(std::move(name), IntrusiveAudioGroupData{proj.release(), projLen, + pool.release(), poolLen, + sdir.release(), sdirLen, + samp.release(), sampLen, GCNDataTag{}}); } } FSeek(fp, origPos, SEEK_SET); @@ -263,7 +269,8 @@ static std::vector> LoadMP1(FILE static bool ValidateMP1Songs(FILE* fp) { - FileLength(fp); + if (FileLength(fp) > 40 * 1024 * 1024) + return false; uint32_t magic; fread(&magic, 1, 4, fp); @@ -404,7 +411,8 @@ static std::vector> LoadMP1S static bool ValidateMP2(FILE* fp) { - FileLength(fp); + if (FileLength(fp) > 40 * 1024 * 1024) + return false; uint32_t magic; fread(&magic, 1, 4, fp); @@ -528,21 +536,26 @@ static std::vector> LoadMP2(FILE uint32_t sampSz; fread(&sampSz, 1, 4, fp); sampSz = SBig(sampSz); + + if (projSz && poolSz && sdirSz && sampSz) + { + std::unique_ptr pool(new uint8_t[poolSz]); + fread(pool.get(), 1, poolSz, fp); - std::unique_ptr pool(new uint8_t[poolSz]); - fread(pool.get(), 1, poolSz, fp); + std::unique_ptr proj(new uint8_t[projSz]); + fread(proj.get(), 1, projSz, fp); - std::unique_ptr proj(new uint8_t[projSz]); - fread(proj.get(), 1, projSz, fp); + std::unique_ptr sdir(new uint8_t[sdirSz]); + fread(sdir.get(), 1, sdirSz, fp); - std::unique_ptr sdir(new uint8_t[sdirSz]); - fread(sdir.get(), 1, sdirSz, fp); + std::unique_ptr samp(new uint8_t[sampSz]); + fread(samp.get(), 1, sampSz, fp); - std::unique_ptr samp(new uint8_t[sampSz]); - fread(samp.get(), 1, sampSz, fp); - - ret.emplace_back(std::move(name), IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), GCNDataTag{}}); + ret.emplace_back(std::move(name), IntrusiveAudioGroupData{proj.release(), projSz, + pool.release(), poolSz, + sdir.release(), sdirSz, + samp.release(), sampSz, GCNDataTag{}}); + } } } FSeek(fp, origPos, SEEK_SET); @@ -589,6 +602,8 @@ static void SwapN64Rom32(void* data, size_t size) static bool ValidateRS1PC(FILE* fp) { size_t endPos = FileLength(fp); + if (endPos > 100 * 1024 * 1024) + return false; uint32_t fstOff; uint32_t fstSz; @@ -640,41 +655,49 @@ static std::vector> LoadRS1PC(FI fread(entries.get(), fstSz, 1, fp); std::unique_ptr proj; + size_t projSz = 0; std::unique_ptr pool; + size_t poolSz = 0; std::unique_ptr sdir; + size_t sdirSz = 0; std::unique_ptr samp; + size_t sampSz = 0; for (uint32_t i=0 ; i> LoadRS1N64(F const RS1FSTEntry* lastEnt = reinterpret_cast(dataSeg + fstEnd); std::unique_ptr proj; + size_t projSz = 0; std::unique_ptr pool; + size_t poolSz = 0; std::unique_ptr sdir; + size_t sdirSz = 0; std::unique_ptr samp; + size_t sampSz = 0; for (; entry != lastEnt ; ++entry) { @@ -778,7 +805,7 @@ static std::vector> LoadRS1N64(F if (ent.compSz == 0xffffffff) { proj.reset(new uint8_t[ent.decompSz]); - memcpy(proj.get(), dataSeg + ent.offset, ent.decompSz); + memmove(proj.get(), dataSeg + ent.offset, ent.decompSz); } else { @@ -786,13 +813,14 @@ static std::vector> LoadRS1N64(F uLongf outSz = ent.decompSz; uncompress(proj.get(), &outSz, dataSeg + ent.offset, ent.compSz); } + projSz = ent.decompSz; } else if (!strncmp("pool_SND", ent.name, 16)) { if (ent.compSz == 0xffffffff) { pool.reset(new uint8_t[ent.decompSz]); - memcpy(pool.get(), dataSeg + ent.offset, ent.decompSz); + memmove(pool.get(), dataSeg + ent.offset, ent.decompSz); } else { @@ -800,13 +828,14 @@ static std::vector> LoadRS1N64(F uLongf outSz = ent.decompSz; uncompress(pool.get(), &outSz, dataSeg + ent.offset, ent.compSz); } + poolSz = ent.decompSz; } else if (!strncmp("sdir_SND", ent.name, 16)) { if (ent.compSz == 0xffffffff) { sdir.reset(new uint8_t[ent.decompSz]); - memcpy(sdir.get(), dataSeg + ent.offset, ent.decompSz); + memmove(sdir.get(), dataSeg + ent.offset, ent.decompSz); } else { @@ -814,13 +843,14 @@ static std::vector> LoadRS1N64(F uLongf outSz = ent.decompSz; uncompress(sdir.get(), &outSz, dataSeg + ent.offset, ent.compSz); } + sdirSz = ent.decompSz; } else if (!strncmp("samp_SND", ent.name, 16)) { if (ent.compSz == 0xffffffff) { samp.reset(new uint8_t[ent.decompSz]); - memcpy(samp.get(), dataSeg + ent.offset, ent.decompSz); + memmove(samp.get(), dataSeg + ent.offset, ent.decompSz); } else { @@ -828,11 +858,12 @@ static std::vector> LoadRS1N64(F uLongf outSz = ent.decompSz; uncompress(samp.get(), &outSz, dataSeg + ent.offset, ent.compSz); } + sampSz = ent.decompSz; } } - ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), + ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projSz, pool.release(), poolSz, + sdir.release(), sdirSz, samp.release(), sampSz, false, N64DataTag{}}); } @@ -842,7 +873,9 @@ static std::vector> LoadRS1N64(F static bool ValidateBFNPC(FILE* fp) { size_t endPos = FileLength(fp); - + if (endPos > 100 * 1024 * 1024) + return false; + uint32_t fstOff; uint32_t fstSz; if (fread(&fstOff, 1, 4, fp) == 4 && fread(&fstSz, 1, 4, fp) == 4) @@ -893,9 +926,13 @@ static std::vector> LoadBFNPC(FI fread(entries.get(), fstSz, 1, fp); std::unique_ptr proj; + size_t projSz = 0; std::unique_ptr pool; + size_t poolSz = 0; std::unique_ptr sdir; + size_t sdirSz = 0; std::unique_ptr samp; + size_t sampSz = 0; for (uint32_t i=0 ; i> LoadBFNPC(FI proj.reset(new uint8_t[entry.decompSz]); FSeek(fp, entry.offset, SEEK_SET); fread(proj.get(), 1, entry.decompSz, fp); + projSz = entry.decompSz; } else if (!strncmp("pool", entry.name, 16)) { pool.reset(new uint8_t[entry.decompSz]); FSeek(fp, entry.offset, SEEK_SET); fread(pool.get(), 1, entry.decompSz, fp); + poolSz = entry.decompSz; } else if (!strncmp("sdir", entry.name, 16)) { sdir.reset(new uint8_t[entry.decompSz]); FSeek(fp, entry.offset, SEEK_SET); fread(sdir.get(), 1, entry.decompSz, fp); + sdirSz = entry.decompSz; } else if (!strncmp("samp", entry.name, 16)) { samp.reset(new uint8_t[entry.decompSz]); FSeek(fp, entry.offset, SEEK_SET); fread(samp.get(), 1, entry.decompSz, fp); + sampSz = entry.decompSz; } } - ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), + ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projSz, pool.release(), poolSz, + sdir.release(), sdirSz, samp.release(), sampSz, true, PCDataTag{}}); } } @@ -1017,9 +1058,13 @@ static std::vector> LoadBFNN64(F const RS1FSTEntry* lastEnt = reinterpret_cast(dataSeg + fstEnd); std::unique_ptr proj; + size_t projSz = 0; std::unique_ptr pool; + size_t poolSz = 0; std::unique_ptr sdir; + size_t sdirSz = 0; std::unique_ptr samp; + size_t sampSz = 0; for (; entry != lastEnt ; ++entry) { @@ -1031,7 +1076,7 @@ static std::vector> LoadBFNN64(F if (ent.compSz == 0xffffffff) { proj.reset(new uint8_t[ent.decompSz]); - memcpy(proj.get(), dataSeg + ent.offset, ent.decompSz); + memmove(proj.get(), dataSeg + ent.offset, ent.decompSz); } else { @@ -1039,13 +1084,14 @@ static std::vector> LoadBFNN64(F uLongf outSz = ent.decompSz; uncompress(proj.get(), &outSz, dataSeg + ent.offset, ent.compSz); } + projSz = ent.decompSz; } else if (!strncmp("pool", ent.name, 16)) { if (ent.compSz == 0xffffffff) { pool.reset(new uint8_t[ent.decompSz]); - memcpy(pool.get(), dataSeg + ent.offset, ent.decompSz); + memmove(pool.get(), dataSeg + ent.offset, ent.decompSz); } else { @@ -1053,13 +1099,14 @@ static std::vector> LoadBFNN64(F uLongf outSz = ent.decompSz; uncompress(pool.get(), &outSz, dataSeg + ent.offset, ent.compSz); } + poolSz = ent.decompSz; } else if (!strncmp("sdir", ent.name, 16)) { if (ent.compSz == 0xffffffff) { sdir.reset(new uint8_t[ent.decompSz]); - memcpy(sdir.get(), dataSeg + ent.offset, ent.decompSz); + memmove(sdir.get(), dataSeg + ent.offset, ent.decompSz); } else { @@ -1067,13 +1114,14 @@ static std::vector> LoadBFNN64(F uLongf outSz = ent.decompSz; uncompress(sdir.get(), &outSz, dataSeg + ent.offset, ent.compSz); } + sdirSz = ent.decompSz; } else if (!strncmp("samp", ent.name, 16)) { if (ent.compSz == 0xffffffff) { samp.reset(new uint8_t[ent.decompSz]); - memcpy(samp.get(), dataSeg + ent.offset, ent.decompSz); + memmove(samp.get(), dataSeg + ent.offset, ent.decompSz); } else { @@ -1081,11 +1129,12 @@ static std::vector> LoadBFNN64(F uLongf outSz = ent.decompSz; uncompress(samp.get(), &outSz, dataSeg + ent.offset, ent.compSz); } + sampSz = ent.decompSz; } } - ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), + ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projSz, pool.release(), poolSz, + sdir.release(), sdirSz, samp.release(), sampSz, true, N64DataTag{}}); } @@ -1164,7 +1213,9 @@ struct RS23SONHead static bool ValidateRS2(FILE* fp) { size_t endPos = FileLength(fp); - + if (endPos > 600 * 1024 * 1024) + return false; + uint64_t fstOff; fread(&fstOff, 1, 8, fp); fstOff = SBig(fstOff); @@ -1228,21 +1279,24 @@ static std::vector> LoadRS2(FILE head.swapBig(); std::unique_ptr pool(new uint8_t[head.poolLen]); - memcpy(pool.get(), audData.get() + head.poolOff, head.poolLen); + memmove(pool.get(), audData.get() + head.poolOff, head.poolLen); std::unique_ptr proj(new uint8_t[head.projLen]); - memcpy(proj.get(), audData.get() + head.projOff, head.projLen); + memmove(proj.get(), audData.get() + head.projOff, head.projLen); std::unique_ptr sdir(new uint8_t[head.sdirLen]); - memcpy(sdir.get(), audData.get() + head.sdirOff, head.sdirLen); + memmove(sdir.get(), audData.get() + head.sdirOff, head.sdirLen); std::unique_ptr samp(new uint8_t[head.sampLen]); - memcpy(samp.get(), audData.get() + head.sampOff, head.sampLen); + memmove(samp.get(), audData.get() + head.sampOff, head.sampLen); - char name[128]; - snprintf(name, 128, "GroupFile%u", j); - ret.emplace_back(name, IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), GCNDataTag{}}); + if (head.projLen && head.poolLen && head.sdirLen && head.sampLen) + { + char name[128]; + snprintf(name, 128, "GroupFile%02u", j); + ret.emplace_back(name, IntrusiveAudioGroupData{proj.release(), head.projLen, pool.release(), head.poolLen, + sdir.release(), head.sdirLen, samp.release(), head.sampLen, GCNDataTag{}}); + } } break; @@ -1299,9 +1353,9 @@ static std::vector> LoadRS2S sonHead.swapBig(); char name[128]; - snprintf(name, 128, "GroupFile%u-%u", j, s); + snprintf(name, 128, "GroupFile%02u-%u", j, s); std::unique_ptr song(new uint8_t[sonHead.length]); - memcpy(song.get(), audData.get() + sonHead.offset, sonHead.length); + memmove(song.get(), audData.get() + sonHead.offset, sonHead.length); ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), sonHead.length, sonHead.groupId, sonHead.setupId)); } @@ -1334,7 +1388,9 @@ struct RS3FSTEntry static bool ValidateRS3(FILE* fp) { size_t endPos = FileLength(fp); - + if (endPos > 600 * 1024 * 1024) + return false; + uint64_t fstOff; fread(&fstOff, 1, 8, fp); fstOff = SBig(fstOff); @@ -1396,21 +1452,24 @@ static std::vector> LoadRS3(FILE head.swapBig(); std::unique_ptr pool(new uint8_t[head.poolLen]); - memcpy(pool.get(), audData.get() + head.poolOff, head.poolLen); + memmove(pool.get(), audData.get() + head.poolOff, head.poolLen); std::unique_ptr proj(new uint8_t[head.projLen]); - memcpy(proj.get(), audData.get() + head.projOff, head.projLen); + memmove(proj.get(), audData.get() + head.projOff, head.projLen); std::unique_ptr sdir(new uint8_t[head.sdirLen]); - memcpy(sdir.get(), audData.get() + head.sdirOff, head.sdirLen); + memmove(sdir.get(), audData.get() + head.sdirOff, head.sdirLen); std::unique_ptr samp(new uint8_t[head.sampLen]); - memcpy(samp.get(), audData.get() + head.sampOff, head.sampLen); + memmove(samp.get(), audData.get() + head.sampOff, head.sampLen); - char name[128]; - snprintf(name, 128, "GroupFile%u", j); - ret.emplace_back(name, IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), GCNDataTag{}}); + if (head.projLen && head.poolLen && head.sdirLen && head.sampLen) + { + char name[128]; + snprintf(name, 128, "GroupFile%02u", j); + ret.emplace_back(name, IntrusiveAudioGroupData{proj.release(), head.projLen, pool.release(), head.poolLen, + sdir.release(), head.sdirLen, samp.release(), head.sampLen, GCNDataTag{}}); + } } break; @@ -1538,11 +1597,19 @@ ContainerRegistry::Type ContainerRegistry::DetectContainerType(const char* path) return Type::Invalid; } - + std::vector> ContainerRegistry::LoadContainer(const char* path) +{ + Type typeOut; + return LoadContainer(path, typeOut); +}; + +std::vector> +ContainerRegistry::LoadContainer(const char* path, Type& typeOut) { FILE* fp; + typeOut = Type::Invalid; /* See if provided file is one of four raw chunks */ const char* dot = nullptr; @@ -1603,49 +1670,50 @@ ContainerRegistry::LoadContainer(const char* path) fclose(fp); fp = fopen(projPath, "rb"); - size_t fLen = FileLength(fp); - if (!fLen) + size_t projLen = FileLength(fp); + if (!projLen) return ret; - std::unique_ptr proj(new uint8_t[fLen]); - fread(proj.get(), 1, fLen, fp); + std::unique_ptr proj(new uint8_t[projLen]); + fread(proj.get(), 1, projLen, fp); fp = fopen(poolPath, "rb"); - fLen = FileLength(fp); - if (!fLen) + size_t poolLen = FileLength(fp); + if (!poolLen) return ret; - std::unique_ptr pool(new uint8_t[fLen]); - fread(pool.get(), 1, fLen, fp); + std::unique_ptr pool(new uint8_t[poolLen]); + fread(pool.get(), 1, poolLen, fp); fp = fopen(sdirPath, "rb"); - fLen = FileLength(fp); - if (!fLen) + size_t sdirLen = FileLength(fp); + if (!sdirLen) return ret; - std::unique_ptr sdir(new uint8_t[fLen]); - fread(sdir.get(), 1, fLen, fp); + std::unique_ptr sdir(new uint8_t[sdirLen]); + fread(sdir.get(), 1, sdirLen, fp); fp = fopen(sampPath, "rb"); - fLen = FileLength(fp); - if (!fLen) + size_t sampLen = FileLength(fp); + if (!sampPath) return ret; - std::unique_ptr samp(new uint8_t[fLen]); - fread(samp.get(), 1, fLen, fp); + std::unique_ptr samp(new uint8_t[sampLen]); + fread(samp.get(), 1, sampLen, fp); fclose(fp); /* SDIR-based format detection */ if (*reinterpret_cast(sdir.get() + 8) == 0x0) - ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), + ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projLen, pool.release(), poolLen, + sdir.release(), sdirLen, samp.release(), sampLen, GCNDataTag{}}); else if (sdir[9] == 0x0) - ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), + ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projLen, pool.release(), poolLen, + sdir.release(), sdirLen, samp.release(), sampLen, false, N64DataTag{}}); else - ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), pool.release(), - sdir.release(), samp.release(), + ret.emplace_back("Group", IntrusiveAudioGroupData{proj.release(), projLen, pool.release(), poolLen, + sdir.release(), sdirLen, samp.release(), sampLen, false, PCDataTag{}}); + typeOut = Type::Raw4; return ret; } @@ -1657,6 +1725,7 @@ ContainerRegistry::LoadContainer(const char* path) { auto ret = LoadMP1(fp); fclose(fp); + typeOut = Type::MetroidPrime; return ret; } @@ -1664,6 +1733,7 @@ ContainerRegistry::LoadContainer(const char* path) { auto ret = LoadMP2(fp); fclose(fp); + typeOut = Type::MetroidPrime2; return ret; } @@ -1671,6 +1741,7 @@ ContainerRegistry::LoadContainer(const char* path) { auto ret = LoadRS1PC(fp); fclose(fp); + typeOut = Type::RogueSquadronPC; return ret; } @@ -1678,6 +1749,7 @@ ContainerRegistry::LoadContainer(const char* path) { auto ret = LoadRS1N64(fp); fclose(fp); + typeOut = Type::RogueSquadronN64; return ret; } @@ -1685,6 +1757,7 @@ ContainerRegistry::LoadContainer(const char* path) { auto ret = LoadBFNPC(fp); fclose(fp); + typeOut = Type::BattleForNabooPC; return ret; } @@ -1692,6 +1765,7 @@ ContainerRegistry::LoadContainer(const char* path) { auto ret = LoadBFNN64(fp); fclose(fp); + typeOut = Type::BattleForNabooN64; return ret; } @@ -1699,6 +1773,7 @@ ContainerRegistry::LoadContainer(const char* path) { auto ret = LoadRS2(fp); fclose(fp); + typeOut = Type::RogueSquadron2; return ret; } @@ -1706,6 +1781,7 @@ ContainerRegistry::LoadContainer(const char* path) { auto ret = LoadRS3(fp); fclose(fp); + typeOut = Type::RogueSquadron3; return ret; } diff --git a/lib/N64MusyXCodec.c b/lib/N64MusyXCodec.c index 6df84f4..99fd562 100644 --- a/lib/N64MusyXCodec.c +++ b/lib/N64MusyXCodec.c @@ -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; } diff --git a/lib/Sequencer.cpp b/lib/Sequencer.cpp index 201cd8f..c184d9c 100644 --- a/lib/Sequencer.cpp +++ b/lib/Sequencer.cpp @@ -71,7 +71,7 @@ Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId, m_midiSetup = it->second->data(); m_submix = m_engine.addSubmix(smx); - m_submix->makeReverbHi(0.2f, 1.f, 1.f, 0.5f, 0.f, 0.f); + m_submix->makeReverbHi(0.2f, 0.65f, 1.f, 0.5f, 0.f, 0.f); } Sequencer::ChannelState::~ChannelState() @@ -79,27 +79,53 @@ Sequencer::ChannelState::~ChannelState() } Sequencer::ChannelState::ChannelState(Sequencer& parent, uint8_t chanId) -: m_parent(parent), m_chanId(chanId), m_setup(m_parent.m_midiSetup[chanId]) +: m_parent(parent), m_chanId(chanId) { - if (chanId == 9) + if (m_parent.m_midiSetup) { - auto it = m_parent.m_songGroup.m_drumPages.find(m_setup.programNo); - if (it != m_parent.m_songGroup.m_drumPages.cend()) - m_page = it->second; + m_setup = &m_parent.m_midiSetup[chanId]; + + if (chanId == 9) + { + auto it = m_parent.m_songGroup.m_drumPages.find(m_setup->programNo); + if (it != m_parent.m_songGroup.m_drumPages.cend()) + m_page = it->second; + } + else + { + auto it = m_parent.m_songGroup.m_normPages.find(m_setup->programNo); + if (it != m_parent.m_songGroup.m_normPages.cend()) + m_page = it->second; + } + + m_curVol = m_setup->volume / 127.f; + m_curPan = m_setup->panning / 64.f - 1.f; + m_ctrlVals[0x5b] = m_setup->reverb; + m_ctrlVals[0x5d] = m_setup->chorus; } else { - auto it = m_parent.m_songGroup.m_normPages.find(m_setup.programNo); - if (it != m_parent.m_songGroup.m_normPages.cend()) - m_page = it->second; + if (chanId == 9) + { + auto it = m_parent.m_songGroup.m_drumPages.find(0); + if (it != m_parent.m_songGroup.m_drumPages.cend()) + m_page = it->second; + } + else + { + auto it = m_parent.m_songGroup.m_normPages.find(0); + if (it != m_parent.m_songGroup.m_normPages.cend()) + m_page = it->second; + } + + m_curVol = 1.f; + m_curPan = 0.f; + m_ctrlVals[0x5b] = 0; + m_ctrlVals[0x5d] = 0; } - m_curVol = m_setup.volume / 127.f; - m_curPan = m_setup.panning / 64.f - 1.f; m_ctrlVals[7] = 127; m_ctrlVals[10] = 64; - m_ctrlVals[0x5b] = m_setup.reverb; - m_ctrlVals[0x5d] = m_setup.chorus; } void Sequencer::advance(double dt) diff --git a/lib/Voice.cpp b/lib/Voice.cpp index 0c75b69..583f45d 100644 --- a/lib/Voice.cpp +++ b/lib/Voice.cpp @@ -503,7 +503,7 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data) { const int16_t* pcm = reinterpret_cast(m_curSampleData); remCount = std::min(samplesRem, m_lastSamplePos - m_curSamplePos); - memcpy(data, pcm + m_curSamplePos, remCount * sizeof(int16_t)); + memmove(data, pcm + m_curSamplePos, remCount * sizeof(int16_t)); decSamples = remCount; break; } @@ -568,7 +568,7 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data) { const int16_t* pcm = reinterpret_cast(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; }