This was just an experiment, and was hashed in to test it out. It does not look like it would be a very efficient thing to ship this in the main app at all, especially since the collection of presets is so dang huge. It was also never meant to replace the existing visualizations, but instead as another option. One of the things from this branch will make it into the final: Optional nullability of the Visualization Manager parameters, since technically they're both checked on call. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
174 lines
4.4 KiB
Objective-C
174 lines
4.4 KiB
Objective-C
//
|
|
// SpectrumViewPM.m
|
|
// Cog
|
|
//
|
|
// Created by Christopher Snowhill on 3/11/25.
|
|
//
|
|
|
|
#import "ProjectMView.h"
|
|
|
|
#import "NSView+Visibility.h"
|
|
|
|
#include <OpenGL/gl3.h>
|
|
|
|
#import <projectM-4/projectM.h>
|
|
|
|
#import <projectM-4/playlist.h>
|
|
|
|
#import "Logging.h"
|
|
|
|
static void *kProjectMViewContext = &kProjectMViewContext;
|
|
|
|
extern NSString *CogPlaybackDidBeginNotificiation;
|
|
extern NSString *CogPlaybackDidPauseNotificiation;
|
|
extern NSString *CogPlaybackDidResumeNotificiation;
|
|
extern NSString *CogPlaybackDidStopNotificiation;
|
|
|
|
@implementation ProjectMView {
|
|
VisualizationController *visController;
|
|
projectm_handle pm;
|
|
projectm_playlist_handle pl;
|
|
|
|
float visAudio[4096];
|
|
|
|
NSTimer *timer;
|
|
}
|
|
|
|
- (id)initWithFrame:(NSRect)frame {
|
|
NSOpenGLPixelFormatAttribute attr[] = {
|
|
NSOpenGLPFAAllowOfflineRenderers,
|
|
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
|
kCGLPFASupportsAutomaticGraphicsSwitching,
|
|
NSOpenGLPFAColorSize, 24,
|
|
NSOpenGLPFAAlphaSize, 8,
|
|
NSOpenGLPFAClosestPolicy,
|
|
NSOpenGLPFADoubleBuffer,
|
|
NSOpenGLPFAAccelerated,
|
|
0
|
|
};
|
|
|
|
NSOpenGLPixelFormat *fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
|
|
if(!fmt) {
|
|
return nil;
|
|
}
|
|
|
|
self = [super initWithFrame:frame pixelFormat:fmt];
|
|
if(self) {
|
|
self.wantsBestResolutionOpenGLSurface = YES;
|
|
|
|
visController = [NSClassFromString(@"VisualizationController") sharedController];
|
|
|
|
timer = [NSTimer timerWithTimeInterval:(1.0 / 60.0) repeats:YES block:^(NSTimer * _Nonnull timer) {
|
|
self.needsDisplay = YES;
|
|
}];
|
|
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[timer invalidate];
|
|
timer = nil;
|
|
|
|
if(pl) {
|
|
projectm_playlist_destroy(pl);
|
|
}
|
|
if(pm) {
|
|
projectm_destroy(pm);
|
|
}
|
|
}
|
|
|
|
static void presetFailed(const char *preset_filename, const char *message, void *user_data) {
|
|
NSLog(@"ProjectM preset failed: %s - for reason: %s", preset_filename, message);
|
|
}
|
|
|
|
- (void)initProjectM {
|
|
if(!pm && !pl) {
|
|
NSBundle* me = [NSBundle bundleForClass: self.class];
|
|
NSLog(@"main bundle: %@", [me bundlePath]);
|
|
NSString* presetsPath = [me pathForResource:@"presets" ofType:nil];
|
|
NSLog(@"presets path %@", presetsPath);
|
|
|
|
NSString* texturesPath = [me pathForResource:@"textures" ofType:nil];
|
|
NSLog(@"textures path %@", texturesPath);
|
|
|
|
pm = projectm_create();
|
|
if(!pm) {
|
|
return;
|
|
}
|
|
|
|
char *_texturesPath = projectm_alloc_string((int)(strlen([texturesPath UTF8String]) + 1));
|
|
if(!_texturesPath) {
|
|
return;
|
|
}
|
|
strcpy(_texturesPath, [texturesPath UTF8String]);
|
|
const char *texturesPaths[1] = { _texturesPath };
|
|
|
|
projectm_set_texture_search_paths(pm, texturesPaths, 1);
|
|
|
|
projectm_free_string(_texturesPath);
|
|
|
|
NSRect rect = [self.window convertRectToBacking:self.bounds];
|
|
projectm_set_window_size(pm, rect.size.width, rect.size.height);
|
|
|
|
projectm_set_fps(pm, 60);
|
|
projectm_set_mesh_size(pm, 48, 32);
|
|
projectm_set_aspect_correction(pm, true);
|
|
projectm_set_preset_locked(pm, false);
|
|
|
|
projectm_set_preset_duration(pm, 30.0);
|
|
projectm_set_soft_cut_duration(pm, 3.0);
|
|
projectm_set_hard_cut_enabled(pm, false);
|
|
projectm_set_hard_cut_duration(pm, 20.0);
|
|
projectm_set_hard_cut_sensitivity(pm, 1.0);
|
|
projectm_set_beat_sensitivity(pm, 1.0);
|
|
|
|
pl = projectm_playlist_create(pm);
|
|
if(!pl) {
|
|
return;
|
|
}
|
|
|
|
projectm_playlist_set_preset_switch_failed_event_callback(pl, presetFailed, NULL);
|
|
|
|
char *_presetsPath = projectm_alloc_string((int)(strlen([presetsPath UTF8String]) + 1));
|
|
if(!_presetsPath) {
|
|
return;
|
|
}
|
|
strcpy(_presetsPath, [presetsPath UTF8String]);
|
|
|
|
projectm_playlist_add_path(pl, _presetsPath, true, false);
|
|
|
|
projectm_free_string(_presetsPath);
|
|
|
|
projectm_playlist_sort(pl, 0, projectm_playlist_size(pl), SORT_PREDICATE_FILENAME_ONLY, SORT_ORDER_ASCENDING);
|
|
|
|
projectm_playlist_set_shuffle(pl, true);
|
|
}
|
|
}
|
|
|
|
|
|
- (void)drawRect:(NSRect)dirtyRect {
|
|
[[self openGLContext] makeCurrentContext];
|
|
|
|
[self initProjectM];
|
|
|
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
NSRect rect = [self.window convertRectToBacking:self.bounds];
|
|
projectm_set_window_size(pm, rect.size.width, rect.size.height);
|
|
|
|
[self->visController copyVisPCM:&visAudio[0] visFFT:nil latencyOffset:0];
|
|
|
|
size_t maxSamples = projectm_pcm_get_max_samples();
|
|
if(maxSamples > 4096) maxSamples = 4096;
|
|
|
|
projectm_pcm_add_float(pm, &visAudio[0], (unsigned int)maxSamples, 1);
|
|
projectm_opengl_render_frame(pm);
|
|
|
|
[[self openGLContext] flushBuffer];
|
|
[NSOpenGLContext clearCurrentContext];
|
|
}
|
|
|
|
@end
|