{"id":5,"date":"2005-12-03T13:01:59","date_gmt":"2005-12-03T21:01:59","guid":{"rendered":"http:\/\/www.viscerallogic.com\/programming\/blog\/2005\/12\/03\/guitar-chords-to-midi-in-objective-c\/"},"modified":"2006-12-29T11:02:51","modified_gmt":"2006-12-29T19:02:51","slug":"guitar-chords-to-midi-in-objective-c","status":"publish","type":"post","link":"https:\/\/www.viscerallogic.com\/programming\/blog\/guitar-chords-to-midi-in-objective-c\/","title":{"rendered":"Guitar Chords to MIDI in Objective-C"},"content":{"rendered":"<p>This tutorial will show you how to write a program that creates MIDI files of guitar chords by processing text files. Although I wrote this in Objective-C and the GUI is built using Cocoa, the MIDI algorithms could easily be applied to another language or platform. Essentially, this program scans a text file to look for a chord or some other symbol that it recognizes. Then it translates that into MIDI format code, and continues parsing until it reaches the end of the file.<!--more--><br \/>\n<P><br \/>\nStart by downloading the project here: <a href=\"http:\/\/www.viscerallogic.com\/cocoaMIDI\/CocoaMIDI.tar.gz\">CocoaMIDI.tar.gz (32 KB)<\/a>. This contains the Project Builder project, with the MainMenu.nib resource and all the files already created, but no implementation for the MIDI.m class file, which is what we will do now. Here is what the MIDI.m file looks like now:<\/p>\n<blockquote><p><code><\/p>\n<pre>#import \"MIDI.h\"\r\n\r\n@implementation MIDI\r\n\r\n@end\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nThe first thing we&#8217;ll do is add an NSDictionary where we will store the chords. Add the following line to MIDI.m before the <i>@implementation&#8230;<\/i> part:<\/p>\n<blockquote><p><code><\/p>\n<pre>NSMutableDictionary *chords;\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nWe will read the chords from a file and store them in here, for easy access when building the MIDI file. We do this in the <code>+ (void) initialize<\/code> function, which gets called for every class before the first time any other call is made. Add this function between the <i>@implementation<\/i> and <i>@end<\/i> lines:<\/p>\n<blockquote><p><code><\/p>\n<pre>+ (void) initialize{\r\n\tint i;\r\n\r\n\tNSString *chordFile = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]\r\n\t\tstringByAppendingString:@\"\/Chords.txt\"];\r\n\tNSString *chordDefs = [NSString stringWithContentsOfFile:chordFile];\r\n\tNSArray *eachChord = [chordDefs componentsSeparatedByString:@\"\\n\"];\t\/\/ each line\r\n\tchords = [[NSMutableDictionary alloc] initWithCapacity:7];\r\n\tfor( i = 0; i < [eachChord count]; i++ ){\r\n\t\tNSArray *temp, *keys;\r\n\r\n\t\tif( [[eachChord objectAtIndex:i] isEqualToString:@\"\"] )\r\n\t\t\tcontinue;\t\/\/ don't crash if Chords.txt has extra returns\r\n\r\n\t\ttemp = [[eachChord objectAtIndex:i] componentsSeparatedByString:@\"\\t\"];\r\n\t\tkeys = [[temp objectAtIndex:1] componentsSeparatedByString:@\"|\"];\r\n\t\t[chords setObject:keys forKey:[temp objectAtIndex:0]];\r\n\t}\r\n}\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nThis function first calculates the complete path to the file in which the chord definitions are contained by finding the complete application path <i>(...\/CocoaMIDI.app)<\/i>, chopping off the <i>.app<\/i> folder, and adding <i>Chords.txt<\/i>.<\/p>\n<p>\nNext we use the NSString constructor <code>stringWithContentsOfFile:<\/code> to read all the chords into one long string in memory. CocoaMIDI assumes each chord name with its associated MIDI key numbers is on a separate line, so we break the big string up into an array of strings, making a new string each time there is a line break.<\/p>\n<p>\nNow we initialize the <i>NSMutableDictionary<\/i> we defined in the first step. In the for-loop, we go through each line of the original <i>Chords.txt<\/i> file. First we check to see if it is empty, which happens if someone has put some extra blank lines in the file. Assuming it is not empty, we break it up into two parts, which are separated by a tab: the first part is the chord name, the second consists of the MIDI key numbers, which are further separated by the \"|\" character, and need to be split up and assigned as an array to the <i>keys<\/i> variable. Then we add these keys to the <i>chords<\/i> dictionary with the name of that chord, which is object 0 of the <i>temp<\/i> array.<\/p>\n<p>\nNext we have the <code>- init...<\/code> function:<\/p>\n<blockquote><p><code><\/p>\n<pre>- initWithIn:(NSString *)inFile out:(NSString *)outFile{\r\n\tself = [super init];\r\n\tlastChord = nil;\r\n\tDELTA = 256;\r\n\tdeltaDivide = 4;\r\n\tinData = [[NSFileHandle fileHandleForReadingAtPath:inFile] availableData];\r\n\tinLength = [inData length];\r\n\tinData = [inData bytes];\r\n\tpos = 0;\r\n\tself->outFile = outFile;\r\n\ttrack = [[NSMutableData alloc] init];\r\n\tcontents = [[NSMutableData alloc] init];\r\n\tvolume = 100;\r\n\t\r\n\treturn self;\r\n}<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nThis initializer takes two strings as parameters: the first is the path to the file we want to translate into MIDI, the second is the path to the MIDI file we want to create. We then initialize all the necessary variables.<\/p>\n<p>\nThis will generate several compile warnings because it is not in the best technical form. If you look at the <i>MIDI.h<\/i> header file, you will notice that the instance variable <i>inData<\/i> is declared to be of type <i>char *<\/i>, and here we are assigning it first an <i>NSData *<\/i> object, and then to a <i>const void *<\/i> which is a pointer to the actual data from the file we want to translate. In between, we make an Objective-C call on the <i>inData<\/i> variable to get the length of the data read. This works because the Objective-C runtime looks at what kind of object a pointer thinks it is to determine the correct call to make, rather than what kind of pointer you have declared it to be.<\/p>\n<p>\nThe <i>track<\/i> instance variable will hold all the note information, and the <i>contents<\/i> instance variable will hold a MIDI file header followed by the <i>track<\/i> data.<\/p>\n<p>\nIf you look at the <i>Controller.m<\/i> file, you will see that it creates and initializes a <i>MIDI<\/i> object, and then calls the <code>- (void) writeFile<\/code> member function. This is where all the action takes place:<\/p>\n<blockquote><p><code><\/p>\n<pre>- (void) writeFile{\r\n\tchar header[18] = { 0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06,\r\n\t\t\t\t\t\t0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x4D, 0x54,\r\n\t\t\t\t\t\t0x72, 0x6B };\t\t\/\/ MThd header\r\n\tchar end[3] = { 0xFF, 0x2F, 0x00 };\t\t\t\t\/\/ end of track marker\r\n\tchar guitar[3] = { 0x00, 0xC0, 0x19 };\t\t\t\t\/\/ change instrument to guitar\r\n\tint length;\r\n\r\n\t[contents appendBytes:header length:18];\r\n\t[self buildTrack];\r\n\tlength = [track length]+3;\r\n\t[contents appendBytes:&length length:4];\r\n\t[contents appendBytes:guitar length:3];\r\n\t[contents appendData:track];\r\n\t[contents appendBytes:end length:3];\r\n\t[[NSFileManager defaultManager] createFileAtPath:outFile contents:contents attributes:nil];\r\n}\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nThese ugly-looking <i>char<\/i> arrays are the MIDI headers. For more information about these, see the references at the end of this tutorial. We start by appending the one called <i>header<\/i> to the <i>contents<\/i> data. Next we make a call to <code>- (void) buildTrack<\/code>, which handles the translation of the input file and stores the resulting MIDI commands in the the <i>track<\/i> NSData variable.<\/p>\n<p>\nWe have to do it this way because the next thing to go in the MIDI file to format it properly is the length of the track. So we append the length of the track we've build + 3, because we want to change the instrument to guitar at the very beginning, from the default of piano, which takes an additional three bytes. So we append the length, then we append the <i>guitar<\/i> array, which is the MIDI command to switch the instrument to a guitar, and then we append the <i>track<\/i> data. The last thing we need to add is the MIDI marker that tells it the end of the track has been reached. I'm not sure why it needs this, since we have already told it how long the track is, but what the heck. So we append <i>end<\/i>. The complete MIDI file is now in memory, so we write it out to the file the user specified.<\/p>\n<p>\nThe <code>- (void) buildTrack<\/code> function is where the translation logic is. Consequently, it is the longest function in the CocoaMIDI application:<\/p>\n<blockquote><p><code><\/p>\n<pre>- (void) buildTrack{\r\n\tchar c;\r\n\tint delta = 0;\r\n\r\n\twhile( pos < inLength ){\r\n\t\tc = inData[pos++];\r\n\t\tif( 'A' <= c &#038;&#038; c <= 'G' ){\t\t\/\/ a chord\r\n\t\t\tNSString *chord = [NSString stringWithFormat:@\"%c\",c];\r\n\t\t\tint i;\r\n\t\t\tfor( i = 0; i < 13; i++ ){\t\/\/ start at 7 characters (unicode, 2 bytes)...\r\n\t\t\t\tif( pos == inLength )\r\n\t\t\t\t\tbreak;\r\n\t\t\t\tchord = [chord stringByAppendingFormat:@\"%c\",inData[pos++]];\r\n\t\t\t}\r\n\t\t\twhile( [chords objectForKey:chord] == nil ){\t\/\/ find longest match\r\n\t\t\t\tchord = [chord substringToIndex:[chord length]-1];\r\n\t\t\t\tpos -= 2;\t\t\/\/ unicode again\r\n\t\t\t}\r\n\t\t\t[self closeLastChord:delta];\r\n\t\t\t[self writeChord:chord atDelta:delta];\r\n\t\t\tdelta = DELTA\/deltaDivide;\r\n\t\t} else if( c == 'x' || c == 'X' ){\t\/\/ kill the chord\r\n\t\t\t[self closeLastChord:delta];\r\n\t\t\tlastChord = nil;\r\n\t\t} else if( c == '-' ){\t\t\t\/\/ a continuation\r\n\t\t\tdelta += DELTA\/deltaDivide;\r\n\t\t} else if( c == '(' ){\t\t\t\/\/ time change\r\n\t\t\tNSString *num = @\"\";\r\n\t\t\twhile( (c = inData[pos++]) != ')' )\r\n\t\t\t\tnum = [num stringByAppendingFormat:@\"%c\",c];\r\n\t\t\tdeltaDivide = [num intValue];\r\n\t\t} else if( c == '^' ){\t\t\t\/\/ step offset\r\n\t\t\tNSString *num = @\"\";\r\n\t\t\twhile( (c = inData[pos++]) != '^' )\r\n\t\t\t\tnum = [num stringByAppendingFormat:@\"%c\",c];\r\n\t\t\tstepOffset = [num intValue];\r\n\t\t} else if( c == ':' ){\r\n\t\t\tNSString *num = @\"\";\r\n\t\t\twhile( (c = inData[pos++]) != ':' )\r\n\t\t\t\tnum = [num stringByAppendingFormat:@\"%c\",c];\r\n\t\t\tvolume = [num intValue];\r\n\t\t}\r\n\t}\r\n\t[self closeLastChord:delta];\r\n\t[self writeVarTime:0];\r\n}\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nWe use a while loop to read all the data that we have stored in memory from the input file. The <i>pos<\/i> variable marks where we currently are in memory, and <i>inLength<\/i> gives the end of the data.<\/p>\n<p>\nEach time we start the new loop by reading in the current character <i>c<\/i> and advancing the position marker. Next we check to see what kind of information we can expect to follow, based on what character this is. If it is between <i>'A'<\/i> and <i>'G'<\/i>, then it will be a chord. An <i>'x'<\/i> signifies that we want to silence the last-played chord, a <i>'-'<\/i> means extend the last played chord. The next three else-if clauses read in an integer enclosed by the given characters and apply it to some variable. An integer between parentheses means change the timing, an integer between carets means change the key, and an integer between colons means change the volume.<\/p>\n<p>\nTo match a chord, we start by building a string that is up to 7 characters long, or however much data is left in the <i>inData<\/i> variable. This is what that first for-loop does, grabbing the next character and appending it to the string. Then we start checking that string against the names of chords defined in our <i>chords<\/i> dictionary. We want to match the longest string possible, which is why we start at 7 characters and work our way down. This way, we will match \"C#m7\" instead of \"C#m\" or \"C#\" or \"C\". Using 7 characters as the maximum length was a purely arbitrary decision, but seems a reasonable length. Since we know that the first character is somewhere from <i>'A' - 'G'<\/i>, we are guaranteed to have at least a one-character chord match, assuming the user hasn't edited the <i>Chords.txt<\/i> file to remove those definitions! Then we stop the previously played chord (that function handles the case of there being no previous chord) and play the chord we just read.<\/p>\n<p>\nYou have seen the <i>delta, DELTA,<\/i> and <i>deltaDivide<\/i> variables several times now. <i>DELTA<\/i> defines the number of \"pulses\" in each whole note, i.e., you can play notes as short as 256th notes. <i>deltaDivide<\/i> sets what kind of timing we are currently using. For example, if <i>deltaDivide<\/i> is 4, then we are using 4th notes, etc. The expression <i>DELTA\/deltaDivide<\/i> tells how many pulses are in each note for the current timing scheme. <i>delta<\/i> simply tells how long has passed since the last event. Every time we play a new chord, we start delta over from then, and every time we hit a chord extension \"-\", we add that amount again. Changing the timing using \"(n)\" changes how fast you want the notes to be played by adjusting how many pulses are added to <i>delta<\/i>.<\/p>\n<p>\nNext we need the <code>- (void) writeChord:...<\/code> function:<\/p>\n<blockquote><p><code><\/p>\n<pre>- (void) writeChord:(NSString *)c atDelta:(int)delta{\r\n\tint i;\r\n\tNSArray *keys;\r\n\r\n\tkeys = [chords objectForKey:c];\r\n\tlastChord = [c retain];\r\n\tlastOffset = stepOffset;\r\n\tlastVolume = volume;\r\n\t[self writeVarTime:delta];\r\n\t[self appendNote:[[keys objectAtIndex:0] intValue]+stepOffset state:YES];\r\n\tfor( i = 1; i < [keys count]; i++ ){\r\n\t\t[self writeVarTime:0];\r\n\t\t[self appendNote:[[keys objectAtIndex:i] intValue]+stepOffset state:YES];\r\n\t}\r\n}\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nThis function takes a chord name and a delta time and inserts the proper data into the MIDI data <i>track<\/i> variable. It first finds the array of keys to be played from the <i>chords<\/i> dictionary, then it sets the <i>lastChord<\/i> variable to this chord name (this is need to close it off when we want to play another chord), and sets the last step offset and volumes to their respective current values. Next we write the offset delta we have been given followed by the first key in the chord. Since all the following keys are played at the same time, they are all preceded by a delta of <i>0<\/i>. Additionally, we add <i>stepOffset<\/i> to each MIDI key value in case the user has desired to change the key by using the \"^step^\" command in the input file.<\/p>\n<p>\nThe <code>- (void) closeLastChord:...<\/code> function is very similar:<\/p>\n<blockquote><p><code><\/p>\n<pre>- (void) closeLastChord:(int)delta{\r\n\tint i;\r\n\tNSArray *keys;\r\n\r\n\tif( lastChord == NULL )\r\n\t\treturn;\r\n\r\n\t[lastChord autorelease];\r\n\tkeys = [chords objectForKey:lastChord];\r\n\t[self writeVarTime:delta];\r\n\t[self appendNote:[[keys objectAtIndex:0] intValue]+lastOffset state:NO];\r\n\tfor( i = 1; i < [keys count]; i++ ){\r\n\t\t[self writeVarTime:0];\r\n\t\t[self appendNote:[[keys objectAtIndex:i] intValue]+lastOffset state:NO];\r\n\t}\r\n}\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nWe start by making sure there actually is a currently playing chord to close, since this function is called before <i>every<\/i> chord is played. Then we write the current delta time and the first note, this time with the state being <i>NO<\/i>, or off. As in <i>writeChord<\/i>, we then iterate through the rest of the notes, playing each with a delta of 0.<\/p>\n<p>\nBy now you are probably wondering where the actual MIDI code goes. Well, most of it goes here in the <code>- (void) appendNote:...<\/code> function:<\/p>\n<blockquote><p><code><\/p>\n<pre>- (void) appendNote:(int)note state:(BOOL)on{\r\n\tchar c[3];\r\n\r\n\tif( on ){\r\n\t\tc[0] = 0x90;\r\n\t\tc[2] = volume;\r\n\t} else {\r\n\t\tc[0] = 0x80;\r\n\t\tc[2] = lastVolume;\r\n\t}\r\n\tc[1] = note;\r\n\t\r\n\t[track appendBytes:&c length:3];\r\n}\r\n<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nThis function constructs a 3-char array and fills it with the appropriate values to make the MIDI note on or off command. The first character is <i>0x90<\/i> for on, or <i>0x80<\/i> for off. Then we put the volume in the third byte (current volume for on, last volume for off), the note value in the second byte, and append the whole thing to the the MIDI <i>track<\/i> data.<\/p>\n<p>\nThe last function you need to add is the <code>- (void) writeVarTime:...<\/code> function:<\/p>\n<blockquote><p><code><\/p>\n<pre>- (void) writeVarTime:(int)value{\r\n\tchar c[2];\r\n\tif( value < 128 ){\r\n\t\tc[0] = value;\r\n\t\t[track appendBytes:&#038;c length:1];\r\n\t} else {\r\n\t\tc[0] = value\/128 | 0x80;\r\n\t\tc[1] = value % 128;\r\n\t\t[track appendBytes:&#038;c length:2];\r\n\t}\r\n}<\/pre>\n<p><\/code><\/p><\/blockquote>\n<p>\nBasically, if the delta in pulses since the last event (<i>value<\/i>) is less than 128, we put it in one byte. If it's larger, we have to put it into two bytes. Technically, the MIDI file format supports up to four bytes for the time delta, but I haven't bothered to put support in for that here, since that would be more than 16 seconds. But if you want, you could change that. See the MIDI references below for more information.<\/p>\n<p>\nYou have now completed the CocoaMIDI program. As you can see, it is fairly easy to add new commands to the <i>buildTrack<\/i> function. Try out some of the included tune definition files, and make your own. Have fun!<\/p>\n<hr width=\"75%\" align=\"center\">\n<p>\nHere is the complete source in a PB project <a href=\"http:\/\/www.viscerallogic.com\/cocoaMIDI\/CocoaMIDISource.tar.gz\">CocoaMIDISource.tar.gz<\/a><\/p>\n<p>\nUseful MIDI resources<\/p>\n<ul>\n<li><a href=\"http:\/\/jedi.ks.uiuc.edu\/~johns\/links\/music\/midispec.htm\">MIDI Specification<\/a> talks about different MIDI commands\n<li><a href=\"http:\/\/www.borg.com\/~jglatt\/tech\/midifile.htm\">MIDI File Format<\/a> describes headers and the delta time format\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>This tutorial will show you how to write a program that creates MIDI files of guitar chords by processing text files. Although I wrote this in Objective-C and the GUI is built using Cocoa, the MIDI algorithms could easily be applied to another language or platform. Essentially, this program scans a text file to look [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","jetpack_publicize_message":"","jetpack_is_tweetstorm":false},"categories":[2,3,5],"tags":[],"jetpack_featured_media_url":"","jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p9npkn-5","jetpack_likes_enabled":true,"jetpack-related-posts":[{"id":46,"url":"https:\/\/www.viscerallogic.com\/programming\/blog\/basic-interpreter-part-ii\/","url_meta":{"origin":5,"position":0},"title":"BASIC Interpreter, Part II","date":"September 7, 2018","format":false,"excerpt":"This is a continuation of Part I. In Part II, we will add functionality to store and run BASIC programs, although still limited to the PRINT statement. We will also be adding a LIST statement to print out the currently stored program. We will be adding a number of new\u2026","rel":"","context":"In &quot;BASIC&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":39,"url":"https:\/\/www.viscerallogic.com\/programming\/blog\/basic-interpreter-part-i\/","url_meta":{"origin":5,"position":1},"title":"BASIC Interpreter, Part I","date":"September 7, 2018","format":false,"excerpt":"This is the first of a multi-post tutorial on using flex and Bison to write a programming language interpreter, in this case, BASIC. We'll be using the 1964 BASIC manual from Dartmouth as the starting point language reference. All code is available at this GitHub repository: https:\/\/github.com\/VisceralLogic\/basic. The interpreter will\u2026","rel":"","context":"In &quot;BASIC&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":111,"url":"https:\/\/www.viscerallogic.com\/programming\/blog\/basic-interpreter-part-v\/","url_meta":{"origin":5,"position":2},"title":"BASIC Interpreter, Part V","date":"September 7, 2018","format":false,"excerpt":"This is a continuation of Part IV. In Part V we will add support for parentheses in mathematical expressions, add the GOTO and END statements, and implement the remaining program storage statements: CATALOG, SCRATCH, and RENAME. Now that we have a framework for supporting numerical operations, adding parentheses is not\u2026","rel":"","context":"In &quot;BASIC&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":90,"url":"https:\/\/www.viscerallogic.com\/programming\/blog\/basic-interpreter-part-iv\/","url_meta":{"origin":5,"position":3},"title":"BASIC Interpreter, Part IV","date":"September 7, 2018","format":false,"excerpt":"This is a continuation of Part III. This time we will add some mathematical operators, the ability to save and load programs, and support numerical variables. To enable mathematical operations, we will create a subclass of DoubleExpression that takes two DoubleExpressions as inputs, as well as a character code signifying\u2026","rel":"","context":"In &quot;BASIC&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":131,"url":"https:\/\/www.viscerallogic.com\/programming\/blog\/basic-interpreter-part-vi\/","url_meta":{"origin":5,"position":4},"title":"BASIC Interpreter, Part VI","date":"September 7, 2018","format":false,"excerpt":"This is a continuation of Part V. In the previous section, we added support for the GOTO statement. Since we don't yet have any control logic, that is not very useful, but it does lay the infrastructure for one of the features we will add this time: IF-THEN. We will\u2026","rel":"","context":"In &quot;BASIC&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":72,"url":"https:\/\/www.viscerallogic.com\/programming\/blog\/basic-interpeter-part-iii\/","url_meta":{"origin":5,"position":5},"title":"BASIC Interpeter, Part III","date":"September 7, 2018","format":false,"excerpt":"This is a continuation of Part II. In this part, we will add support for numerical as well as string expressions, support multiple expression in a PRINT statement, and add functionality for deleting program lines. Let's start with the Basic class header file, basic.h. All we need to do here\u2026","rel":"","context":"In &quot;BASIC&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/posts\/5"}],"collection":[{"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/comments?post=5"}],"version-history":[{"count":0,"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/posts\/5\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/media?parent=5"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/categories?post=5"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.viscerallogic.com\/programming\/blog\/wp-json\/wp\/v2\/tags?post=5"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}