source: orbit/iOS/Orbit/Orbit/AudioGenerator.m @ 3bf994e

ServoTab_Interfacepyramid
Last change on this file since 3bf994e was 3bf994e, checked in by Steve Castellotti <sc@…>, 7 years ago

AudioGenerator?:

  • comments added for debugging
  • Property mode set to 100644
File size: 12.6 KB
Line 
1//
2//  AudioGenerator.m
3//  orbit
4//
5//  Created by Jonathon Horsman on 19/07/2013.
6//  Copyright (c) 2013 Puzzlebox Productions, LLC. All rights reserved.
7//
8
9#import "AudioGenerator.h"
10
11#define sampleRate 44100
12
13/**
14 * Half periods in the audio code, in seconds.
15 */
16#define longHIGH 0.000829649
17#define longLOW 0.000797027
18#define shortHIGH 0.000412649
19#define shortLOW 0.000378351
20
21@implementation AudioGenerator {
22    float sampleTime;
23        AudioComponentInstance toneUnit;
24   
25    int longHighLength, shortHighLength, longZeroLength, longLowLength, mediumLowLength, shortLowLength, waveBitLength;
26    float *waveLongHigh, *waveShortHigh, *waveLongZero, *waveLongLow, *waveMediumLow, *waveShortLow, *waveBit;
27   
28    int MAX_BUFFER_SIZE;
29}
30
31@synthesize yaw, pitch, throttle;
32
33- (id) init
34{
35    self = [super init];
36    if (self) {
37        sampleTime = 1 / (float)sampleRate;
38       
39        [self prepareStaticArrays];
40    }
41    return self;
42}
43
44- (void) prepareStaticArrays
45{
46   
47    waveLongHigh = [self generateHalfSine:true halfPeriod:longHIGH];
48    longHighLength = [self arraySizeWithHalfPeriod:longHIGH];
49    waveLongLow = [self generateHalfSine:false halfPeriod:longLOW];
50    longLowLength = [self arraySizeWithHalfPeriod:longLOW];
51    waveShortHigh = [self generateHalfSine:true halfPeriod:shortHIGH];
52    shortHighLength = [self arraySizeWithHalfPeriod:shortHIGH];
53    waveShortLow = [self generateHalfSine:false halfPeriod:shortLOW];
54    shortLowLength = [self arraySizeWithHalfPeriod:shortLOW];
55   
56    float mediumLow = 0.0005 - sampleTime;
57    waveMediumLow = [self generateHalfSine:false halfPeriod:mediumLow];
58    mediumLowLength = [self arraySizeWithHalfPeriod:mediumLow];
59   
60   
61    longZeroLength = (int)floor((0.002 + 1 / sampleRate) * sampleRate);
62    waveLongZero = malloc(longZeroLength);
63   
64    [self generateWaveBit];
65}
66
67- (void) generateWaveBit
68{
69    waveBitLength = longHighLength + longLowLength + shortHighLength + shortLowLength;
70    waveBit = malloc(sizeof(waveBit) * waveBitLength);
71    int c = 0;
72    for (int i = 0; i < longHighLength; i++) {
73        waveBit[c++] = waveLongHigh[i];
74    }
75    for (int i = 0; i < longLowLength; i++) {
76        waveBit[c++] = waveLongLow[i];
77    }
78    for (int i = 0; i < shortHighLength; i++) {
79        waveBit[c++] = waveShortHigh[i];
80    }
81    for (int i = 0; i < shortLowLength; i++) {
82        waveBit[c++] = waveShortLow[i];
83    }
84}
85
86
87- (void) stop
88{
89    if (toneUnit != nil) {
90        AudioOutputUnitStop(toneUnit);
91        AudioUnitUninitialize(toneUnit);
92        AudioComponentInstanceDispose(toneUnit);
93        toneUnit = nil;
94    }
95}
96
97- (void) playWithThrottle: (int)t yaw: (int)y pitch: (int)p
98{
99    self.yaw = y;
100    self.pitch = p;
101    self.throttle = t;
102   
103    if (toneUnit == nil) {
104        [self createToneUnit];
105       
106        // Stop changing parameters on the unit
107        OSErr err = AudioUnitInitialize(toneUnit);
108       
109        // Start playback
110        err = AudioOutputUnitStart(toneUnit);
111    }
112}
113
114OSStatus RenderTone(
115                    void *inRefCon,
116                    AudioUnitRenderActionFlags  *ioActionFlags,
117                    const AudioTimeStamp                *inTimeStamp,
118                    UInt32                                              inBusNumber,
119                    UInt32                                              inNumberFrames,
120                    AudioBufferList                     *ioData)
121
122{
123   
124       
125        // This is a mono tone generator so we only need the first buffer
126        const int channel = 0;
127        Float32 *buffer = (Float32 *)ioData->mBuffers[channel].mData;
128   
129    AudioGenerator *generator = (__bridge AudioGenerator *)inRefCon;
130    [generator writeBytesToBuffer:buffer maxSize:inNumberFrames];
131    return noErr;
132}
133
134/**
135 * All this code stolen from http://www.cocoawithlove.com/2010/10/ios-tone-generator-introduction-to.html
136 */
137- (void) createToneUnit
138{
139    // Configure the search parameters to find the default playback output unit
140    // (called the kAudioUnitSubType_RemoteIO on iOS but
141    // kAudioUnitSubType_DefaultOutput on Mac OS X)
142    AudioComponentDescription defaultOutputDescription;
143    defaultOutputDescription.componentType = kAudioUnitType_Output;
144    defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO;
145    defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple;
146    defaultOutputDescription.componentFlags = 0;
147    defaultOutputDescription.componentFlagsMask = 0;
148   
149    // Get the default playback output unit
150    AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription);
151    if (defaultOutput == NULL) {
152        NSLog(@"Can't find default output");
153    }
154   
155    // Create a new unit based on this that we'll use for output
156    OSErr err = AudioComponentInstanceNew(defaultOutput, &toneUnit);
157    if (toneUnit == NULL) {
158        NSLog(@"Error creating unit: %hd", err);
159    }
160   
161    // Set our tone rendering function on the unit
162    AURenderCallbackStruct input;
163    input.inputProc = RenderTone;
164    input.inputProcRefCon = (__bridge void *)(self);
165    err = AudioUnitSetProperty(toneUnit,
166                               kAudioUnitProperty_SetRenderCallback,
167                               kAudioUnitScope_Input,
168                               0,
169                               &input,
170                               sizeof(input));
171    if (err != noErr) {
172        NSLog(@"Error setting callback: %hd", err);
173    }
174   
175    // Set the format to 32 bit, single channel, floating point, linear PCM
176    const int four_bytes_per_float = 4;
177    const int eight_bits_per_byte = 8;
178    AudioStreamBasicDescription streamFormat;
179    streamFormat.mSampleRate = sampleRate;
180    streamFormat.mFormatID = kAudioFormatLinearPCM;
181    streamFormat.mFormatFlags =
182    kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved;
183    streamFormat.mBytesPerPacket = four_bytes_per_float;
184    streamFormat.mFramesPerPacket = 1;
185    streamFormat.mBytesPerFrame = four_bytes_per_float;
186    streamFormat.mChannelsPerFrame = 1;
187    streamFormat.mBitsPerChannel = four_bytes_per_float * eight_bits_per_byte;
188    err = AudioUnitSetProperty (toneUnit,
189                                kAudioUnitProperty_StreamFormat,
190                                kAudioUnitScope_Input,
191                                0,
192                                &streamFormat,
193                                sizeof(AudioStreamBasicDescription));
194    if (err != noErr) {
195        NSLog(@"Error setting stream format: %hd", err);
196    }
197}
198
199// calculate the checksum for the generated code used to generate the WAV array
200- (int) codeChecksum:(int)code
201{
202    int checksum = 0;
203    for (int i = 0; i < 7; i++) {
204        checksum += (code >> 4*i) & 15;
205    }
206    return 16 - (checksum & 15);
207}
208
209// Generate the code used to create the WAV file based on the given throttle, yaw and pitch.
210// Copied from AudioService.java in the Android app (command2code method)
211// throttle: 0~127, nothing will happen if this value is below 30.
212// yaw: 0~127, normally 78 will keep orbit from rotating.
213// pitch: 0~63, normally 31 will stop the top propeller.
214// channel: 1=Channel A, 0=Channel B 2= Channel C, depend on which channel you want to pair to the orbit. You can fly at most 3 orbit in a same room.
215- (int) generateCode
216{
217    int channel = 1;
218    int code = throttle << 21;
219    code += 1048576; // code += 1 << 20 ;
220    code += yaw << 12;
221    code += pitch << 4 ;
222    code += ((channel >> 1) & 1) << 19;
223    code += (channel & 1) << 11;
224    return code + [self codeChecksum:code];
225}
226
227- (void) writeBytesToBuffer:(Float32 *) buffer maxSize:(UInt32) frames
228{
229    MAX_BUFFER_SIZE = sizeof(buffer) * frames;
230    int position = 0;
231    [self writeWaveToBuffer:buffer        at:&position];
232    [self writeInitialWaveToBuffer:buffer at:&position];
233}
234
235- (void) writeInitialWaveToBuffer:(Float32 *) buffer at: (int *) position
236{
237    [self writeWave123To: buffer at: position];
238    [self writeWave123To: buffer at: position];
239    [self writeWave456To: buffer at: position];
240    [self writeWave456To: buffer at: position];
241    [self writeWave456To: buffer at: position];
242}
243
244- (void) writeWave123To:(Float32 *) buffer at:(int *) position
245{
246    [self writeOriginalTo:buffer at: position];
247    [self writeArray:waveMediumLow to:buffer length:mediumLowLength at:position];
248    for (int i = 0; i < 4; i++) {
249        [self writeShortHShortLTo:buffer at:position];
250    }
251    [self writeArray:waveShortHigh to:buffer length:shortHighLength at:position];
252    [self writeMediumLShortHTo:buffer at:position];
253    [self writePauseInSamplesTo:buffer at:position];
254}
255
256- (void) writeWave456To:(Float32 *) buffer at:(int *) position
257{
258    [self writeOriginalTo:buffer at:position];
259    [self writeArray:waveShortLow to:buffer length:shortLowLength at:position];
260    for (int i = 0; i < 4; i++) {
261        [self writeShortHShortLTo:buffer at:position];
262    }
263    [self writeArray:waveShortHigh to:buffer length:shortHighLength at:position];
264    [self writeMediumLShortHTo:buffer at:position];
265    [self writePauseInSamplesTo:buffer at:position];
266}
267
268
269- (void) writeOriginalTo:(Float32 *) buffer at:(int *) position
270{
271    [self writeLongHLongZeroTo: buffer at: position];
272    [self writeLongHLongZeroTo: buffer at: position];
273    [self writeArray:waveLongHigh to:buffer length:longHighLength at:position];
274    [self writeMediumLShortHTo:buffer at:position];
275}
276
277- (void) writeLongHLongZeroTo:(Float32 *) buffer at:(int *) position
278{
279    [self writeArray:waveLongHigh to:buffer length:longHighLength at:position];
280    [self writeArray:waveLongZero to:buffer length:longZeroLength at:position];
281}
282
283- (void) writeMediumLShortHTo:(Float32 *) buffer at:(int *) position
284{
285    [self writeArray:waveMediumLow to:buffer length:mediumLowLength at:position];
286    [self writeArray:waveShortHigh to:buffer length:shortHighLength at:position];
287}
288
289- (void) writeShortHShortLTo:(Float32 *) buffer at:(int *) position
290{
291    [self writeArray:waveShortHigh to:buffer length:shortHighLength at:position];
292    [self writeArray:waveShortLow to:buffer length:shortLowLength at:position];
293}
294
295- (void) writePauseInSamplesTo:(Float32 *) buffer at:(int *) position
296{
297    int length = (int)floor(0.010 * sampleRate); // length of pause to insert
298    float arr[length];
299    [self writeArray:arr to:buffer length:length at:position];
300}
301
302- (void) writeArray:(float *) array to:(Float32 *) buffer length: (int)length at:(int *) position
303{
304    for (int i = 0; i < length; i++) {
305        if (*position < MAX_BUFFER_SIZE) buffer[(*position)++] = array[i];
306    }
307}
308
309- (void) writeWaveToBuffer:(Float32 *) buffer at: (int *) position
310{
311    [self halfSineGen: false halfPeriod: longLOW                   toBuffer:buffer at:position];
312    [self halfSineGen: true  halfPeriod: longHIGH - sampleTime * 2 toBuffer:buffer at:position];
313    [self halfSineGen: false halfPeriod: shortLOW + sampleTime * 2 toBuffer:buffer at:position];
314    [self halfSineGen: true  halfPeriod: longHIGH - sampleTime * 2 toBuffer:buffer at:position]; // duplicate?
315    [self halfSineGen: false halfPeriod: shortLOW + sampleTime * 2 toBuffer:buffer at:position]; // duplicate?
316   
317    int code = [self generateCode];
318    for (int i=0; i < 27; i++) {
319        buffer[(*position)++] = waveBit[((code >> (27 - i)) & 1)];
320    }
321    for (int i = 0; i < longHighLength; i++) {
322        buffer[(*position)++] = waveLongHigh[i];
323    }
324   
325}
326
327/**
328 * Generate half sine signal.
329 * Copied from AudioService.java in the AndroidApp
330 * @param upper, means it's the upper half or lower half or sine wave.
331 * @param halfPeriod: half of the period of sine wave, in seconds
332 * @param toBuffer the buffer (float array) to write to
333 * @param position the position in the array to start writing to
334 */
335- (void) halfSineGen:(BOOL)upper halfPeriod: (double)halfPeriod toBuffer:(Float32 *) buffer at:(int *) position
336{
337   
338   // TODO - testing
339   //halfPeriod =true;
340   
341    int sampleCount = [self arraySizeWithHalfPeriod:halfPeriod];
342    double increment = M_PI/(halfPeriod * sampleRate);
343    double angle = upper ? 0 : M_PI;
344       
345    for (int i = 0; i < sampleCount; i++) {
346        buffer[(*position)++] = sinf(angle);
347        angle += increment;
348    }
349}
350
351- (int) arraySizeWithHalfPeriod: (double)halfPeriod
352{
353    return (int)floor(halfPeriod * sampleRate);
354}
355
356/**
357 * Generate half sine signal.
358 * Copied from AudioService.java in the AndroidApp
359 * @param upper, means it's the upper half or lower half of sine wave.
360 * @param halfPeriod: half of the period of sine wave, in seconds
361 * @return the length of the array
362 */
363- (float *) generateHalfSine:(BOOL)upper halfPeriod: (double)halfPeriod
364{
365    int sampleCount = [self arraySizeWithHalfPeriod:halfPeriod];
366    float *array = malloc(sizeof(*array) * sampleCount);
367    double increment = M_PI/(halfPeriod * sampleRate);
368    double angle = upper ? 0 : M_PI;
369   
370    for (int i = 0; i < sampleCount; i++) {
371        array[i] = sinf(angle);
372        angle += increment;
373    }
374    return array;
375}
376
377@end
Note: See TracBrowser for help on using the repository browser.