/* Copyright (c) 2021 Alex Diener This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Alex Diener alex@ludobloom.com */ #include "audiosynth/MusicInstrumentConfiguration.h" #include "audiosynth/MusicSequence.h" #include "utilities/IOUtilities.h" #include #define stemobject_implementation MusicSequence stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_end(); MusicSequence * MusicSequence_create(float beatsPerMinute, unsigned int defaultMeterSize, unsigned int defaultMeterBasicNote) { stemobject_create_implementation(init, beatsPerMinute, defaultMeterSize, defaultMeterBasicNote) } static void initEmpty(MusicSequence * self) { call_super(init, self); self->patternCount = 0; self->patterns = NULL; self->patternInstanceCount = 0; self->patternInstances = NULL; self->instrumentCount = 0; self->instruments = NULL; self->trackCount = 0; self->tracks = NULL; } bool MusicSequence_init(MusicSequence * self, float beatsPerMinute, unsigned int defaultMeterSize, unsigned int defaultMeterBasicNote) { initEmpty(self); self->beatsPerMinute = beatsPerMinute; self->defaultMeterSize = defaultMeterSize; self->defaultMeterBasicNote = defaultMeterBasicNote; return true; } void MusicSequence_dispose(MusicSequence * self) { for (unsigned int patternIndex = 0; patternIndex < self->patternCount; patternIndex++) { call_virtual(dispose, self->patterns[patternIndex]); } free(self->patterns); free(self->patternInstances); for (unsigned int instrumentIndex = 0; instrumentIndex < self->instrumentCount; instrumentIndex++) { if (self->instruments[instrumentIndex].instrument != NULL) { call_virtual(dispose, self->instruments[instrumentIndex].instrument); free(self->instruments[instrumentIndex].name); } } free(self->instruments); free(self->tracks); call_super_virtual(dispose, self); } MusicSequence * MusicSequence_copy(MusicSequence * self) { stemobject_copy_implementation(initCopy) } void MusicSequence_initCopy(MusicSequence * self, MusicSequence * original) { call_super(init, self); self->beatsPerMinute = original->beatsPerMinute; self->defaultMeterSize = original->defaultMeterSize; self->defaultMeterBasicNote = original->defaultMeterBasicNote; self->patternCount = original->patternCount; if (self->patternCount == 0) { self->patterns = NULL; } else { self->patterns = malloc(self->patternCount * sizeof(*self->patterns)); for (unsigned int patternIndex = 0; patternIndex < self->patternCount; patternIndex++) { self->patterns[patternIndex] = MusicPattern_copy(original->patterns[patternIndex]); } } self->patternInstanceCount = original->patternInstanceCount; if (self->patternInstanceCount == 0) { self->patternInstances = NULL; } else { self->patternInstances = malloc(self->patternInstanceCount * sizeof(*self->patternInstances)); for (unsigned int patternInstanceIndex = 0; patternInstanceIndex < self->patternInstanceCount; patternInstanceIndex++) { self->patternInstances[patternInstanceIndex] = original->patternInstances[patternInstanceIndex]; } } self->instrumentCount = original->instrumentCount; if (self->instrumentCount == 0) { self->instruments = NULL; } else { self->instruments = malloc(self->instrumentCount * sizeof(*self->instruments)); for (unsigned int instrumentIndex = 0; instrumentIndex < self->instrumentCount; instrumentIndex++) { self->instruments[instrumentIndex].instrumentID = original->instruments[instrumentIndex].instrumentID; self->instruments[instrumentIndex].name = strdup(original->instruments[instrumentIndex].name); self->instruments[instrumentIndex].instrument = call_virtual(copy, original->instruments[instrumentIndex].instrument); } } self->trackCount = original->trackCount; self->tracks = memdup(original->tracks, original->trackCount * sizeof(*original->tracks)); } unsigned int MusicSequence_addPattern(MusicSequence * self, MusicPattern * pattern) { self->patterns = realloc(self->patterns, (self->patternCount + 1) * sizeof(*self->patterns)); self->patterns[self->patternCount] = pattern; return self->patternCount++; } void MusicSequence_deletePattern(MusicSequence * self, unsigned int patternIndex) { if (patternIndex < self->patternCount) { unsigned int offset = 0; for (unsigned int patternInstanceIndex = 0; patternInstanceIndex < self->patternInstanceCount; patternInstanceIndex++) { self->patternInstances[patternInstanceIndex] = self->patternInstances[patternInstanceIndex + offset]; if (self->patternInstances[patternInstanceIndex].patternIndex == patternIndex) { self->patternInstanceCount--; patternInstanceIndex--; offset++; } else if (self->patternInstances[patternInstanceIndex].patternIndex > patternIndex) { self->patternInstances[patternInstanceIndex].patternIndex--; } } call_virtual(dispose, self->patterns[patternIndex]); self->patternCount--; for (; patternIndex < self->patternCount; patternIndex++) { self->patterns[patternIndex] = self->patterns[patternIndex + 1]; } } } void MusicSequence_addPatternInstance(MusicSequence * self, unsigned int measureOffset, unsigned int trackIndex, unsigned int patternIndex, unsigned int repeatCount) { self->patternInstances = realloc(self->patternInstances, (self->patternInstanceCount + 1) * sizeof(*self->patternInstances)); self->patternInstances[self->patternInstanceCount].measureOffset = measureOffset; self->patternInstances[self->patternInstanceCount].trackIndex = trackIndex; self->patternInstances[self->patternInstanceCount].patternIndex = patternIndex; self->patternInstances[self->patternInstanceCount].repeatCount = repeatCount; self->patternInstanceCount++; } void MusicSequence_deletePatternInstance(MusicSequence * self, unsigned int patternInstanceIndex) { if (patternInstanceIndex < self->patternInstanceCount) { self->patternInstanceCount--; for (; patternInstanceIndex < self->patternInstanceCount; patternInstanceIndex++) { self->patternInstances[patternInstanceIndex] = self->patternInstances[patternInstanceIndex + 1]; } } } void MusicSequence_setInstrument(MusicSequence * self, unsigned int instrumentID, const char * name, compat_type(MusicInstrument *) instrument) { for (unsigned int instrumentIndex = 0; instrumentIndex < self->instrumentCount; instrumentIndex++) { if (self->instruments[instrumentIndex].instrumentID == instrumentID) { call_virtual(dispose, self->instruments[instrumentIndex].instrument); self->instruments[instrumentIndex].instrument = instrument; free(self->instruments[instrumentIndex].name); self->instruments[instrumentIndex].name = strdup(name); return; } } self->instruments = realloc(self->instruments, (self->instrumentCount + 1) * sizeof(*self->instruments)); self->instruments[self->instrumentCount].instrumentID = instrumentID; self->instruments[self->instrumentCount].name = strdup(name); self->instruments[self->instrumentCount].instrument = instrument; self->instrumentCount++; } void MusicSequence_removeInstrument(MusicSequence * self, unsigned int instrumentID) { for (unsigned int instrumentIndex = 0; instrumentIndex < self->instrumentCount; instrumentIndex++) { if (self->instruments[instrumentIndex].instrumentID == instrumentID) { call_virtual(dispose, self->instruments[instrumentIndex].instrument); self->instrumentCount--; for (; instrumentIndex < self->instrumentCount; instrumentIndex++) { self->instruments[instrumentIndex] = self->instruments[instrumentIndex + 1]; } break; } } } MusicInstrument * MusicSequence_getInstrumentWithID(MusicSequence * self, unsigned int instrumentID) { for (unsigned int instrumentIndex = 0; instrumentIndex < self->instrumentCount; instrumentIndex++) { if (self->instruments[instrumentIndex].instrumentID == instrumentID) { return self->instruments[instrumentIndex].instrument; } } return NULL; } MusicSequence_track MusicSequence_getTrackParameters(MusicSequence * self, unsigned int trackIndex) { if (trackIndex < self->trackCount) { return self->tracks[trackIndex]; } return MusicSequence_track_default; } void MusicSequence_setTrackParameters(MusicSequence * self, unsigned int trackIndex, MusicSequence_track trackParameters) { if (trackIndex >= self->trackCount) { self->tracks = realloc(self->tracks, (trackIndex + 1) * sizeof(*self->tracks)); for (unsigned int trackIndex2 = self->trackCount; trackIndex2 < trackIndex; trackIndex2++) { self->tracks[trackIndex2] = MusicSequence_track_default; } self->trackCount = trackIndex + 1; } self->tracks[trackIndex] = trackParameters; } AudioSequence * MusicSequence_createAudioSequence(MusicSequence * self) { unsigned int itemCount = 0; struct AudioSequence_item * items; for (unsigned int patternInstanceIndex = 0; patternInstanceIndex < self->patternInstanceCount; patternInstanceIndex++) { MusicPattern * pattern = self->patterns[self->patternInstances[patternInstanceIndex].patternIndex]; unsigned int patternNoteCount = 0; for (unsigned int measureIndex = 0; measureIndex < pattern->measureCount; measureIndex++) { patternNoteCount += pattern->measures[measureIndex].noteCount; } itemCount += patternNoteCount * (self->patternInstances[patternInstanceIndex].repeatCount + 1); } items = malloc(itemCount * sizeof(*items)); itemCount = 0; float measureLength = 60.0f / self->beatsPerMinute * 4; unsigned int soloTrackIndex = UINT_MAX; for (unsigned int trackIndex = 0; trackIndex < self->trackCount; trackIndex++) { if (self->tracks[trackIndex].solo) { soloTrackIndex = trackIndex; break; } } for (unsigned int patternInstanceIndex = 0; patternInstanceIndex < self->patternInstanceCount; patternInstanceIndex++) { MusicPattern * pattern = self->patterns[self->patternInstances[patternInstanceIndex].patternIndex]; if (soloTrackIndex != UINT_MAX && self->patternInstances[patternInstanceIndex].trackIndex != soloTrackIndex) { continue; } MusicSequence_track track = MusicSequence_getTrackParameters(self, self->patternInstances[patternInstanceIndex].trackIndex); if (track.mute || track.amplitude == 0.0f) { continue; } float leftMultiplier = track.amplitude, rightMultiplier = track.amplitude; if (track.invert) { rightMultiplier = -rightMultiplier; } if (track.pan < 0.0f) { rightMultiplier *= 1.0f + fmaxf(track.pan, -1.0f); } else if (track.pan > 0.0f) { leftMultiplier *= 1.0f - fminf(track.pan, 1.0f); } for (unsigned int repeatIndex = 0; repeatIndex <= self->patternInstances[patternInstanceIndex].repeatCount; repeatIndex++) { for (unsigned int measureIndex = 0; measureIndex < pattern->measureCount; measureIndex++) { for (unsigned int noteIndex = 0; noteIndex < pattern->measures[measureIndex].noteCount; noteIndex++) { struct MusicPattern_note note = pattern->measures[measureIndex].notes[noteIndex]; MusicInstrument * instrument = MusicSequence_getInstrumentWithID(self, note.instrumentID); if (instrument == NULL) { continue; } items[itemCount].parameters = call_virtual(createAudioSamplerParameters, instrument, ¬e); if (items[itemCount].parameters == NULL) { continue; } items[itemCount].parametersOwned = true; items[itemCount].time = measureLength * (self->patternInstances[patternInstanceIndex].measureOffset + measureIndex + repeatIndex * self->patterns[self->patternInstances[patternInstanceIndex].patternIndex]->measureCount) + measureLength * note.beat.numerator / note.beat.denominator; items[itemCount].leftMultiplier = leftMultiplier; items[itemCount].rightMultiplier = rightMultiplier; itemCount++; } } } } return AudioSequence_create(items, itemCount, false, true); } AudioSequence * MusicSequence_createAudioSequenceForPattern(MusicSequence * self, MusicPattern * pattern) { unsigned int itemCount = 0; struct AudioSequence_item * items; for (unsigned int measureIndex = 0; measureIndex < pattern->measureCount; measureIndex++) { itemCount += pattern->measures[measureIndex].noteCount; } items = malloc(itemCount * sizeof(*items)); itemCount = 0; float measureLength = 60.0f / self->beatsPerMinute * 4; for (unsigned int measureIndex = 0; measureIndex < pattern->measureCount; measureIndex++) { for (unsigned int noteIndex = 0; noteIndex < pattern->measures[measureIndex].noteCount; noteIndex++) { struct MusicPattern_note note = pattern->measures[measureIndex].notes[noteIndex]; MusicInstrument * instrument = MusicSequence_getInstrumentWithID(self, note.instrumentID); if (instrument == NULL) { continue; } items[itemCount].parameters = call_virtual(createAudioSamplerParameters, instrument, ¬e); if (items[itemCount].parameters == NULL) { continue; } items[itemCount].parametersOwned = true; items[itemCount].time = measureLength * measureIndex + measureLength * note.beat.numerator / note.beat.denominator; items[itemCount].leftMultiplier = items[itemCount].rightMultiplier = 1.0f; itemCount++; } } return AudioSequence_create(items, itemCount, false, true); } double MusicSequence_getLoopDuration(MusicSequence * self) { unsigned int measureMax = 0; for (unsigned int patternInstanceIndex = 0; patternInstanceIndex < self->patternInstanceCount; patternInstanceIndex++) { MusicPattern * pattern = self->patterns[self->patternInstances[patternInstanceIndex].patternIndex]; unsigned int patternInstanceMeasureMax = pattern->measureCount * (self->patternInstances[patternInstanceIndex].repeatCount + 1) + self->patternInstances[patternInstanceIndex].measureOffset; if (patternInstanceMeasureMax > measureMax) { measureMax = patternInstanceMeasureMax; } } return measureMax * 60.0f / self->beatsPerMinute * 4; } MusicSequence * MusicSequence_deserialize(compat_type(DeserializationContext *) deserializationContext) { stemobject_deserialize_implementation(initFromSerializedData) } static void loadSerializedDataMinimal(MusicSequence * self, DeserializationContext * context, uint16_t formatVersion) { const uint16_t MUSIC_INSTRUMENT_FORMAT_VERSIONS[MusicSequence_formatVersion + 1] = {0, 1, 1}; self->beatsPerMinute = call_virtual(readFloat, context, "bpm"); self->defaultMeterSize = call_virtual(readUInt16, context, "default_meter_size"); self->defaultMeterBasicNote = call_virtual(readUInt16, context, "default_meter_basic_note"); unsigned int patternCount = call_virtual(beginArray, context, "patterns"); if (patternCount > MusicSequence_patternCountMax) { return; } self->patternCount = patternCount; self->patterns = malloc(patternCount * sizeof(*self->patterns)); for (unsigned int patternIndex = 0; patternIndex < patternCount; patternIndex++) { call_virtual(beginStructure, context, NULL); unsigned int meterSize = call_virtual(readUInt16, context, "meter_size"); unsigned int meterBasicNote = call_virtual(readUInt16, context, "meter_basic_note"); unsigned int measureCount = call_virtual(beginArray, context, "measures"); MusicPattern * pattern = MusicPattern_create(measureCount, meterSize, meterBasicNote); for (unsigned int measureIndex = 0; measureIndex < pattern->measureCount; measureIndex++) { pattern->measures[measureIndex].noteCount = call_virtual(beginArray, context, NULL); pattern->measures[measureIndex].notes = malloc(pattern->measures[measureIndex].noteCount * sizeof(*pattern->measures[measureIndex].notes)); for (unsigned int noteIndex = 0; noteIndex < pattern->measures[measureIndex].noteCount; noteIndex++) { call_virtual(beginStructure, context, NULL); pattern->measures[measureIndex].notes[noteIndex].note = call_virtual(readInt16, context, "note"); pattern->measures[measureIndex].notes[noteIndex].beat.numerator = call_virtual(readUInt16, context, "beat"); pattern->measures[measureIndex].notes[noteIndex].beat.denominator = call_virtual(readUInt16, context, "beat_division"); pattern->measures[measureIndex].notes[noteIndex].pressure = call_virtual(readFloat, context, "pressure"); pattern->measures[measureIndex].notes[noteIndex].holdDuration = call_virtual(readFloat, context, "hold_duration"); pattern->measures[measureIndex].notes[noteIndex].flags = call_virtual(readUInt32, context, "flags"); pattern->measures[measureIndex].notes[noteIndex].instrumentID = call_virtual(readUInt16, context, "instrument"); call_virtual(endStructure, context); } call_virtual(endArray, context); } call_virtual(endArray, context); self->patterns[patternIndex] = pattern; call_virtual(endStructure, context); } call_virtual(endArray, context); unsigned int patternInstanceCount = call_virtual(beginArray, context, "pattern_instances"); if (patternInstanceCount > MusicSequence_patternInstanceCountMax) { return; } self->patternInstanceCount = patternInstanceCount; self->patternInstances = malloc(patternInstanceCount * sizeof(*self->patternInstances)); for (unsigned int patternInstanceIndex = 0; patternInstanceIndex < patternInstanceCount; patternInstanceIndex++) { call_virtual(beginStructure, context, NULL); self->patternInstances[patternInstanceIndex].measureOffset = call_virtual(readUInt16, context, "measure_offset"); self->patternInstances[patternInstanceIndex].trackIndex = call_virtual(readUInt8, context, "track_index"); self->patternInstances[patternInstanceIndex].patternIndex = call_virtual(readUInt16, context, "pattern_index"); self->patternInstances[patternInstanceIndex].repeatCount = call_virtual(readUInt16, context, "repeat_count"); call_virtual(endStructure, context); } call_virtual(endArray, context); unsigned int instrumentCount = call_virtual(beginArray, context, "instruments"); if (instrumentCount > MusicSequence_instrumentCountMax) { return; } self->instrumentCount = instrumentCount; self->instruments = malloc(instrumentCount * sizeof(*self->instruments)); for (unsigned int instrumentIndex = 0; instrumentIndex < instrumentCount; instrumentIndex++) { call_virtual(beginStructure, context, NULL); self->instruments[instrumentIndex].instrumentID = call_virtual(readUInt16, context, "id"); self->instruments[instrumentIndex].name = strdup_nullSafe(call_virtual(readString, context, "name")); MusicInstrumentConfiguration * configuration = MusicInstrumentConfiguration_deserializeMinimal(context, MUSIC_INSTRUMENT_FORMAT_VERSIONS[formatVersion]); if (configuration == NULL) { self->instruments[instrumentIndex].instrument = NULL; } else { self->instruments[instrumentIndex].instrument = MusicInstrumentConfiguration_createMusicInstrument(configuration); MusicInstrumentConfiguration_dispose(configuration); } call_virtual(endStructure, context); } call_virtual(endArray, context); if (formatVersion > 1) { unsigned int trackCount = call_virtual(beginArray, context, "tracks"); if (trackCount > MusicSequence_trackCountMax) { return; } self->trackCount = trackCount; self->tracks = malloc(trackCount * sizeof(*self->tracks)); for (unsigned int trackIndex = 0; trackIndex < trackCount; trackIndex++) { call_virtual(beginStructure, context, NULL); self->tracks[trackIndex].mute = call_virtual(readBoolean, context, "mute"); self->tracks[trackIndex].solo = call_virtual(readBoolean, context, "solo"); self->tracks[trackIndex].invert = call_virtual(readBoolean, context, "invert"); self->tracks[trackIndex].amplitude = call_virtual(readFloat, context, "amplitude"); self->tracks[trackIndex].pan = call_virtual(readFloat, context, "pan"); call_virtual(endStructure, context); } call_virtual(endArray, context); } } bool MusicSequence_initFromSerializedData(MusicSequence * self, compat_type(DeserializationContext *) deserializationContext) { DeserializationContext * context = deserializationContext; call_virtual(beginStructure, context, MusicSequence_formatType); const char * formatType = call_virtual(readString, context, "format_type"); if (formatType == NULL || strcmp(formatType, MusicSequence_formatType)) { return false; } uint16_t formatVersion = call_virtual(readUInt16, context, "format_version"); if (formatVersion > MusicSequence_formatVersion) { return false; } initEmpty(self); loadSerializedDataMinimal(self, context, formatVersion); call_virtual(endStructure, context); if (context->status != SERIALIZATION_ERROR_OK) { call_virtual(dispose, self); return false; } return true; } bool MusicSequence_initFromSerializedDataMinimal(MusicSequence * self, compat_type(DeserializationContext *) deserializationContext, uint16_t formatVersion) { DeserializationContext * context = deserializationContext; initEmpty(self); loadSerializedDataMinimal(self, deserializationContext, formatVersion); if (context->status != SERIALIZATION_ERROR_OK) { call_virtual(dispose, self); return false; } return true; } void MusicSequence_serialize(MusicSequence * self, compat_type(SerializationContext *) serializationContext) { SerializationContext * context = serializationContext; call_virtual(beginStructure, context, MusicSequence_formatType); call_virtual(writeString, context, "format_type", MusicSequence_formatType); call_virtual(writeUInt16, context, "format_version", MusicSequence_formatVersion); MusicSequence_serializeMinimal(self, context); call_virtual(endStructure, context); } void MusicSequence_serializeMinimal(MusicSequence * self, compat_type(SerializationContext *) serializationContext) { SerializationContext * context = serializationContext; call_virtual(writeFloat, context, "bpm", self->beatsPerMinute); call_virtual(writeUInt16, context, "default_meter_size", self->defaultMeterSize); call_virtual(writeUInt16, context, "default_meter_basic_note", self->defaultMeterBasicNote); call_virtual(beginArray, context, "patterns"); for (unsigned int patternIndex = 0; patternIndex < self->patternCount; patternIndex++) { MusicPattern * pattern = self->patterns[patternIndex]; call_virtual(beginStructure, context, NULL); call_virtual(writeUInt16, context, "meter_size", pattern->meterSize); call_virtual(writeUInt16, context, "meter_basic_note", pattern->meterBasicNote); call_virtual(beginArray, context, "measures"); for (unsigned int measureIndex = 0; measureIndex < pattern->measureCount; measureIndex++) { call_virtual(beginArray, context, NULL); for (unsigned int noteIndex = 0; noteIndex < pattern->measures[measureIndex].noteCount; noteIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeInt16, context, "note", pattern->measures[measureIndex].notes[noteIndex].note); call_virtual(writeUInt16, context, "beat", pattern->measures[measureIndex].notes[noteIndex].beat.numerator); call_virtual(writeUInt16, context, "beat_division", pattern->measures[measureIndex].notes[noteIndex].beat.denominator); call_virtual(writeFloat, context, "pressure", pattern->measures[measureIndex].notes[noteIndex].pressure); call_virtual(writeFloat, context, "hold_duration", pattern->measures[measureIndex].notes[noteIndex].holdDuration); call_virtual(writeUInt32, context, "flags", pattern->measures[measureIndex].notes[noteIndex].flags); call_virtual(writeUInt16, context, "instrument", pattern->measures[measureIndex].notes[noteIndex].instrumentID); call_virtual(endStructure, context); } call_virtual(endArray, context); } call_virtual(endArray, context); call_virtual(endStructure, context); } call_virtual(endArray, context); call_virtual(beginArray, context, "pattern_instances"); for (unsigned int patternInstanceIndex = 0; patternInstanceIndex < self->patternInstanceCount; patternInstanceIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeUInt16, context, "measure_offset", self->patternInstances[patternInstanceIndex].measureOffset); call_virtual(writeUInt8, context, "track_index", self->patternInstances[patternInstanceIndex].trackIndex); call_virtual(writeUInt16, context, "pattern_index", self->patternInstances[patternInstanceIndex].patternIndex); call_virtual(writeUInt16, context, "repeat_count", self->patternInstances[patternInstanceIndex].repeatCount); call_virtual(endStructure, context); } call_virtual(endArray, context); call_virtual(beginArray, context, "instruments"); for (unsigned int instrumentIndex = 0; instrumentIndex < self->instrumentCount; instrumentIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeUInt16, context, "id", self->instruments[instrumentIndex].instrumentID); call_virtual(writeString, context, "name", self->instruments[instrumentIndex].name); MusicInstrumentConfiguration * configuration = MusicInstrumentConfiguration_createFromMusicInstrument(self->instruments[instrumentIndex].instrument); MusicInstrumentConfiguration_serializeMinimal(configuration, context); MusicInstrumentConfiguration_dispose(configuration); call_virtual(endStructure, context); } call_virtual(endArray, context); call_virtual(beginArray, context, "tracks"); for (unsigned int trackIndex = 0; trackIndex < self->trackCount; trackIndex++) { call_virtual(beginStructure, context, NULL); call_virtual(writeBoolean, context, "mute", self->tracks[trackIndex].mute); call_virtual(writeBoolean, context, "solo", self->tracks[trackIndex].solo); call_virtual(writeBoolean, context, "invert", self->tracks[trackIndex].invert); call_virtual(writeFloat, context, "amplitude", self->tracks[trackIndex].amplitude); call_virtual(writeFloat, context, "pan", self->tracks[trackIndex].pan); call_virtual(endStructure, context); } call_virtual(endArray, context); }