-
Notifications
You must be signed in to change notification settings - Fork 41
/
SynthController.m
297 lines (227 loc) · 9.4 KB
/
SynthController.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#import "SynthController.h"
#import <PYMIDI/PYMIDI.h>
#import "InstrumentsDataSource.h"
#import "ChannelsDataSource.h"
@implementation SynthController
- (void)awakeFromNib
{
ChannelsDataSource* channelsDataSource;
InstrumentsDataSource* instrumentsDataSource;
NSPoint origin;
audioSystem = [[AudioSystem alloc] init];
virtualDestination = [[PYMIDIVirtualDestination alloc] initWithName:@"SimpleSynth virtual input"];
[self buildMIDIInputPopUp];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(midiSetupChanged:)
name:@"PYMIDISetupChanged" object:nil
];
// This alloc/init pair is split over 2 lines to stop silly compiler warnings.
channelsDataSource = [ChannelsDataSource alloc];
channelsDataSource = [channelsDataSource initWithAudioSystem:audioSystem];
[channelsTable setDataSource:channelsDataSource];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(audioSystemInstrumentChanged:)
name:@"instrumentChanged" object:audioSystem
];
// Catch when user picks a new MIDI channel to update Instruments selection to match
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(channelsTableSelectionChanged:)
name:@"NSTableViewSelectionDidChangeNotification" object:channelsTable
];
// The following hardcoded values should really be dynamically pulled from the
// AudioSystem, but the values you get when doing that make the slider very
// lop-sided. I'd also have to set the slider labels dynamically.
[cutoffSlider setMinValue:10.0];
[cutoffSlider setMaxValue:12000.0];
[audioSystem setFilterCutoff:12000.0];
[cutoffSlider setFloatValue:[audioSystem getFilterCutoff]];
instrumentsDataSource = [[InstrumentsDataSource alloc] initWithAudioSystem:audioSystem];
[instrumentsTable setDataSource:instrumentsDataSource];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(instrumentsTableSelectionChanged:)
name:@"NSTableViewSelectionDidChangeNotification" object:instrumentsTable
];
[self updateMIDIDetails];
// We must delay opening the drawer until the window is visible
[instrumentsDrawer performSelector:@selector(open) withObject:nil afterDelay:0];
if (![mainWindow setFrameUsingName:@"MainWindowFrame"]) {
// Center our window, taking the width of the drawer into account.
[mainWindow center];
origin = [mainWindow frame].origin;
origin = NSMakePoint (origin.x - [instrumentsDrawer contentSize].width/2.0, origin.y);
[mainWindow setFrameOrigin:origin];
}
[mainWindow setFrameAutosaveName:@"MainWindowFrame"];
uiUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateUI:) userInfo:nil repeats:YES];
[uiUpdateTimer retain];
}
- (void)updateUI:(NSTimer*)timer
{
[self updateCPULoadGuage];
}
- (void)buildMIDIInputPopUp
{
PYMIDIManager* manager = [PYMIDIManager sharedInstance];
NSArray* sources;
NSEnumerator* enumerator;
PYMIDIEndpoint* input;
[midiInputPopup removeAllItems];
sources = [manager realSources];
enumerator = [sources objectEnumerator];
while (input = [enumerator nextObject]) {
[midiInputPopup addItemWithTitle:[input displayName]];
[[midiInputPopup lastItem] setRepresentedObject:input];
}
if ([sources count] > 0) {
[[midiInputPopup menu] addItem:[NSMenuItem separatorItem]];
}
[midiInputPopup addItemWithTitle:[virtualDestination name]];
[[midiInputPopup lastItem] setRepresentedObject:virtualDestination];
if ([audioSystem midiInput] == nil) {
if ([sources count] > 0)
[audioSystem setMIDIInput:[sources objectAtIndex:0]];
else
[audioSystem setMIDIInput:virtualDestination];
}
[midiInputPopup selectItemAtIndex:[midiInputPopup indexOfItemWithRepresentedObject:[audioSystem midiInput]]];
}
// This is called when the user selects a new MIDI input from the popup
- (IBAction)midiInputChanged:(id)sender
{
[audioSystem setMIDIInput:[[midiInputPopup selectedItem] representedObject]];
}
- (void)midiSetupChanged:(NSNotification*)notification
{
[self buildMIDIInputPopUp];
}
// This is called when the user pressed the "Open..." button or chooses "Open..." from
// the file menu.
// I couldn't make NSApplication do this for me, so this displays an OpenPanel and calls
// application:opeFile: for any files chosen.
- (IBAction)openDocument:(id)sender
{
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
NSMutableArray* fileTypes;
NSArray* fileNames;
int i;
// Note that there should be some way to get these from the Info.plist
fileTypes = [NSMutableArray arrayWithCapacity:4];
[fileTypes addObject:@"sf2"];
[fileTypes addObject:@"SF2"];
[fileTypes addObject:@"dls"];
[fileTypes addObject:@"DLS"];
[openPanel runModalForTypes:fileTypes];
fileNames = [openPanel filenames];
for (i = 0; i < [fileNames count]; i++) {
[self application:[NSApplication sharedApplication] openFile:[fileNames objectAtIndex:i]];
}
}
- (IBAction)restoreAppleSounds:(id)sender
{
[audioSystem restoreAppleSounds];
[soundSetTextField setStringValue:@"Apple DLS Sound Set"];
[channelsTable reloadData];
[instrumentsTable reloadData];
[channelsTable selectRow:0 byExtendingSelection:NO];
[self updateInstrumentSelection];
[self updateMIDIDetails];
}
// This is called to open a particular file, either by the openDocument method
// or by NSApplication when files are dragged-and-dropped on the app.
- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename
{
if ([audioSystem openFile:filename]) {
// Display the filename in our window
[soundSetTextField setStringValue:[filename lastPathComponent]];
[channelsTable reloadData];
[instrumentsTable reloadData];
[self updateMIDIDetails];
[channelsTable selectRow:0 byExtendingSelection:NO];
[self updateInstrumentSelection];
// Add the file to the recent documents menu
[[NSDocumentController sharedDocumentController]
noteNewRecentDocumentURL:[NSURL fileURLWithPath:filename]
];
return YES;
}
else {
[self displayOpenFailedAlert:filename];
return NO;
}
}
- (void)displayOpenFailedAlert:(NSString*)fileName
{
NSRunAlertPanel (
[NSString
stringWithFormat:NSLocalizedString (@"The file \"%@\" could not be opened.", @""),
[fileName lastPathComponent]
],
NSLocalizedString (@"The file is in a format that SimpleSynth does not understand.", @""),
NSLocalizedString (@"OK", @""), nil, nil
);
}
- (void)audioSystemInstrumentChanged:(NSNotification*)notification
{
[channelsTable reloadData];
}
- (void)channelsTableSelectionChanged:(NSNotification*)notification
{
[self updateInstrumentSelection];
}
- (void)updateInstrumentSelection
{
UInt32 channel = [channelsTable selectedRow];
MusicDeviceInstrumentID channelInstrumentID = [audioSystem currentInstrumentOnChannel:channel];
UInt32 instrumentIndex = [audioSystem indexOfInstrumentID:channelInstrumentID];
[instrumentsTable selectRow:instrumentIndex byExtendingSelection:NO];
}
- (void)instrumentsTableSelectionChanged:(NSNotification*)notification
{
int channel = [channelsTable selectedRow];
int instrumentIndex = [instrumentsTable selectedRow];
MusicDeviceInstrumentID instrumentID = [audioSystem instrumentIDAtIndex:instrumentIndex];
[self updateMIDIDetails];
[audioSystem setInstrument:instrumentID forChannel:channel];
}
- (void)updateMIDIDetails
{
int instrumentIndex = [instrumentsTable selectedRow];
MusicDeviceInstrumentID instrumentID = [audioSystem instrumentIDAtIndex:instrumentIndex];
MIDIInstrument instrument = [AudioSystem instrumentIDToInstrument:instrumentID];
[programNumberField setIntValue:instrument.programChange + 1];
[bankSelectMSBField setIntValue:instrument.bankSelectMSB];
[bankSelectLSBField setIntValue:instrument.bankSelectLSB];
}
// This is called when the user moves the filter cutoff slider
- (IBAction)cutoffSliderChanged:(id)sender
{
[audioSystem setFilterCutoff:[cutoffSlider floatValue]];
}
- (void)updateCPULoadGuage
{
float load = [audioSystem getCPULoad];
[cpuLoadGuage setFloatValue:load];
}
- (IBAction)displayLicense:(id)sender
{
[[NSWorkspace sharedWorkspace]
openFile:[[NSBundle mainBundle] pathForResource:@"License" ofType:@"html"]
];
}
- (IBAction)visitWebSite:(id)sender
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://notahat.com/simplesynth/"]];
}
- (IBAction)sendFeedback:(id)sender
{
NSString* name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
name = [[name componentsSeparatedByString:@" "] componentsJoinedByString:@"%20"];
NSString* version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
version = [[version componentsSeparatedByString:@" "] componentsJoinedByString:@"%20"];
[[NSWorkspace sharedWorkspace] openURL:[NSURL
URLWithString:[NSString
stringWithFormat:@"mailto:[email protected]?subject=%@%%20%@", name, version
]
]];
}
@end