public static String print(Program program) throws PrintException {
        StringBuilder sb = new StringBuilder();
        sb.append(program.getProgramName().getDisplayString()).append("\n");
        sb.append("  Guitar Style: ").append(printGuitarStyles(program)).append("\n");
        sb.append("  Effect Type: ").append(printEffectTypes(program)).append("\n");
        sb.append("  Application Type: ").append(printApplicationTypes(program)).append("\n");
        sb.append("  Effect Routing:\n");
        for (String line : RoutingPrinter.print(program).split("\n")) {
            sb.append("    ").append(line).append("\n");
        }
        printProgram(sb, "Effect 1", program.getEffect1(), program.getEffectsStatus().getEffect1On(), program.getEffect1ToePatch());
        printProgram(sb, "Effect 2", program.getEffect2(), program.getEffectsStatus().getEffect2On(), program.getEffect2ToePatch());
        printProgram(sb, "Chorus", program.getChorus(), program.getEffectsStatus().getChorusOn(), program.getChorusToePatch());
        printProgram(sb, "Delay", program.getDelay(), program.getEffectsStatus().getDelayOn(), program.getDelayToePatch());
        printProgram(sb, "Reverb", program.getReverb(), program.getEffectsStatus().getReverbOn(), program.getReverbToePatch());
        printProgram(sb, "EQ", program.getEq(), program.getEffectsStatus().getEqOn(), program.getEqToePatch());
        printProgram(sb, "Gain", program.getGain(), program.getEffectsStatus().getGainOn(), program.getGainToePatch());
        sb.append("  Softrow:\n");
        for (int i = 0; i < 10; i++) {
            sb.append(printSoftRow(program, i));
        }
        sb.append("  Patching:\n");
        for (int i = 1; i <= 5; i++) {
            sb.append(printPatch(program, program.getPatch(i), i));
        }
        sb.append("  Controllers:\n");
        sb.append("    Knob:\n").append(printKnob(program.getKnob()));
        sb.append("    LFO 1:\n").append(printLfo(program.getLfo1()));
        sb.append("    LFO 2:\n").append(printLfo(program.getLfo2()));
        sb.append("    Random:\n").append(printRandom(program.getRandom()));
        sb.append("    A/B:\n").append(printAb(program.getAb()));
        sb.append("    Envelope:\n").append(printEnvelopeGenerator(program.getEnvelopeGenerator()));
        sb.append("  Mix:\n");
        sb.append("    Send:\n").append(printSendMix(program.getSendMix()));
        sb.append("    Post:\n").append(printPostMix(program.getPostMix()));
        if (program.getEffect1() != null) {
            sb.append("    FX1:\n");
            sb.append("  ").append(printParameter(program.getEffect1().mix));
            sb.append("  ").append(printParameter(program.getEffect1().level));
        }
        if (program.getEffect2() != null) {
            sb.append("    FX2:\n");
            sb.append("  ").append(printParameter(program.getEffect2().mix));
            sb.append("  ").append(printParameter(program.getEffect2().level));
        }
        if (program.getChorus() != null) {
            sb.append("    Chorus:\n");
            sb.append("  ").append(printParameter(program.getChorus().mix));
            sb.append("  ").append(printParameter(program.getChorus().level));
        }
        if (program.getDelay() != null) {
            sb.append("    Delay:\n");
            sb.append("  ").append(printParameter(program.getDelay().mix));
            sb.append("  ").append(printParameter(program.getDelay().level));
        }
        if (program.getReverb() != null) {
            sb.append("    Reverb:\n");
            sb.append("  ").append(printParameter(program.getReverb().mix));
            sb.append("  ").append(printParameter(program.getReverb().level));
        }
        if (program.getEq() != null) {
            sb.append("    Eq:\n");
            sb.append("  ").append(printParameter(program.getEq().mix));
            sb.append("  ").append(printParameter(program.getEq().level));
        }
        sb.append("  Tempo:\n").append(printTempo(program.getTempo()));
        sb.append("  Speaker Sim: ").append(program.isSpeakerSimulatorEnable().getDisplayString()).append("\n");
        sb.append(printParameter(program.getSpeakerSimulatorCabinet()));
        sb.append("  Noise Gate:\n").append(printNoiseGate(program.getNoiseGate()));
        return sb.toString().trim();
    }