How to fade an NSSound object - objective-c

I've written a cheap & cheerful sound board in for my Mac, and I play the various sounds with NSSound like this:
-(void)play:(NSSound *)soundEffect:(BOOL)stopIfPlaying {
BOOL wasPlaying = FALSE;
if([nowPlaying isPlaying]) {
[nowPlaying stop];
wasPlaying = TRUE;
}
if(soundEffect != nowPlaying)
{
[soundEffect play];
nowPlaying = soundEffect;
} else if(soundEffect == nowPlaying && ![nowPlaying isPlaying] && !wasPlaying) {
[nowPlaying play];
}
}
Rather than just stop it dead, I'd like it to fade out over a couple of seconds or so.

This is the final version of the method:
-(void)play:(NSSound *)soundEffect:(BOOL)stopIfPlaying {
BOOL wasPlaying = FALSE;
if([nowPlaying isPlaying]) {
struct timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 25000000;
// If the sound effect is the same, fade it out.
if(soundEffect == nowPlaying)
{
for(int i = 1; i < 30; ++i)
{
[nowPlaying setVolume: (1.0 / i )];
nanosleep(&ts, &ts);
}
}
[nowPlaying stop];
[nowPlaying setVolume:1];
wasPlaying = TRUE;
}
if(soundEffect != nowPlaying)
{
[soundEffect play];
nowPlaying = soundEffect;
} else if(soundEffect == nowPlaying && ![nowPlaying isPlaying] && !wasPlaying) {
[nowPlaying play];
}
}
So it only fades out if I pass the same sound in (ie, click the same button), also, I went for nanosleep rather than sleep, as that on has a granularity of 1 second.
I struggled for a while trying to work out why my 200 millisecond delay didn't seem to have any effect, but then 200 NANOseconds isn't really that long is it :-)

I would use NSTimer to avoid blocking the main thread.

Something like this perhaps? You probably want a more linear dropoff, but the basic idea is make a loop and sleep the period of time till the next update.
if([nowPlaying isPlaying]) {
for(int i = 1; i < 100; ++i)
{
[nowPlaying setVolume: (1.0 / i)];
Sleep(20);
}
[nowPlaying stop];
wasPlaying = TRUE;
}

Related

FFmpeg jump to most recent frame

I am looking for some help with dropping/skipping FFmpeg frames. The project I am working on streams live video which when the app goes into the background, upon returning to an active state the video stream spends a long time catching up by fast forwarding itself to the current frame. This isn't ideal and what I am aiming to achieve is have the app immediately jump to the most recent frame.
What I need to do is drop the amount of frames that are being fast-forwarded in order to catch up to the most recent frame. Is this possible? Here is my current code which decodes the frames:
- (NSArray *) decodeFrames: (CGFloat) minDuration
{
NSMutableArray *result = [NSMutableArray array];
#synchronized (lock) {
if([_reading integerValue] != 1){
_reading = [NSNumber numberWithInt:1];
#synchronized (_seekPosition) {
if([_seekPosition integerValue] != -1 && _seekPosition){
[self seekDecoder:[_seekPosition longLongValue]];
_seekPosition = [NSNumber numberWithInt:-1];
}
}
if (_videoStream == -1 &&
_audioStream == -1)
return nil;
AVPacket packet;
CGFloat decodedDuration = 0;
CGFloat totalDuration = [TimeHelper calculateTimeDifference];
do {
BOOL finished = NO;
int count = 0;
while (!finished) {
if (av_read_frame(_formatCtx, &packet) < 0) {
_isEOF = YES;
[self endOfFileReached];
break;
}
[self frameRead];
if (packet.stream_index ==_videoStream) {
int pktSize = packet.size;
while (pktSize > 0) {
int gotframe = 0;
int len = avcodec_decode_video2(_videoCodecCtx,
_videoFrame,
&gotframe,
&packet);
if (len < 0) {
LoggerVideo(0, #"decode video error, skip packet");
break;
}
if (gotframe) {
if (!_disableDeinterlacing &&
_videoFrame->interlaced_frame) {
avpicture_deinterlace((AVPicture*)_videoFrame,
(AVPicture*)_videoFrame,
_videoCodecCtx->pix_fmt,
_videoCodecCtx->width,
_videoCodecCtx->height);
}
KxVideoFrame *frame = [self handleVideoFrame];
if (frame) {
[result addObject:frame];
_position = frame.position;
decodedDuration += frame.duration;
if (decodedDuration > minDuration)
finished = YES;
}
} else {
count++;
}
if (0 == len)
break;
pktSize -= len;
}
}
av_free_packet(&packet);
}
} while (totalDuration > 0);
_reading = [NSNumber numberWithInt:0];
return result;
}
}
return result;

