2 min read

Major Improvements to Intentional Music Program

Major Improvements to Intentional Music Program

I spent the morning refactoring my Intentional Music Program to move away from Swift Playgrounds and make it a full on console application. It now uses JSON files to define the songs and allows me to enter the name of the song on the command line.

{
    "name": "Arbitrary Escape",
    "duration": 600,
    "tracks": [
        { "name": "A", "period": 30, "affluence": 4 },
        { "name": "B", "period": 30, "inPosition": 20, "outPosition": 100, "affluence": 5 },
        { "name": "C", "period": 15, "inPosition": 100, "affluence": 6 },
        { "name": "D", "period": 15, "inPosition": 50, "chord": [0,3,5], "affluence": 7 },
        { "name": "E", "period": 15, "affluence": 7 },
        { "name": "F", "period": 30, "inPosition": 200, "outPosition": 400, "chord": [0,2,3,5], "affluence": 7 }
    ],
    "seed": [1,3,5,7,12,15],
    "baseNote": 26,
    "algorithm": "b"
}

This allows for a much smoother workflow when creating new songs as I can develop the algorithm separately from the song definition. The way I had it before, all of my code was jumbled up in one big file. It's now divided up into separate files for each struct and major function. I really like how XCode works with files. It has global scope for the functions and structs. I'm sure there are drawbacks to this for larger applications, but it's exactly what I needed for my current project.

The main program is so much cleaner now.

//
//  main.swift
//  IntentionalMusic
//
//  Created by Michael Earls on 3/6/21.
//
import ArgumentParser
import Foundation
import MidiParser

struct IntentionalMusic : ParsableCommand {
    @Option(name: .shortAndLong, help: "The song name.")
    var name: String?
    
    @Argument(help: "The name of JSON definition file for the song")
    var jsonDefinition: String
    
    mutating func run() throws {
        print("Running Intentional Music...")
        
        print("Loading JSON file")
        if let jsonData = readLocalJsonFile(forName: jsonDefinition) {
            
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            
            print("Decoding \(jsonDefinition)")
            var song1: song = try! decoder.decode(song.self, from: jsonData)
            // Use the song name that was passed in, default to name in JSON file
            song1.name = name ?? song1.name
            print("Song Name: \(song1.name)")
            
            let midi: MidiData
            
            // Build the song using the specified algorithm
            switch song1.algorithm
            {
            case Algorithm.a:
                midi = algoA(song: song1)
            case Algorithm.b:
                midi = algoB(song: song1)
            }
            
            print("Writing Song...")
            // write data to file
            do{
                let outFile = //getDocumentsDirectory().appendingPathComponent("\(song1.name).mid")
                    getCurrentDirectory().appendingPathComponent("\(song1.name).mid")
                try midi.writeData(to: outFile)
            }catch{
                print(error)
            }
        }
        else
        {
            print("Could not load JSON file")
        }
    }
}

IntentionalMusic.main()

Now, I simply run the program from the command line:

./IntentionalMusic sadre.json -n NewSong3

This is much better. Now I can focus on the music and spend less time fighting with XCode to make it do what I want.

You can hear the results of this JSON file on my bandcamp page.