diff --git a/AudioUnit/AmuseContainerMainMenu.xib b/AudioUnit/AmuseContainerMainMenu.xib index 64c767c..14bd957 100644 --- a/AudioUnit/AmuseContainerMainMenu.xib +++ b/AudioUnit/AmuseContainerMainMenu.xib @@ -167,7 +167,8 @@ CA - + + @@ -178,9 +179,9 @@ CA - + - + @@ -216,7 +217,7 @@ CA - + @@ -253,11 +254,8 @@ CA - - - - - + + @@ -269,23 +267,22 @@ CA - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -294,74 +291,39 @@ CA - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - + + - - + + - + - + - + - + @@ -369,39 +331,39 @@ CA - - - + + + - + - - + + - + - + - + - + - + @@ -409,11 +371,11 @@ CA - - + + - + @@ -421,7 +383,7 @@ CA - + @@ -438,24 +400,10 @@ CA - - - - - - - - - - - - - - - - - - + + + + @@ -464,74 +412,39 @@ CA - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - + + - - + + - + - + - + - + @@ -539,39 +452,39 @@ CA - - - + + + - + - - + + - + - + - + - + - + @@ -579,11 +492,11 @@ CA - - + + - + @@ -591,7 +504,7 @@ CA - + @@ -608,24 +521,10 @@ CA - - - - - - - - - - - - - - - - - - + + + + diff --git a/AudioUnit/AmuseContainingApp.hpp b/AudioUnit/AmuseContainingApp.hpp index 52f345f..f3eb80a 100644 --- a/AudioUnit/AmuseContainingApp.hpp +++ b/AudioUnit/AmuseContainingApp.hpp @@ -3,10 +3,12 @@ #import #import "AudioGroupFilePresenter.hpp" +#include +#include @interface DataOutlineView : NSOutlineView { - @public +@public IBOutlet NSButton* removeDataButton; IBOutlet NSMenuItem* deleteMenuItem; } @@ -14,14 +16,16 @@ @interface SamplesTableController : NSObject { - + AudioGroupFilePresenter* presenter; } +- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present; @end @interface SFXTableController : NSObject { - + AudioGroupFilePresenter* presenter; } +- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present; @end @interface AppDelegate : NSObject @@ -40,9 +44,18 @@ 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 76236c2..c281347 100644 --- a/AudioUnit/AmuseContainingApp.mm +++ b/AudioUnit/AmuseContainingApp.mm @@ -9,13 +9,27 @@ @class SamplesTableController; @class SFXTableController; -/* Blocks mousedown events */ +/* 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 +{ + NSRect restrictFrame = cellFrame; + restrictFrame.size.width = 22; + if (NSPointInRect([controlView convertPoint:[event locationInWindow] fromView:nil], restrictFrame)) + return NSCellHitTrackableArea; + return NSCellHitNone; +} +@end + @interface MainView : NSView { AudioUnitViewController* amuseVC; @@ -89,12 +103,49 @@ - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView { - + return presenter->m_sampleTableData.size(); } -- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn*)tableColumn row:(NSInteger)row +- (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 @@ -104,11 +155,60 @@ - (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView { + return presenter->m_sfxTableData.size(); } -- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn*)tableColumn row:(NSInteger)row +- (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 = NSApp.delegate; + [delegate startSFX:sfxToken->m_loadId]; +} + +- (id)initWithAudioGroupPresenter:(AudioGroupFilePresenter*)present +{ + self = [super init]; + presenter = present; + return self; } @end @@ -117,7 +217,10 @@ - (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 new]; @@ -126,15 +229,22 @@ dataOutline.delegate = groupFilePresenter; [dataOutline reloadItem:nil reloadChildren:YES]; - samplesController = [SamplesTableController new]; + samplesController = [[SamplesTableController alloc] initWithAudioGroupPresenter:groupFilePresenter]; samplesTable.dataSource = samplesController; samplesTable.delegate = samplesController; [samplesTable reloadData]; - sfxController = [SFXTableController new]; + 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 @@ -174,6 +284,23 @@ }]; } +- (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]]; diff --git a/AudioUnit/AudioGroupFilePresenter.hpp b/AudioUnit/AudioGroupFilePresenter.hpp index f5e249c..a34537a 100644 --- a/AudioUnit/AudioGroupFilePresenter.hpp +++ b/AudioUnit/AudioGroupFilePresenter.hpp @@ -12,6 +12,8 @@ @class AudioGroupFilePresenter; @class AudioGroupDataToken; @class AudioGroupCollectionToken; +@class AudioGroupSFXToken; +@class AudioGroupSampleToken; struct AudioGroupDataCollection { @@ -42,7 +44,7 @@ struct AudioGroupDataCollection std::experimental::optional m_metaData; std::experimental::optional m_loadedData; - std::experimental::optional m_loadedProj; + const amuse::AudioGroup* m_loadedGroup; void moveURL(NSURL* oldUrl, NSURL* newUrl); @@ -72,6 +74,8 @@ struct AudioGroupCollection void addCollection(std::vector>&& collection); void update(AudioGroupFilePresenter* presenter); bool doSearch(const std::string& str); + void addSFX(std::vector& vecOut); + void addSamples(std::vector& vecOut); }; @interface AudioGroupDataToken : NSObject @@ -90,6 +94,26 @@ struct AudioGroupCollection - (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 AudioGroupFilePresenter : NSObject { NSURL* m_groupURL; @@ -98,6 +122,10 @@ struct AudioGroupCollection std::vector>::iterator> m_filterAudioGroupCollections; NSOutlineView* lastOutlineView; NSString* searchStr; + +@public + std::vector m_sfxTableData; + std::vector m_sampleTableData; } - (BOOL)addCollectionName:(std::string&&)name items:(std::vector>&&)collection; - (void)update; diff --git a/AudioUnit/AudioGroupFilePresenter.mm b/AudioUnit/AudioGroupFilePresenter.mm index 61cdf2a..b42101d 100644 --- a/AudioUnit/AudioGroupFilePresenter.mm +++ b/AudioUnit/AudioGroupFilePresenter.mm @@ -28,6 +28,28 @@ static std::string StrToLower(const std::string& str) } @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 AudioGroupFilePresenter - (NSURL*)presentedItemURL @@ -130,6 +152,56 @@ bool AudioGroupCollection::doSearch(const std::string& str) return ret; } +void AudioGroupCollection::addSFX(std::vector& vecOut) +{ + for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it) + { + 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]); + } + } + } +} + +void AudioGroupCollection::addSamples(std::vector& vecOut) +{ + for (auto it = m_groups.begin() ; it != m_groups.end() ; ++it) + { + 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]); + } + } +} + 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), @@ -137,7 +209,9 @@ AudioGroupDataCollection::AudioGroupDataCollection(const std::string& name, NSUR bool AudioGroupDataCollection::_attemptLoad(AudioGroupFilePresenter* presenter) { - if (m_metaData && m_loadedData && m_loadedProj) + amuse::Engine& engine = *((AppDelegate*)NSApp.delegate)->amuseEngine; + + if (m_metaData && m_loadedData && m_loadedGroup) return true; if (!loadProj(presenter)) return false; @@ -176,12 +250,8 @@ bool AudioGroupDataCollection::_attemptLoad(AudioGroupFilePresenter* presenter) break; } - if (m_metaData && m_loadedData) - { - m_loadedProj.emplace(amuse::AudioGroupProject::CreateAudioGroupProject(*m_loadedData)); - return true; - } - return false; + m_loadedGroup = engine.addAudioGroup(*m_loadedData); + return m_loadedData && m_loadedGroup; } void AudioGroupDataCollection::enable(AudioGroupFilePresenter* presenter) @@ -437,22 +507,22 @@ bool AudioGroupDataCollection::loadMeta(AudioGroupFilePresenter* presenter) return [NSNumber numberWithInt:data.m_metaData->active ? NSOnState : NSOffState]; else if ([tableColumn.identifier isEqualToString:@"DetailsColumn"]) { - if (!data.m_loadedProj) + if (!data.m_loadedGroup) return @""; - if (data.m_loadedProj->songGroups().size() && data.m_loadedProj->sfxGroups().size()) + 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_loadedProj->songGroups().size(), - data.m_loadedProj->songGroups().size() > 1 ? "s" : "", - data.m_loadedProj->sfxGroups().size(), - data.m_loadedProj->sfxGroups().size() > 1 ? "s" : ""]; - else if (data.m_loadedProj->songGroups().size()) + 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_loadedProj->songGroups().size(), - data.m_loadedProj->songGroups().size() > 1 ? "s" : ""]; - else if (data.m_loadedProj->sfxGroups().size()) + 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_loadedProj->sfxGroups().size(), - data.m_loadedProj->sfxGroups().size() > 1 ? "s" : ""]; + data.m_loadedGroup->getProj().sfxGroups().size(), + data.m_loadedGroup->getProj().sfxGroups().size() > 1 ? "s" : ""]; else return @""; } @@ -495,7 +565,7 @@ bool AudioGroupDataCollection::loadMeta(AudioGroupFilePresenter* presenter) } if (dirty) - [outlineView reloadItem:nil reloadChildren:YES]; + [self resetIterators]; } - (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(nonnull id)cell forTableColumn:(nullable NSTableColumn *)tableColumn item:(nonnull id)item @@ -649,12 +719,19 @@ bool AudioGroupDataCollection::loadMeta(AudioGroupFilePresenter* presenter) if (searchStr) search = 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) || !searchStr || StrToLower(it->first).find(search) != std::string::npos) m_filterAudioGroupCollections.push_back(it); + } [lastOutlineView reloadItem:nil reloadChildren:YES]; + [(AppDelegate*)NSApp.delegate reloadTables]; } - (void)setSearchFilter:(NSString*)str 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/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/lib/ContainerRegistry.cpp b/lib/ContainerRegistry.cpp index 1fbfe29..74b0dfc 100644 --- a/lib/ContainerRegistry.cpp +++ b/lib/ContainerRegistry.cpp @@ -1293,7 +1293,7 @@ static std::vector> LoadRS2(FILE if (head.projLen && head.poolLen && head.sdirLen && head.sampLen) { char name[128]; - snprintf(name, 128, "GroupFile%u", j); + 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{}}); } @@ -1353,7 +1353,7 @@ 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); ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), sonHead.length, @@ -1466,7 +1466,7 @@ static std::vector> LoadRS3(FILE if (head.projLen && head.poolLen && head.sdirLen && head.sampLen) { char name[128]; - snprintf(name, 128, "GroupFile%u", j); + 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{}}); }