Waiting for code to execute (already used dispatch groups)

Got this lengthy section of code shown below and I've hit a brick wall. Basically the code runs perfectly and does exactly what I want it to do. However, it needs to finish running all the code within this section before printing the "Finished" at the end. However adding semaphores or another dispatch group forces a breakpoint. Might be obvious, but could someone give me a bit of advice on this please?
Note: I cannot use that dispatch at the bottom to call another method. Remember its within a loop.
for (id i in arr) {
searchByName = nil;
if ([i containsString:#"word1"] || [i containsString:#"word2"]) {
NSRange searchFromRange = [i rangeOfString:#"ong>"];
NSRange searchToRange = [i rangeOfString:#"</str"];
NSString *substring = [i substringWithRange:NSMakeRange(searchFromRange.location+searchFromRange.length, searchToRange.location-searchFromRange.location-searchFromRange.length)];
[allergens addObject:substring];
if ([substring isEqualToString:#"Examee"] && veg_lac_ovoSafe == TRUE) {
veg_ovoSafe = FALSE;
vegSafe = FALSE;
}
else if ([substring isEqualToString:#"Example"] && veg_lac_ovoSafe == TRUE) { //USE OF HEURISTICS
veg_lacSafe = FALSE;
vegSafe = FALSE;
}
else if ([substring isEqualToString:#"Exam"]) {
pescetarianSafe = TRUE;
vegSafe = FALSE;
veg_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_lac_ovoSafe = FALSE;
pollotarianSafe = FALSE;
}
NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
NSCharacterSet *numbersToRemove = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
substring = [[substring componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:#""];
searchByName = [[[substring componentsSeparatedByCharactersInSet:numbersToRemove] componentsJoinedByString:#""] lowercaseString];
}
else {
NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
NSCharacterSet *numbersToRemove = [NSCharacterSet characterSetWithCharactersInString:#"0123456789"];
NSString *searchItem = [[i componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:#""];
searchByName = [[[searchItem componentsSeparatedByCharactersInSet:numbersToRemove] componentsJoinedByString:#""] lowercaseString];
}
if (![searchByName isEqualToString:#" "]) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_enter(_groupSearch);
dispatch_async(queue, ^{
[[self databaseQuery:searchByName] observeEventType:FIRDataEventTypeChildAdded
withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
if (snapshot.value != NULL) {
NSLog(#"%#", snapshot.value);
for (int i=0; i < [[NSString stringWithFormat:#"%#", snapshot.value] length]; i++) {
NSString *x = [NSString stringWithFormat:#"%c", [[NSString stringWithFormat:#"%#", snapshot.value] characterAtIndex:i]];
NSLog(#"%#", x);
if ([x isEqualToString:#"1"]) {
vegSafe = FALSE;
}
else if ([x isEqualToString:#"2"]) {
vegSafe = FALSE;
veg_lacSafe = FALSE;
}
else if ([x isEqualToString:#"3"]) {
vegSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"4"]) { //Could use switch case.
vegSafe = FALSE;
veg_lac_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"5"]) {
pescetarianSafe = FALSE;
}
else if ([x isEqualToString:#"6"]) {
pollotarianSafe = FALSE;
}
}
}
dispatch_group_leave(_groupSearch);
}
withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
dispatch_group_leave(_groupSearch);
}];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_group_wait(_groupSearch, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
dispatch_sync(queue, ^{
//Hi
});
});
}
}
NSLog(#"Finished");
Since you're calling dispatch_group_wait() inside a dispatch_async() block, it appears you want to run the completion block asynchronously on the global dispatch queue. The proper way to do this is to use dispatch_group_notify() instead:
dispatch_group_notify(groupSearch, queue, ^{
NSLog(#"Finished")
});
This will run the block on the specified queue once all the blocks submitted to the group have finished, so no dispatch_async() or dispatch_sync() is needed. Additionally, waiting inside dispatch_async() will block a thread in the GCD pool, which is a really bad idea since there are a finite number of them. This is also something that using dispatch_group_notify() instead will avoid.
EDIT: The paragraph below is no longer relevant to your updated code:
Also, as the other answerer pointed out, you probably want to put the call to dispatch_group_notify() after the loop finishes, assuming that you want one completion block to run at the very end of your work. If what you want actually is a whole bunch of separate notifications, one for each run through the loop, then you should probably create a new group within the loop and use that instead. Using one group for the whole shebang and then setting up notifications within the loop is going to cause each notification to wait not only for the dispatch_group_leave() calls for that particular run through the loop, but for all the dispatch_group_enter() calls that have been made on any run through the loop to be balanced. So you'll get nothing until all your work is done, and then you'll suddenly spam a whole bunch of completion blocks all at once, with an accompanying thread explosion since they'll all be submitted on the global queue. This is probably not what you want.
Your use of dispatch_group_wait is in the wrong place. It needs to after the for loop, not inside it. And you can create queue before the loop and reuse it as needed.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
for (id i in arr) {
searchByName = nil;
// Lots of other code here
if (![searchByName isEqualToString:#" "]) {
dispatch_group_enter(_groupSearch);
dispatch_async(queue, ^{
[[self databaseQuery:searchByName] observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
if (snapshot.value != NULL) {
NSLog(#"%#", snapshot.value);
for (int i=0; i < [[NSString stringWithFormat:#"%#", snapshot.value] length]; i++) {
NSString *x = [NSString stringWithFormat:#"%c", [[NSString stringWithFormat:#"%#", snapshot.value] characterAtIndex:i]];
NSLog(#"%#", x);
if ([x isEqualToString:#"1"]) {
vegSafe = FALSE;
}
else if ([x isEqualToString:#"2"]) {
vegSafe = FALSE;
veg_lacSafe = FALSE;
}
else if ([x isEqualToString:#"3"]) {
vegSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"4"]) { //Could use switch case.
vegSafe = FALSE;
veg_lac_ovoSafe = FALSE;
veg_lacSafe = FALSE;
veg_ovoSafe = FALSE;
}
else if ([x isEqualToString:#"5"]) {
pescetarianSafe = FALSE;
}
else if ([x isEqualToString:#"6"]) {
pollotarianSafe = FALSE;
}
}
}
dispatch_group_leave(_groupSearch);
}
withCancelBlock:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
dispatch_group_leave(_groupSearch);
}];
});
}
}
dispatch_group_wait(_groupSearch, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
NSLog(#"Finished");

Stop pinwheel of death in infinite loop Objective C

I am writing a simple timer program for myself in objective c for my mac. The timer counts down properly, but I get the spinning pinwheel of death. How can I make the pinwheel go away? I think its because I have an infinite loop but there must be someway to bypass it.
I have an IBAction that triggered on a button click (the button is start). And from there, it calls another function that does the work.
Here is my IBAction:
- (IBAction)timerStart:(id)sender {
self.timerDidPause = NO;
[self timerRunning];
}
And here is timerRunning:
- (void)timerRunning {
for (;;) {
usleep(1000000);
if (self.timerDidPause == YES) {
}
else {
if (self.seconds == 0) {
if (self.minutes == 0) {
[self timerDone];
break;
}
else {
self.seconds = 59;
self.minutes = self.minutes - 1;
[self formatTimerLabel:self.hours :self.minutes :self.seconds];
}
}
else {
self.seconds = self.seconds - 1;
[self formatTimerLabel:self.hours :self.minutes :self.seconds];
}
}
}
}
In this function, the function formatTimerLabel is called so here is that:
- (void)formatTimerLabel:(int)hours
:(int)minutes
:(int)seconds {
NSString *minuteString = [[NSString alloc] init];
NSString *secondString = [[NSString alloc] init];
if (minutes < 10) {
minuteString = [NSString stringWithFormat:#"0%d", minutes];
}
else {
minuteString = [NSString stringWithFormat:#"%d", minutes];
}
if (seconds < 10) {
secondString = [NSString stringWithFormat:#"0%d", seconds];
}
else {
secondString = [NSString stringWithFormat:#"%d", seconds];
}
[self.timerLabel setStringValue:[NSString stringWithFormat:#"%d:%#:%#", hours, minuteString, secondString]];
[self.timerLabel display];
}
You're causing the UI thread to hang with your loop. After a couple of seconds of that, the OS switches the cursor to a pinwheel.
You need to look into NSTimer and the Timer Programming Guide to schedule the timer to run outside of the UI thread.

is it possible to put a method(s) in a while loop and or if statements?

trying to convert from java to objective-c. im havin a hard time finding anything on this subject.i get error "semantic issue" cant figure it out.
here's the code:
-(void) createColorPalatte{
for (int i=0; i<4; i++) {
colorPalatte[i] = [self getRandomColor];
}}
-(BOOL) checkColorPalatte {
for (int i=0; i<4; i++) {
for (int j=i+1; j<4; j++) {
if ([getColorFroemPalatte[i]]==[getColorFromPalatte[j]] ) {
return YES;
}
}
}
return NO;
}
-(void) redoColorPalatte {
while (YES==[checkColorPalatte];) {
[createColorPalatte];
}
}
-(char) getColorFromPalatte: (int) index{
return colorPalatte[index];
}
You need to give the target for your method as well, i.e. which instance it is called on. And of course use : instead of [] for the arguments.
[self checkColorPalette] and [self getColorFromPalette:i]
Here's the fixes I can see in the methods you've posted:
-(BOOL) checkColorPalatte {
for (int i=0; i<4; i++) {
for (int j=i+1; j<4; j++) {
if ([self getColorFromPalatte:i]==[self getColorFromPalatte:j] ) { // CHANGED Froem typo and changed [getColorFromPalette[i]] to [self getColorFromPalette:i]
return YES;
}
}
}
return NO;
}
-(void) redoColorPalatte {
while (YES==[self checkColorPalatte]) { // changed [checkColorPalatte]; to [self getColorPalatte]
[self createColorPalatte]; // ADDED 'self'
}
}
Basically remember the convention of [object method] or [object methodArg: value] where in Java it would be more like object.method() or object.method(value)
Also, Palette as in colours is spelt "palette" (not a bug but a spelling mistake).

How to correctly measure NSInputStream data rate

I'm trying to measure actual transfer speed during ftp download, download itself is working, streams are hooked up in run loop. Measurment is done in NSStreamEventHasBytesAvailable using CFTimeGetCurrent on event start and at the end, after data is written to file elapsed time is computed with (double)previousTimestamp-CFAbsoluteTimeGetCurrent, but the time I get is absolutely unreasonable. Tested on simulator and device, can anyone enlighten me?
code:
switch (eventCode) {
case NSStreamEventOpenCompleted: {
} break;
case NSStreamEventHasBytesAvailable: {
if ([[self.fileStream propertyForKey:NSStreamFileCurrentOffsetKey] intValue]==0) {
previousTimestamp = CFAbsoluteTimeGetCurrent();
}
NSInteger bytesRead;
uint8_t buffer[32768];
bytesRead = [self.networkStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1)
{
[self _stopReceiveWithStatus:#"Err"];
}
else if (bytesRead == 0)
{
[self _stopReceiveWithStatus:nil];
}
else
{
[self completition:bytesRead];
NSInteger bytesWritten;
NSInteger bytesWrittenSoFar;
// Write to the file.
bytesWrittenSoFar = 0;
do {
bytesWritten = [self.fileStream write:&buffer[bytesWrittenSoFar] maxLength:bytesRead - bytesWrittenSoFar];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self _stopReceiveWithStatus:#"File err"];
break;
} else {
bytesWrittenSoFar += bytesWritten;
}
} while (bytesWrittenSoFar != bytesRead);
[self downloadSpeedSave:bytesRead :previousTimestamp-CFAbsoluteTimeGetCurrent()];
previousTimestamp = CFAbsoluteTimeGetCurrent();
An alternative that I have used is to use the time.h and c routines to capture time.
http://www.cplusplus.com/reference/clibrary/ctime/time/
Another good link on SO
iPhone: How to get current milliseconds?