Playing a sine wave tone in Cocoa - objective-c

(Update: found the answer, see below.)
I'm trying to play a 1 kHz sine wave tone in an Objective-C Cocoa app; I've (tried to) translate a Swift example to Objective-C, but there must be a mistake somewhere, as the resulting tone is around 440 Hz instead of 1 kHz, and only on the left channel.
The code:
#property (nonatomic, strong) AVAudioEngine *audioEngine;
#property (nonatomic, strong) AVAudioPlayerNode *player;
#property (nonatomic, strong) AVAudioMixerNode *mixer;
#property (nonatomic, strong) AVAudioPCMBuffer *buffer;
// -----
self.audioEngine = [[AVAudioEngine alloc] init];
self.player = [[AVAudioPlayerNode alloc] init];
self.mixer = self.audioEngine.mainMixerNode;
self.buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[self.player outputFormatForBus:0] frameCapacity:100];
self.buffer.frameLength = 100;
float amplitude = 0.4;
float frequency = 1000;
float sampleRate = [[self.mixer outputFormatForBus:0] sampleRate];
NSInteger channelCount = [[self.mixer outputFormatForBus:0] channelCount];
float *const *floatChannelData = self.buffer.floatChannelData;
float *p2 = *floatChannelData;
NSLog(#"Sine generator: sample rate = %.1f, %ld channels, frame length = %u.", sampleRate, (long)channelCount, self.buffer.frameLength);
for (int i = 0; i < self.buffer.frameLength ; i += channelCount) {
// a = Amplitude
// n = current sample
// r = Sample rate (samples / sec.)
//
// f(n) = a * sin( theta(n) )
// where theta(n) = 2 * M_PI * n / r
float theta = 441.0f * i * 2.0 * M_PI / sampleRate;
float value = sinf(theta);
p2[i] = value * amplitude;
}
[self.audioEngine attachNode:self.player];
[self.audioEngine connect:self.player to:self.mixer format:[self.player outputFormatForBus:0]];
[self.audioEngine startAndReturnError:nil];
[self.player play];
[self.player scheduleBuffer:self.buffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
I suspect that there is either a math error in the float theta=... line, or I'm making a mistake with the floatChannelData buffer. The original Swift line reads:
buffer.floatChannelData.memory[i] = val * 0.5
Not sure what to make of the float *const * type of floatChannelData exactly. My understanding is that this is a pointer to 2 x float * const arrays. (2 because of the number of channels, left/right.)
The source of the Swift code is here: http://www.tmroyal.com/playing-sounds-in-swift-audioengine.html
It would be really nice if somebody could explain the buffer structure to me.
Found the solution
The problem was two-fold. First, the value 441.0 did indeed control the frequency. But changing that alone did not solve the problem; the resulting tone was more sawtooth-like than sine, and found out why.
With the factor 441 and a sample rate of 44.1 kHz, the ratio of those value was 1:100 - exactly the number of samples in the buffer. Changing 441 to a value that is not a whole multiple of that results in an "incomplete" sine wave: the value in the last sample frame (#100) is not zero, which causes a sharp drop-off when the loop starts again - and that sounds like a sawtooth wave.
I had to change the frame buffer length to be exactly (or a multiple of) the frequency-to-sample-rate ratio, so that the last sample value was (close to) zero.
The updated code:
self.audioEngine = [[AVAudioEngine alloc] init];
self.player = [[AVAudioPlayerNode alloc] init];
self.mixer = self.audioEngine.mainMixerNode;
float sampleRate = [[self.mixer outputFormatForBus:0] sampleRate];
AVAudioFrameCount frameBufferLength = floor(sampleRate / self.frequency) * 1;
self.buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[self.player outputFormatForBus:0] frameCapacity:frameBufferLength];
self.buffer.frameLength = frameBufferLength;
NSInteger channelCount = [[self.mixer outputFormatForBus:0] channelCount];
float *const *floatChannelData = self.buffer.floatChannelData;
NSLog(#"Sine generator: sample rate = %.1f, %ld channels, frame length = %u.", sampleRate, (long)channelCount, self.buffer.frameLength);
for (int i = 0; i < self.buffer.frameLength ; i ++) {
float theta = self.frequency * i * 2.0 * M_PI / sampleRate;
float value = sinf(theta);
for (int channelNumber = 0; channelNumber < channelCount ; channelNumber++) {
float * const channelBuffer = floatChannelData[channelNumber];
channelBuffer[i] = value * self.amplitude;
}
}
That way any number of channels are handled correctly, too.

The frequency part is easy: the literal 441.0f in your calculation of theta controls that, so just change it to whatever you want.
For the mono issue, you appear to only be writing one channel of data: p2[i] = value * amplitude; If you're correct about the composition of floatChannelData, then you want this:
float * const * floatChannelData = self.buffer.floatChannelData;
float * const left = floatChannelData[0];
float * const right = floatChannelData[1];
//...
// N.B. Changed the increment
for (int i = 0; i < self.buffer.frameLength ; i++ ) {
// ...
left[i] = value * amplitude;
right[i] = value * amplitude;
}
However, given the increment step in your for loop, it's possible that your buffer is interleaved (left and right channels alternating in the same buffer). In that case, you leave the loop increment, but write to both p2[i] and p2[i+1] on each step (easy for stereo; if you had more channels, you'd do an inner loop over those and write to p2[j] for j from 0 to $NUM_CHANNELS).

Related

Yaw(angle) value are not stable, it is drifting a few degrees. Does anyone know how to solve it?

I am currently working on iOS project, where i use motion data.
I get a good results with pitch and roll values, but yaw value is constantly drifting. I have applied Kalman filter and results are remain the same.
Does anyone has an idea how to solve it?
Here is some source code (Objective C)
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical
toQueue:[NSOperationQueue currentQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
//NSString *yaw = [NSString
//stringWithFormat:#" %.3f", motion.attitude.yaw];
NSString *pitch = [NSString
stringWithFormat:#" %.3f", motion.attitude.pitch];
NSString *roll = [NSString
stringWithFormat:#" %.3f", motion.attitude.roll];
//Converting NSSring type variable in to a double
//double a_yaw = [yaw doubleValue];
double a_pitch = [pitch doubleValue];
double a_roll = [roll doubleValue];
CMQuaternion quat = self.motionManager.deviceMotion.attitude.quaternion;
double yaw = 180/M_PI * (asin(2*quat.x*quat.y + 2*quat.w*quat.z));
// Kalman filtering
static float q = 0.1; // process noise
static float r = 0.1; // sensor noise
static float p = 0.1; // estimated error
static float k = 0.5; // kalman filter gain
float x = motionLastYaw;
p = p + q;
k = p / (p + r);
x = x + k*(yaw - x);
p = (1 - k)*p;
motionLastYaw = x;
//Converting angles to degrees
//yaw = yaw * 180/M_PI;
a_pitch = a_pitch * 180/M_PI;
a_roll = a_roll * 180/M_PI;
"yaw" value need a reference to additional coordinate system(camera...).

Plotting hits within given areas of circle - Code for setup getting bloated

[Alternative approach given by user1118321, old and new code listed for reference]
I am busy writing a small app which allows a game of darts 501 to be scored. I have set up a class to contain the individual scoring areas (GDSBoardArea) and one mutable array which represents the entire dart board and holds all the individual scoring class instances.
I have written the code for one section (20 score, inner and outer, and triple and double 20) but the issue I have is that the code for this is quite bloated and was woindering if there was a better way to init all the areas with less, more managable code as I will have to repeat this below another 19 times for the other sections?
I have a class that holds the settings for the various individual areas, one class to hold all these areas and helper functions.
I did attempt to try reuse the CGPoints and area arrays but this simply caused issues I think related to the fact that it updates a reference as the scoringAreas addObject does not copy the array object (Please correct me if I am wrong).
I setup the scoring areas once when the application runs using the createAreas function and I cant seem to reuse the CGPoints and arrays holding them.
My questions are:
1 - Is there a way to pass in a variable array of CGPoints to "GDSBoardArea"'s last parameter instead of having to create the CGPoints, add them to an array and pass the array in.
2 - Does the mutable array method addobject copy or reference the object passed to it? Is it there any way to update an array and then make a copy of it to a "master" mutable array so that the master array is effectively holding a different array in each element. I am basically try to avoid having to create a new array for each of the master arrays elements.
createAreas
-(void)createAreas
{
scoringAreas = [[NSMutableArray alloc] init];
/*
20 scoring section
*/
// 20 score - Inner triangle area
/////////////////////////////////////////
// Set up CGPoints for area
CGPoint pt_inner20_1 = CGPointMake(368.0f, 234.0f);
CGPoint pt_inner20_2 = CGPointMake(413.0f, 234.0f);
CGPoint pt_inner20_3 = CGPointMake(389.0f, 360.0f);
// Add points to array for passing to GDSBoardArea Class
NSMutableArray * areaPointsArray_Inner20 = [NSMutableArray array];
[areaPointsArray_Inner20 addObject:[NSValue valueWithCGPoint:pt_inner20_1]];
[areaPointsArray_Inner20 addObject:[NSValue valueWithCGPoint:pt_inner20_2]];
[areaPointsArray_Inner20 addObject:[NSValue valueWithCGPoint:pt_inner20_3]];
// Create GDSBoardArea Instance with init values
GDSBoardArea *scoreAreaFor_Inner20 = [[GDSBoardArea alloc] initWithName:#"20-Inner" abbrev:#"20" areaValue:20 pointsArray:NULL];
// Add Array of CGPoints to GDSScoreArea
[scoreAreaFor_Inner20 setPointsForPath:areaPointsArray_Inner20];
// Add ScoreArea to ScoreAreas Class
[scoringAreas addObject:scoreAreaFor_Inner20];
// 20 score - Outer Quadrilateral
/////////////////////////////////////////
CGPoint pt_outer20_1 = CGPointMake(353.0f, 125.0f);
CGPoint pt_outer20_2 = CGPointMake(429.0f, 127.0f);
CGPoint pt_outer20_3 = CGPointMake(415.0f, 208.0f);
CGPoint pt_outer20_4 = CGPointMake(365.0f, 209.0f);
NSMutableArray * areaPointsArray_Outer20 = [NSMutableArray array];
[areaPointsArray_Outer20 addObject:[NSValue valueWithCGPoint:pt_outer20_1]];
[areaPointsArray_Outer20 addObject:[NSValue valueWithCGPoint:pt_outer20_2]];
[areaPointsArray_Outer20 addObject:[NSValue valueWithCGPoint:pt_outer20_3]];
[areaPointsArray_Outer20 addObject:[NSValue valueWithCGPoint:pt_outer20_4]];
GDSBoardArea *scoreAreaFor_Outer20 = [[GDSBoardArea alloc] initWithName:#"20-Outer" abbrev:#"20" areaValue:20 pointsArray:NULL];
[scoreAreaFor_Outer20 setPointsForPath:areaPointsArray_Outer20];
[scoringAreas addObject:scoreAreaFor_Outer20];
// Double 20 Quadrilateral
/////////////////////////////////////////
CGPoint pt_d20_1 = CGPointMake(351.0f, 102.0f);
CGPoint pt_d20_2 = CGPointMake(353.0f, 118.0f);
CGPoint pt_d20_3 = CGPointMake(430.0f, 119.0f);
CGPoint pt_d20_4 = CGPointMake(433.0f, 104.0f);
NSMutableArray * areaPointsArray_Double20 = [NSMutableArray array];
[areaPointsArray_Double20 addObject:[NSValue valueWithCGPoint:pt_d20_1]];
[areaPointsArray_Double20 addObject:[NSValue valueWithCGPoint:pt_d20_2]];
[areaPointsArray_Double20 addObject:[NSValue valueWithCGPoint:pt_d20_3]];
[areaPointsArray_Double20 addObject:[NSValue valueWithCGPoint:pt_d20_4]];
GDSBoardArea *scoreAreaFor_double20 = [[GDSBoardArea alloc] initWithName:#"20-Double" abbrev:#"d20" areaValue:40 pointsArray:NULL];
[scoreAreaFor_double20 setPointsForPath:areaPointsArray_Double20];
[scoringAreas addObject:scoreAreaFor_double20];
// Triple 20 Quadrilateral
/////////////////////////////////////////
CGPoint pt_t20_1 = CGPointMake(367.0f, 214.0f);
CGPoint pt_t20_2 = CGPointMake(368.0f, 226.0f);
CGPoint pt_t20_3 = CGPointMake(412.0f, 226.0f);
CGPoint pt_t20_4 = CGPointMake(413.0f, 214.0f);
NSMutableArray * areaPointsArray_Triple20 = [NSMutableArray array];
[areaPointsArray_Triple20 addObject:[NSValue valueWithCGPoint:pt_t20_1]];
[areaPointsArray_Triple20 addObject:[NSValue valueWithCGPoint:pt_t20_2]];
[areaPointsArray_Triple20 addObject:[NSValue valueWithCGPoint:pt_t20_3]];
[areaPointsArray_Triple20 addObject:[NSValue valueWithCGPoint:pt_t20_4]];
GDSBoardArea *scoreAreaFor_triple20 = [[GDSBoardArea alloc] initWithName:#"20-Triple" abbrev:#"t20" areaValue:60 pointsArray:NULL];
[scoreAreaFor_triple20 setPointsForPath:areaPointsArray_Triple20];
[scoringAreas addObject:scoreAreaFor_triple20];
}
ScoringAreas is a Mutable Array that simplt represents all the areas of the board and is defined as:
#property (strong, atomic) NSMutableArray *scoringAreas;
GDSBoardArea Class in case its relevant
// GDSBoardArea.h
#import <Foundation/Foundation.h>
#interface GDSBoardArea : NSObject
#property NSMutableArray *pointsForPath;
#property int areaValue;
#property NSString *name;
#property NSString *abbrev;
-(id) init;
-(id)initWithName:(NSString *)name_ abbrev:(NSString *)abbrev_ areaValue:(int)areaValue_ pointsArray:(NSMutableArray*)pointsArray;
#end
--
// GDSBoardArea.m
#import "GDSBoardArea.h"
#implementation GDSBoardArea
-(id)init{
if (self = [super init]) {
/* perform your post-initialization logic here */
[self setName:#""];
[self setAbbrev:#""];
[self setAreaValue:0];
[self setPointsForPath:NULL];
}
return self;
}
-(id)initWithName:(NSString *)name_ abbrev:(NSString *)abbrev_ areaValue:(int)areaValue_ pointsArray:(NSMutableArray*)pointsArray
{
self = [super init];
if (self) {
self.name = name_;
self.abbrev = abbrev_;
self.areaValue = areaValue_;
self.pointsForPath = pointsArray;
}
return self;
}
#end
Code change as per answer from user1118321:
createAreas function was completely removed and direct calculation done as follows (Will add more as it is fleshed out until complete)
-(GDSBoardArea *)checkPointsScore:(CGPoint)tappedPoint
{
#define NUM_PIE_SLICES 20
#define NUM_RINGS 7
#define DEG_PER_SLICE 18
#define PII 3.141592653
#define OFFSETFORBOARD 99.3f
float radius_board = 344.0f;
float thickness_innerbull = 16.0f;
float thickness_outerbull = 18.0f;
float thickness_innerSingleScore = 116.0f;
float thickness_double = 16.0f;
float thickness_outerSingleScore = 80.0f;
float thickness_triple = 20.0f;
float thickness_noscore = 78.0f;
GDSBoardArea *tappedScoreArea = NULL;
double dartX = tappedPoint.x;
double dartY = tappedPoint.y;
double centerX = 390.0f;
double centerY = 390.0f;
double angle = atan2((dartY - centerY), (dartX - centerX)) * 180 / PII;
angle = angle + OFFSETFORBOARD; // To force slice 0 to correspond to Score of 20
int slice = (int)floor(angle / DEG_PER_SLICE);
NSLog(#"angle: %f", angle);
NSLog(#"slice #: %i", slice);
return tappedScoreArea;
}
If it were me, I wouldn't bother at all with storing the various areas of the board. First, they aren't triangles and quads, and second, it's fairly easy to calculate them. You can figure out which "pie slice" you're in by getting the arctangent of the point where the dart lands, like this:
double angle = atan2((dartY - centerY) / (dartX - centerX));
You can then convert that to an integer between 1 and # of slices like this:
int slice = (int)floor(angle / anglePerSlice);
Next, you can figure out which ring the user is in by using the distance from the board's center:
double deltaX = dartX - centerX;
double deltaY = dartY - centerY;
double distFromCenter = sqrt (deltaX * deltaX + deltaY * deltaY);
You could then convert that into an index by looking it up in an array. You have a single array containing all the boundaries in a single slice. All slices have their boundaries at the same distances. So it would look something like this:
double concentricBounds[kNumBounds] = { 10, 15, 100, 110, 200, 210 }; // Just made up - I don't know what distances you're using.
bool done = false;
int index = 0;
for (int i = 0; (i < kNumBounds) && (!done); i++)
{
if (distFromCenter < concentricBounds [ i ])
{
done = true;
index = i;
}
}
if (!done)
{
...point was off the board...
}
So now you have the slice it was in and the ring it's in. You can just have a 2D array of point values to look up into:
int points [][] = { {50, 25, 20, 60, 20, 40 }, { 50, 25, 5, 15, 5, 15 }, ...etc... };
Note that atan2() returns angles with 0° being parallel to the X-axis and it increases in counter-clockwise order. So you may have to rotate the scores to orient your board correctly.

Having problems seeing polygons in my cocos2d code. Using cocos2d and box2d. Only in debug mode the actual polygons are visible

So I need help figuring out what code I am missing here. I have checked all over the place, but I need specifics on wether its the formulas used or a typo that i haven't noticed yet.
Here is the polygon class. I am trying to create random polygons with 8 vertices and then of course fill with a plain color. But I want them to continue to generate random position but leave them fixed. In a better way the poly's are my terrain.Ok revise: the polygons are there and my character interacts with them, but I cannot see them, and yes they are on the same layer. Oh but they don't keep generating at the bottom, which i am guessing i just need to delete the old ones once they go off the screen and it should make a new poly.
-(void) genBody:(b2World *)world pos:(CGPoint *)pos {
//Here we generate a somewhat random convex polygon by sampling
//the "eccentric anomaly" of an ellipse with randomly generated
//x and y scaling constants (a,b). The algorithm is limited by
//the parameter max_verts, and has a number of tunable minimal
//and scaling values.
// I need to change this to randomly choosing teh number of vertices between 3-8,
// then choosing random offsets from equally distributed t values.
// This will eliminate teh while loop.
screen_pos = ccp(pos->x, pos->y);
float cur_t;
float new_t;
float delta_t;
float min_delta_t = 0.5;
float t_scale = 1.5;
b2Vec2 *verts= new b2Vec2[m_maxVerts]; // this should be replaced by a private verts ... maybe ... hmm that will consume more ram though
float t_vec[m_maxVerts];
// Generate random vertices
int vec_len;
while (true) {
cur_t = 0.0;
for (vec_len=0; vec_len<m_maxVerts; vec_len++) {
//delta_t = t_scale*(float)rand()/(float)RAND_MAX; // wish they just had a randf method :/
delta_t = t_scale*floorf((double)arc4random()/ARC4RANDOM_MAX);
#ifdef POLY_DEBUG
CCLOG(#"delta_t %0.2f", delta_t);
#endif
if (delta_t < min_delta_t) {
delta_t = min_delta_t;
}
new_t = cur_t + delta_t;
if (new_t > 2*PI) {
break;
}
t_vec[vec_len] = new_t;
cur_t = new_t;
}
// We need at least three points for a triangle
if ( vec_len > 3 ) {
break;
}
}
At least where the body is being generated.
then...
float num_verts = vec_len;
b2BodyDef BodyDef;
BodyDef.type = b2_staticBody;
BodyDef.position.Set(pos->x/PTM_RATIO, pos->y/PTM_RATIO);
BodyDef.userData = self; // hope this is correct
m_polyBody = world->CreateBody(&BodyDef);
b2PolygonShape polyShape;
int32 polyVert = num_verts;
polyShape.Set(verts, polyVert);
b2FixtureDef FixtureDef;
FixtureDef.shape = &polyShape;
FixtureDef.userData = self; // hope this is correct
FixtureDef.density = 1.6f;
FixtureDef.friction = 0.4f;
FixtureDef.restitution = 0.5f;
m_polyBody->CreateFixture(&FixtureDef);
for (int i=0; i < num_verts; i++) {
// Convert from b2Vec2 to CCPoint and from physics units to pixels
m_verts[i] = ccp(verts[i].x*PTM_RATIO, verts[i].y*PTM_RATIO);
}
m_numVerts = num_verts;
delete verts;
}
-(void) setColor:(ccColor4F) color {
m_color = color;
}
-(BOOL) dirty {
return true;
}
-(void) draw {
//[super draw];
ccDrawPoly(m_verts, m_numVerts, YES);
CCLOG(#"Drawing?");
}
-(CGAffineTransform) nodeToParentTransform {
b2Vec2 pos = m_polyBody->GetPosition();
float x = pos.x * PTM_RATIO;
float y = pos.y * PTM_RATIO;
/*if ( ignoreAnchorPointForPosition_ ) {
x += anchorPointInPixels_.x;
y += anchorPointInPixels_.y;
}*/
// Make matrix
float radians = m_polyBody->GetAngle();
float c = cosf(radians);
float s = sinf(radians);
if( ! CGPointEqualToPoint(anchorPointInPixels_, CGPointZero) ){
x += c*-anchorPointInPixels_.x + -s*-anchorPointInPixels_.y;
y += s*-anchorPointInPixels_.x + c*-anchorPointInPixels_.y;
}
// Rot, Translate Matrix
transform_ = CGAffineTransformMake( c, s,
-s, c,
x, y );
return transform_;
}
there is some stuff in between but its less important. I can post it if asked.
Then the update function, which is based in my game scene class.
-(void)updateObstacles
{
//CCLOG(#"updating obstacles");
int xpos;
int ypos;
CGPoint pos;
for (int i=0; i<MAX_OBSTACLES; i++ ) {
// If there is no obstacle generate a new one
if ( obstacles[i] == NULL ) {
polyObstacleSprite *sprite = [[polyObstacleSprite alloc] init];
ypos = int(_winSize.width/2*(double)arc4random()/ARC4RANDOM_MAX) - _winSize.width/2;
xpos = int(_winSize.height/2*(double)arc4random()/ARC4RANDOM_MAX) - _winSize.height/2;
//CCLOG(#"generating obstacle at %d,%d", xpos, ypos);
pos = ccp(xpos, ypos);
[sprite genBody:_world pos:&pos];
[self addChild:sprite z:1];
obstacles[i] = sprite;
}
//CCLOG(#"position: %d, %d", obstacles[i]->screen, obstacles[i]->position.y); FINISH
}
}
Sorry if its sort of a mess I set this up quick, but pretty much what I want to do is have randomly generated polygons appear at the bottom of my iphone screen as my character moves down with gravity. I got everything else but the polygons working.
Thanks in advance for spending the time to read this.

Create monochrome CGImageRef (1-bit per pixel bitmap)

I'm trying to show a preview of an image in 1-bit monochrome, as in, not grayscale, but bitonal black and white. It's supposed to be an indication of how the image will look if it were faxed. Formats as low as 1-bit per pixel aren't available on OS X, only 8-bit grayscale. Is there any way to achieve this effect using Core Graphics or another framework (ideally with dithering)?
I know there's a filter called CIColorMonochrome but this only converts the image to grayscale.
The creation of a 1 bit deep NSImageRep (and also in the CG-world) is AFAIK not supported, So we have to do it manually. It might be useful to use CIImage for this task. Here I go the classical (you may call it old-fashioned) way. Here is a code that shows how we can do it. First a gray image is created from an NSImageRep so we have a well defined and simple format whatever the source image will be formatted (could also be a PDF file). The resulting gray image is the source for the bitonal image. Here is the code for creating a gray image: (without respecting the size / resolution of the source image, only the pixels count!):
- (NSBitmapImageRep *) grayRepresentationOf:(NSImageRep *)aRep
{
NSBitmapImageRep *newRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:[aRep pixelsWide]
pixelsHigh:[aRep pixelsHigh]
bitsPerSample:8
samplesPerPixel:1
hasAlpha:NO //must be NO !
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bytesPerRow:0
bitsPerPixel:0 ];
// this new imagerep has (as default) a resolution of 72 dpi
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:newRep];
if( context==nil ){
NSLog( #"*** %s context is nil", __FUNCTION__ );
return nil;
}
[NSGraphicsContext setCurrentContext:context];
[aRep drawInRect:NSMakeRect( 0, 0, [newRep pixelsWide], [newRep pixelsHigh] )];
[NSGraphicsContext restoreGraphicsState];
return [newRep autorelease];
}
In the next method we create an NXBitmapImageRep (bits per pixel=1, samples per pixel=1) from a given NSImageRep (one of it's subclasses) and will use the method just given:
- (NSBitmapImageRep *) binaryRepresentationOf:(NSImageRep *)aRep
{
NSBitmapImageRep *grayRep = [aRep grayRepresentation];
if( grayRep==nil ) return nil;
NSInteger numberOfRows = [grayRep pixelsHigh];
NSInteger numberOfCols = [grayRep pixelsWide];
NSBitmapImageRep *newRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:numberOfCols
pixelsHigh:numberOfRows
bitsPerSample:1
samplesPerPixel:1
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bitmapFormat:0
bytesPerRow:0
bitsPerPixel:0 ];
unsigned char *bitmapDataSource = [grayRep bitmapData];
unsigned char *bitmapDataDest = [newRep bitmapData];
// here is the place to use dithering or error diffusion (code below)
// iterate over all pixels
NSInteger grayBPR = [grayRep bytesPerRow];
NSInteger binBPR = [newRep bytesPerRow];
NSInteger pWide = [newRep pixelsWide];
for( NSInteger row=0; row<numberOfRows; row++ ){
unsigned char *rowDataSource = bitmapDataSource + row*grayBPR;
unsigned char *rowDataDest = bitmapDataDest + row*binBPR;
NSInteger destCol = 0;
unsigned char bw = 0;
for( NSInteger col = 0; col<pWide; ){
unsigned char gray = rowDataSource[col];
if( gray>127 ) {bw |= (1<<(7-col%8)); };
col++;
if( (col%8 == 0) || (col==pWide) ){
rowDataDest[destCol] = bw;
bw = 0;
destCol++;
}
}
}
// save as PNG for testing and return
[[newRep representationUsingType:NSPNGFileType properties:nil] writeToFile:#"/tmp/bin_1.png" atomically:YES];
return [newRep autorelease];
}
For error diffusion I used the following code which changes directly the bitmap of the gray image. This is allowed because the gray image itself is no longer used.
// change bitmapDataSource : use Error-Diffusion
for( NSInteger row=0; row<numberOfRows-1; row++ ){
unsigned char *currentRowData = bitmapDataSource + row*grayBPR;
unsigned char *nextRowData = currentRowData + grayBPR;
for( NSInteger col = 1; col<numberOfCols; col++ ){
NSInteger origValue = currentRowData[col];
NSInteger newValue = (origValue>127) ? 255 : 0;
NSInteger error = -(newValue - origValue);
currentRowData[col] = newValue;
currentRowData[col+1] = clamp(currentRowData[col+1] + (7*error/16));
nextRowData[col-1] = clamp( nextRowData[col-1] + (3*error/16) );
nextRowData[col] = clamp( nextRowData[col] + (5*error/16) );
nextRowData[col+1] = clamp( nextRowData[col+1] + (error/16) );
}
}
clamp is a macro defined before the method
#define clamp(z) ( (z>255)?255 : ((z<0)?0:z) )
This makes the unsigned char bytes to have valid values (0<=z<=255)

ObjC/Cocoa class for converting size to human-readable string?

Is there a simple way to do something like..
[NSMagicDataConverter humanStringWithBytes:20000000]
..which would return "19.1MB"?
Starting in OS X 10.8 and iOS 6, you can use NSByteCountFormatter.
Your example would look like this:
[NSByteCountFormatter stringFromByteCount:20000000 countStyle:NSByteCountFormatterCountStyleFile];
I would mush this into an NSFormatter subclass.
#import <Foundation/Foundation.h>
#interface SOFileSizeFormatter : NSNumberFormatter
{
#private
BOOL useBaseTenUnits;
}
/** Flag signaling whether to calculate file size in binary units (1024) or base ten units (1000). Default is binary units. */
#property (nonatomic, readwrite, assign, getter=isUsingBaseTenUnits) BOOL useBaseTenUnits;
#end
static const char sUnits[] = { '\0', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
static int sMaxUnits = sizeof sUnits - 1;
#implementation SOFileSizeFormatter
#synthesize useBaseTenUnits;
- (NSString *) stringFromNumber:(NSNumber *)number
{
int multiplier = useBaseTenUnits ? 1000 : 1024;
int exponent = 0;
double bytes = [number doubleValue];
while ((bytes >= multiplier) && (exponent < sMaxUnits)) {
bytes /= multiplier;
exponent++;
}
return [NSString stringWithFormat:#"%# %cB", [super stringFromNumber: [NSNumber numberWithDouble: bytes]], sUnits[exponent]];
}
#end
Usage:
NSString *path = ...; // path to a file of 1,500,000 bytes
NSString *sizeString = nil;
NSNumber *sizeAttrib = [[[NSFileManager defaultManager] attributesOfItemAtPath:path error:NULL]objectForKey:NSFileSize];
SOFileSizeFormatter *sizeFormatter = [[[SOFileSizeFormatter alloc] init] autorelease];
[sizeFormatter setMaximumFractionDigits:2];
sizeString = [sizeFormatter stringFromNumber:sizeAttrib];
// sizeString ==> #"1.43 MB"
[sizeFormatter setUseBaseTenUnits:YES];
sizeString = [sizeFormatter stringFromNumber:sizeAttrib];
// sizeString ==> #"1.5 MB"
Here's my own take on the problem:
enum {
kUnitStringBinaryUnits = 1 << 0,
kUnitStringOSNativeUnits = 1 << 1,
kUnitStringLocalizedFormat = 1 << 2
};
NSString* unitStringFromBytes(double bytes, uint8_t flags){
static const char units[] = { '\0', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' };
static int maxUnits = sizeof units - 1;
int multiplier = (flags & kUnitStringOSNativeUnits && !leopardOrGreater() || flags & kUnitStringBinaryUnits) ? 1024 : 1000;
int exponent = 0;
while (bytes >= multiplier && exponent < maxUnits) {
bytes /= multiplier;
exponent++;
}
NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease];
[formatter setMaximumFractionDigits:2];
if (flags & kUnitStringLocalizedFormat) {
[formatter setNumberStyle: NSNumberFormatterDecimalStyle];
}
// Beware of reusing this format string. -[NSString stringWithFormat] ignores \0, *printf does not.
return [NSString stringWithFormat:#"%# %cB", [formatter stringFromNumber: [NSNumber numberWithDouble: bytes]], units[exponent]];
}
By default (if 0 is passed for flags), it will output SI units (base ten). You can set kUnitStringBinaryUnits to select binary (base two) units suitable for memory, or kUnitStringOSNativeUnits to have the unit type selected automatically based on OS version (pre-Leopard gets base two, post-Leopard gets base ten). Setting kUnitStringLocalizedFormat formats the string based on the user's current locale. For example:
unitStringFromBytes(1073741824, 0); // → "1.07 GB"
unitStringFromBytes(1073741824, kUnitStringBinaryUnits); // → "1 GB"
unitStringFromBytes(1073741824, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "1.07 GB" (In Mac OS 10.6)
unitStringFromBytes(12345678901234567890123456789, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "12,345.68 YB" (In Mac OS 10.6, in the US)
unitStringFromBytes(12345678901234567890123456789, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "12.345,68 YB" (In Mac OS 10.6, in Spain)
Here's the helper function required for OS-native units:
BOOL leopardOrGreater(){
static BOOL alreadyComputedOS = NO;
static BOOL leopardOrGreater = NO;
if (!alreadyComputedOS) {
SInt32 majorVersion, minorVersion;
Gestalt(gestaltSystemVersionMajor, &majorVersion);
Gestalt(gestaltSystemVersionMinor, &minorVersion);
leopardOrGreater = ((majorVersion == 10 && minorVersion >= 5) || majorVersion > 10);
alreadyComputedOS = YES;
}
return leopardOrGreater;
}
NSString *stringFromFileSize(NSInteger theSize)
{
/*
From http://snippets.dzone.com/posts/show/3038 with slight modification
*/
float floatSize = theSize;
if (theSize<1023)
return([NSString stringWithFormat:#"%i bytes",theSize]);
floatSize = floatSize / 1024;
if (floatSize<1023)
return([NSString stringWithFormat:#"%1.1f KB",floatSize]);
floatSize = floatSize / 1024;
if (floatSize<1023)
return([NSString stringWithFormat:#"%1.1f MB",floatSize]);
floatSize = floatSize / 1024;
return([NSString stringWithFormat:#"%1.1f GB",floatSize]);
}
Here is a more Objective C-like function (uses NSNumber, NSArray, NSStirng, etc...) for doing this conversion.
This is based on Sidnicious's answer, so a big thanks for the initial work done there. Also based on Wikipedia articles.
Use it generally like this: [HumanReadableDataSizeHelper humanReadableSizeFromBytes:[NSNumber numberWithDouble:doubleValue]].
But, it looks like you want SI units with a 1024 multiplier so you would use it like this: [HumanReadableDataSizeHelper humanReadableSizeFromBytes:[NSNumber numberWithDouble:doubleValue] useSiPrefixes:YES useSiMultiplier:NO]
The reason I default to binary prefixes (ki, Mi) is because those seem to be the most appropriate unit prefix set to use for sizes of data on a computer. What you requested was the SI unit prefixes but using a multiplier of 1024, technically incorrect. Though I will note that SI prefixes for multiples of 1024 is fairly common and binary prefixes are not well accepted (according to Wikipedia).
HumanReadableDataSizeHelper.h
#interface HumanReadableDataSizeHelper : NSObject
/**
#brief Produces a string containing the largest appropriate units and the new fractional value.
#param sizeInBytes The value to convert in bytes.
This function converts the bytes value to a value in the greatest units that produces a value >= 1 and returns the new value and units as a string.
The magnitude multiplier used is 1024 and the prefixes used are the binary prefixes (ki, Mi, ...).
*/
+ (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes;
/**
#brief Produces a string containing the largest appropriate units and the new fractional value.
#param sizeInBytes The value to convert in bytes.
#param useSiPrefixes Controls what prefix-set is used.
#param useSiMultiplier Controls what magnitude multiplier is used.
This function converts the bytes value to a value in the greatest units that produces a value >= 1 and returns the new value and units as a string.
When useSiPrefixes is true, the prefixes used are the SI unit prefixes (k, M, ...).
When useSiPrefixes is false, the prefixes used are the binary prefixes (ki, Mi, ...).
When useSiMultiplier is true, the magnitude multiplier used is 1000
When useSiMultiplier is false, the magnitude multiplier used is 1024.
*/
+ (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes useSiPrefixes:(BOOL)useSiPrefixes useSiMultiplier:(BOOL)useSiMultiplier;
#end
HumanReadableDataSizeHelper.m
#implementation HumanReadableDataSizeHelper
+ (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes
{
return [self humanReadableSizeFromBytes:sizeInBytes useSiPrefixes:NO useSiMultiplier:NO];
}
+ (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes useSiPrefixes:(BOOL)useSiPrefixes useSiMultiplier:(BOOL)useSiMultiplier
{
NSString *unitSymbol = #"B";
NSInteger multiplier;
NSArray *prefixes;
if (useSiPrefixes)
{
/* SI prefixes
http://en.wikipedia.org/wiki/Kilo-
kilobyte (kB) 10^3
megabyte (MB) 10^6
gigabyte (GB) 10^9
terabyte (TB) 10^12
petabyte (PB) 10^15
exabyte (EB) 10^18
zettabyte (ZB) 10^21
yottabyte (YB) 10^24
*/
prefixes = [NSArray arrayWithObjects: #"", #"k", #"M", #"G", #"T", #"P", #"E", #"Z", #"Y", nil];
}
else
{
/* Binary prefixes
http://en.wikipedia.org/wiki/Binary_prefix
kibibyte (KiB) 2^10 = 1.024 * 10^3
mebibyte (MiB) 2^20 ≈ 1.049 * 10^6
gibibyte (GiB) 2^30 ≈ 1.074 * 10^9
tebibyte (TiB) 2^40 ≈ 1.100 * 10^12
pebibyte (PiB) 2^50 ≈ 1.126 * 10^15
exbibyte (EiB) 2^60 ≈ 1.153 * 10^18
zebibyte (ZiB) 2^70 ≈ 1.181 * 10^21
yobibyte (YiB) 2^80 ≈ 1.209 * 10^24
*/
prefixes = [NSArray arrayWithObjects: #"", #"ki", #"Mi", #"Gi", #"Ti", #"Pi", #"Ei", #"Zi", #"Yi", nil];
}
if (useSiMultiplier)
{
multiplier = 1000;
}
else
{
multiplier = 1024;
}
NSInteger exponent = 0;
double size = [sizeInBytes doubleValue];
while ( (size >= multiplier) && (exponent < [prefixes count]) )
{
size /= multiplier;
exponent++;
}
NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease];
[formatter setMaximumFractionDigits:2];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle]; // Uses localized number formats.
NSString *sizeInUnits = [formatter stringFromNumber:[NSNumber numberWithDouble:size]];
return [NSString stringWithFormat:#"%# %#%#", sizeInUnits, [prefixes objectAtIndex:exponent], unitSymbol];
}
#end
You can use FormatterKit and its TTTUnitOfInformationFormatter class:
https://github.com/mattt/FormatterKit
It is also available through CocoaPods with:
pod 'FormatterKit', '~> 1.1.1'
- (id)transformedValue:(id)value
{
double convertedValue = [value doubleValue];
int multiplyFactor = 0;
NSArray *tokens = #[#"bytes",#"KB",#"MB",#"GB",#"TB"];
while (convertedValue > 1024) {
convertedValue /= 1024;
multiplyFactor++;
}
return [NSString stringWithFormat:#"%4.2f %#",convertedValue, tokens[multiplyFactor]];
}
I know the questions is for Obj C but if anyone looking for a swift version:
public static func fileSizeDisplay(fromBytes:Int) -> String {
let display = ["bytes","KB","MB","GB","TB","PB"]
var value:Double = Double(fromBytes)
var type = 0
while (value > 1024){
value /= 1024
type = type + 1
}
return "\(String(format:"%g", value)) \(display[type])"
}