diff --git a/.gitignore b/.gitignore index babecd0..73cc6cd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ Desktop.ini Thumbs.db *.deb *.swp +*.mus diff --git a/lib/symbol_table.rb b/lib/symbol_table.rb index c4f1aab..4807217 100644 --- a/lib/symbol_table.rb +++ b/lib/symbol_table.rb @@ -30,8 +30,8 @@ module Assembler6502 name = name.to_sym scope = current_scope if scope.has_key?(name) - path_string = generate_scope_path(path_ary) - fail(InvalidScope, "Scope: #{path_string} already exists") + #path_string = generate_scope_path(path_ary) + fail(InvalidScope, "Scope: #{name} already exists") end scope[name] = {} @scope_stack.push(name) diff --git a/sound_engine.asm b/sound_engine.asm new file mode 100644 index 0000000..5257bc1 --- /dev/null +++ b/sound_engine.asm @@ -0,0 +1,199 @@ +;------------------------------------------------------------------------------ +; An NES sound engine that understands the binary stream outputted from my +; MIDI converter :) +;;;; +; Create an iNES header +.ines {"prog": 1, "char": 0, "mapper": 0, "mirror": 0} + + +;;;; +; Include all the symbols in the nes library +.inc + + +;;;; +; Let's put a data structure to control the sound engine in the zero page +.org $0000 +.scope sound_engine + .space stream_read_ptr_lo 1 + .space stream_read_ptr_hi 1 + .space stream_write_ptr_lo 1 + .space stream_write_ptr_hi 1 + .space delta 1 +. + + +;;;; +; Open the prog section bank 0 +.segment prog 0 + + +;;;; +; Setup the interrupt vectors +.org $FFFA +.dw vblank +.dw main +.dw irq + + +;;;; +; Here is our code entry point, which we'll call main. +.org $C000 +.scope main + ; Disable interrupts and decimal flag + sei + cld + + ; Wait for 2 vblanks + wait_vb1: + lda nes.ppu.status + bpl wait_vb1 + wait_vb2: + lda nes.ppu.status + bpl wait_vb2 + + ; Now we want to initialize the hardware to a known state + lda #%00 + ldx #$00 + clear_segments: + sta $0, x + sta $100, x + sta $200, x + sta $300, x + sta $400, x + sta $500, x + sta $600, x + sta $700, x + inx + bne clear_segments + + ; Reset the stack pointer + ldx #$FF + txs + + ; Disable all graphics and vblank nmi + lda #$00 + sta nes.ppu.control + sta nes.ppu.mask + + ; Initialize sound engine structure + ; To read from $D000, and to write to $40** + lda #$D0 + sta sound_engine.stream_read_ptr_hi + lda #$00 + sta sound_engine.stream_read_ptr_lo + sta sound_engine.stream_write_ptr_lo + + ; Make the first delta happen immediately + lda #$01 + sta sound_engine.delta + + lda #$40 + sta sound_engine.stream_write_ptr_hi + + jsr init_sound + + ; Resume interrupts and NMI and loop here forever + lda #%10000000 + sta nes.ppu.control + cli + forever: + jmp forever +. + + +;;;; +; Initialize the APU to enable Pulse1 +; Bitfield: ---D NT21 +.scope init_sound + lda #$00 + sta nes.apu.pulse1.control + sta nes.apu.pulse1.ramp_control + sta nes.apu.pulse1.ft + sta nes.apu.pulse1.ct + lda #$01 + sta nes.apu.channel_enable + rts +. + + +;;;; +; VBlank reads our music buffer +.scope vblank + ; Backup our registers + pha + txa + pha + tya + pha + + jsr sound_engine_driver + + ; Restore the registers + pla + tay + pla + tax + pla + rti +. + +;;;; +; Sound driver that updates the APU registers +; via a stream of APU write commands +.scope sound_engine_driver + dec sound_engine.delta + bne done + + read_event: + ; Load the new delta from the stream + ldy #$00 + lda (sound_engine.stream_read_ptr_lo), Y + sta sound_engine.delta + + ; Read pulse1 control register value + ldy #$01 + lda (sound_engine.stream_read_ptr_lo), Y + sta nes.apu.pulse1.control + + ; Read the value for pulse1.ft + ldy #$02 + lda (sound_engine.stream_read_ptr_lo), Y + sta nes.apu.pulse1.ft + + + ; Read the value for pulse1.ct + ldy #$03 + lda (sound_engine.stream_read_ptr_lo), Y + sta nes.apu.pulse1.ct + + ; Advance the 16-bit stream pointer by number of bytes read + lda sound_engine.stream_read_ptr_lo + clc + adc #$04 + sta sound_engine.stream_read_ptr_lo + bcc done + inc sound_engine.stream_read_ptr_hi + + ; If the very next event is 0 delta away, do it now too + ; Read the value for pulse1.ct + ldy #$04 + lda (sound_engine.stream_read_ptr_lo), Y + beq read_event + + done: + rts +. + + +;;;; +; IRQ Does nothing +.scope irq + rti +. + +;;;; +; Include the music buffer stream +.org $D000 +music_buffer: +.incbin "data.mus" diff --git a/utils/midi/Makefile b/utils/midi/Makefile new file mode 100644 index 0000000..e30bd7b --- /dev/null +++ b/utils/midi/Makefile @@ -0,0 +1,3 @@ + +build: convert + clang++ source/*.cpp -I include -o convert diff --git a/utils/midi/c_scale.mid b/utils/midi/c_scale.mid new file mode 100644 index 0000000..004129e Binary files /dev/null and b/utils/midi/c_scale.mid differ diff --git a/utils/midi/convert b/utils/midi/convert new file mode 100755 index 0000000..33feb1b Binary files /dev/null and b/utils/midi/convert differ diff --git a/utils/midi/guitar.mid b/utils/midi/guitar.mid new file mode 100644 index 0000000..e6e0ebe Binary files /dev/null and b/utils/midi/guitar.mid differ diff --git a/utils/midi/include/event.h b/utils/midi/include/event.h new file mode 100644 index 0000000..9474735 --- /dev/null +++ b/utils/midi/include/event.h @@ -0,0 +1,93 @@ +#ifndef MIDI_EVENT_H +#define MIDI_EVENT_H + +#include +#include +#include "helpers.h" + +namespace Midi { + +enum MidiMessageType {Midi = 0, MidiNoteOff = 0x8, MidiNoteOn, MidiPolyAftertouch, + MidiControlChange, MidiProgramChange, MidiChannelAftertouch, + MidiPitchWheel, MidiMetaEvent = 0xFF, MidiSysex1 = 0xF0, MidiSysex2 = 0xF7}; + + +enum MidiMetaType {MetaSequenceNumber = 0x0, MetaTextEvent = 0x1, MetaCopyright = 0x2, + MetaTrackName = 0x3, MetaInstrumentName = 0x4, MetaLyricText = 0x5, + MetaMarkerText = 0x6, MetaCuePoint = 0x7, MetaChannelPrefixAssignment = 0x20, + MetaEndOfTrack = 0x2F, MetaTempoSetting = 0x51, MetaSMPTEOffset = 0x54, + MetaTimeSignature = 0x58, MetaKeySignature = 0x59, MetaSequenceSpecific = 0x7F}; + +/*** + * Spec: + * MidiTrackEvent = + | | + * MidiEvent = + + + * MetaEvent = 0xFF + + + + * SysexEvent = 0xF0 + + 0xF7 -or- + * SysexEvent = 0xF7 + 0xF7 + ***/ + +class Event { + private: + unsigned int m_delta; + unsigned int m_size; + unsigned int m_data_size; + unsigned char m_status; + unsigned char m_meta_type; + unsigned char m_parameter1; + unsigned char m_parameter2; + void *m_data; + + public: + Event(); + Event(int delta, int status, int parameter1, int parameter2); + void init_midi(int delta, int status, int parameter1, int parameter2); + void init_from_file(FILE *fp, unsigned char last_status); + ~Event(); + + // Some easy inline functions + static bool is_status_byte(unsigned char byte) { return ((byte & 0x80) >> 7) == 1; } + unsigned int delta() const { return m_delta; } + unsigned char status() const { return m_status; } + unsigned int bytes_read() const { return m_size; } + unsigned char parameter1() const { return m_parameter1; } + unsigned char parameter2() const { return m_parameter2; } + int message_type() const { return (m_status &0xF0) >> 4; } + int channel() const { return (m_status &0xF); } + void *sysex_data() const { return m_data; } + + + void print_yaml(){ + printf(" - :delta: 0x%X\n", m_delta); + printf(" :status: 0x%X\n", m_status); + + switch(m_status){ + case 0xFF: + printf(" :meta_type: 0x%X\n", m_meta_type); + printf(" :meta_data_size: 0x%X\n", m_data_size); + break; + case 0xF0: + case 0xF7: + printf(" :sysex_data_size: %d\n", m_data_size); + break; + default: + printf(" :parameter1: 0x%X\n", m_parameter1); + printf(" :parameter2: 0x%X\n", m_parameter2); + } + /* Printing out the actual data as a string confuses the YAML parser + * So let's just not do that. + if(m_data){ + printf(" :data: \""); + + for(int i = 0; i < m_data_size; i++){ + printf("%c", ((unsigned char*)m_data)[i]); + } + printf("\"\n"); + } + */ + } +}; + +} + +#endif diff --git a/utils/midi/include/file.h b/utils/midi/include/file.h new file mode 100644 index 0000000..7be5a33 --- /dev/null +++ b/utils/midi/include/file.h @@ -0,0 +1,57 @@ +#ifndef MIDI_FILE_H +#define MIDI_FILE_H + +#include +#include "track.h" + +namespace Midi { + +/*** + * Spec: + * MidiFile = + [+ ...] + * + * midi_header_t = "MThd" + + + + + * + ***/ + +typedef struct midi_header_t { + char cookie[4]; + unsigned int size; + unsigned short format; + unsigned short track_count; + unsigned short ticks_per_quarter_note; +} __attribute__((packed)) midi_header_t; + + +class File { + private: + midi_header_t m_header; + + public: + File(void); + ~File(void); + std::vector m_tracks; + void init_from_file(const char *filename); + + void print_yaml(){ + printf("---\n"); + printf(":midi_file:\n"); + printf(" :header: Mthd\n"); + printf(" :size: %u\n", m_header.size); + printf(" :format: %u\n", m_header.format); + printf(" :track_count: %u\n", m_header.track_count); + printf(" :ticks_per_quarter_note: %u\n", m_header.ticks_per_quarter_note); + printf(" :tracks:\n"); + + for(int i = 0; i < m_tracks.size(); i++){ + if(i == 0){ + m_tracks[i]->print_yaml(); + }else{ + m_tracks[i]->print_yaml(); + } + } + } +}; + +} +#endif diff --git a/utils/midi/include/helpers.h b/utils/midi/include/helpers.h new file mode 100644 index 0000000..7c5da2b --- /dev/null +++ b/utils/midi/include/helpers.h @@ -0,0 +1,14 @@ +#ifndef MIDI_HELPERS_H +#define MIDI_HELPERS_H + +#include +#include + +namespace Midi { + +unsigned int read_variable_length(FILE *fp, unsigned int *value_size); +short swap_endian_16(short big_endian); +int swap_endian_32(int big_endian); + +} +#endif diff --git a/utils/midi/include/track.h b/utils/midi/include/track.h new file mode 100644 index 0000000..210ba49 --- /dev/null +++ b/utils/midi/include/track.h @@ -0,0 +1,45 @@ +#ifndef MIDI_TRACK_H +#define MIDI_TRACK_H + +#include "event.h" +#include + +namespace Midi { + +/*** + * Spec: + * MidiTrack = + [+ ...] + * midi_track_header_t = "MTrk" + + * + ***/ + +typedef struct midi_track_header_t { + char cookie[4]; + unsigned int size; +} __attribute__((packed)) midi_track_header_t; + + +class Track { + private: + unsigned int m_total_size; + midi_track_header_t m_header; + + public: + Track(void); + ~Track(void); + void init_from_file(FILE *fp); + std::vector m_events; + + void print_yaml(){ + printf(" - :header: MTrk\n"); + printf(" :total_size: %u\n", m_header.size); + printf(" :events:\n"); + + for(int i = 0; i < m_events.size(); i++){ + m_events[i]->print_yaml(); + } + } +}; + +} +#endif diff --git a/utils/midi/lil_melody.mid b/utils/midi/lil_melody.mid new file mode 100644 index 0000000..5334f29 Binary files /dev/null and b/utils/midi/lil_melody.mid differ diff --git a/utils/midi/midi_to_nes.rb b/utils/midi/midi_to_nes.rb new file mode 100755 index 0000000..ecc3f2c --- /dev/null +++ b/utils/midi/midi_to_nes.rb @@ -0,0 +1,200 @@ +#!/usr/bin/env ruby + +require 'yaml' + +class MidiToNES + + #### Custom Exceptions + class MidiFormatNotSupported < StandardError; end + + #### Some Constants + NoteOff = 0x8 + NoteOn = 0x9 + + #### A440 Tuning, and NES CPU speed in hz + Tuning = 440.0 + CPU = 1789773.0 + + + #### LSB Address registers of the APU, MSB is always 0x40 + Pulse1Control = 0x00 + Pulse1FT = 0x2 + Pulse1CT = 0x3 + + + #### + ## Initialize from a yaml file + def self.init_from_file(filename, bpm) + self.new(File.read(filename), bpm) + end + + + #### + ## Initialize with a yaml string + def initialize(yaml_string, bpm) + @bpm = bpm.to_f + @midi_data = YAML.load(yaml_string)[:midi_file] + unless @midi_data[:format].zero? + fail(MidiFormatNotSupported, "Currently only supports format 0 Midi Files") + end + @ticks_per_quarter_note = @midi_data[:ticks_per_quarter_note] + end + + + #### + ## Write to binary file + def write_binary(filename) + binary = convert + File.open(filename, 'wb') do |fp| + fp.write(binary) + end + end + + + #### + ## For now assume one track + def convert + tick_count = 1 + events = [] + + track = @midi_data[:tracks].first + track[:events].each do |event| + + delta, status, note, velocity = event.values_at(:delta, :status, :parameter1, :parameter2) + + ## The status byte contains both the Midi message type, and channel. + type = (status & 0b11110000) >> 4 + channel = status & 0b00001111 + + ## We only care about note on and off, and only care about channel 0 for now. + next unless type == NoteOn || type == NoteOff + next unless channel.zero? + + ## Update the total time + tick_count += delta + + ## Ok this is a note either turning on or off + if type == NoteOff || velocity.zero? + event = {:start => tick_count, :note => note, :velocity => 0} + events << event + else + event = {:start => tick_count, :note => note, :velocity => velocity} + events << event + end + end + + ## Finally sort event list by start time + events.sort! do |a, b| + a[:start] <=> b[:start] + end + + ## Now convert these events to a bytestream for our NES sound engine + events_to_byte_stream(events) + end + + + #### + ## This converts a list of note events into a byte stream for updating NES APU registers + def events_to_byte_stream(events) + last_tick = 1 + byte_stream = [] + + events.each do |event| + ## Work out the delta again + delta = event[:start] - last_tick + byte_stream << midi_tick_to_vblank(delta) # Delta + byte_stream << pulse_control_value(event) # Value + if event[:velocity].zero? + byte_stream << 0 # Off with 0 frequency timer + byte_stream << 0 + else + byte_stream << pulse_ft_value(event) # Value + byte_stream << pulse_ct_value(event) # Value + end + last_tick += delta + end + byte_stream.pack('C*') + end + + + #### + ## Given an event, produce a value for register nes.apu.pulse1.control + ## DDLC VVVV + ## Duty (D), envelope loop / length counter halt (L), constant volume (C), volume/envelope (V) + def pulse_control_value(event) + ## Start with 50% duty cycle, length counter halt is on + ## Constant volume is On, and volume is determined by bit-reducing the event velocity to 4-bit + value = 0b10110000 + + four_bit_max = (2**4 - 1) + seven_bit_max = (2**7 - 1) + + volume_float = event[:velocity] / seven_bit_max.to_f + volume_4_bit = (volume_float * four_bit_max).round & 0b00001111 + + value | volume_4_bit + end + + + #### + ## Given an event, produce a value for register nes.apu.pulse1.ft + ## TTTT TTTT + ## This is the low byte of the timer, the higher few bits being in pulse1.ct + def pulse_ft_value(event) + midi_note_to_nes_timer(event[:note]) & 0xff + end + + + #### + ## Given an event, produce a value for register nes.apu.pulse1.ct + ## LLLL LTTT + ## This has the higher 3 bits of the timer, and L is the length counter. + ## For now let's just use duration as the length counter. + def pulse_ct_value(event) + + ## We will grab the high 3 bits of the 11-bit timer value now + timer_high_3bit = midi_note_to_nes_timer(event[:note]) & 0b11100000000 + timer_high_3bit >> 8 + end + + + #### + ## Midi note to NES timer + def midi_note_to_nes_timer(midi_note) + frequency = Tuning * 2**((midi_note - 69) / 12.0) + timer = (CPU / (16 * frequency)) - 1 + timer.round + end + + + #### + ## Convert a MIDI tick delta to an NES vblank delta. + def midi_tick_to_vblank(midi_tick) + quarter_note_in_seconds = 60 / @bpm + vblanks_per_quarter_note = quarter_note_in_seconds / (1/60.0) + tick_normalized = midi_tick / @ticks_per_quarter_note.to_f + vblanks = tick_normalized * vblanks_per_quarter_note + vblanks.round + end + +end + +if __FILE__ == $0 + unless ARGV.size == 2 + STDERR.puts("Usage #{$0} ") + exit(1) + end + + bpm, midi_file = ARGV + + ## Run the midi file through my converter written in C++ + IO.popen("./convert #{midi_file}") do |io| + midi_to_nes = MidiToNES.new(io.read, bpm.to_i) + midi_to_nes.write_binary('../../data.mus') + end + +end + + + + diff --git a/utils/midi/source/convert.cpp b/utils/midi/source/convert.cpp new file mode 100644 index 0000000..92a4ef8 --- /dev/null +++ b/utils/midi/source/convert.cpp @@ -0,0 +1,16 @@ +#include +#include "file.h" + +int main(int argc, char **argv){ + + if(argc < 1){ + printf("Need a midi file argument\n"); + exit(1); + } + + Midi::File midi_file; + midi_file.init_from_file(argv[1]); + midi_file.print_yaml(); + + return 0; +} diff --git a/utils/midi/source/event.cpp b/utils/midi/source/event.cpp new file mode 100644 index 0000000..bd575a5 --- /dev/null +++ b/utils/midi/source/event.cpp @@ -0,0 +1,96 @@ +#include "event.h" + +namespace Midi { + +//// +// Default constructor +Event::Event(void) : + m_delta(0), m_size(0), + m_data_size(0), m_status(0), + m_meta_type(0), m_parameter1(0), + m_parameter2(0), m_data(NULL) { } + + +//// +// Constructor to set specific fields +Event::Event(int delta, int status, int parameter1, int parameter2) : + m_delta(delta), m_size(4), + m_data_size(0), m_status(status), + m_meta_type(0), m_parameter1(parameter1), + m_parameter2(parameter2), m_data(NULL) { } + + +//// +// Use a file pointer to initialize +void Event::init_from_file(FILE *fp, unsigned char last_status){ + fpos_t saved_position; + unsigned int value_size; + // All types of events are preceeded by a uintvar delta time + m_delta = read_variable_length(fp, &value_size); + m_size += value_size; + + // All types then have a status byte, unless they are reusing + // the previous status, get ready to rewind if this is the case. + fgetpos(fp, &saved_position); + fread(&m_status, sizeof(unsigned char), 1, fp); + m_size++; + + if(!is_status_byte(m_status)){ + // This is not a status byte, so it must be reusing the previous one, Rewind + m_status = last_status; + fsetpos(fp, &saved_position); + m_size--; + } + + switch(m_status){ + case 0xFF: // Meta + // This will have a meta sub type + fread(&m_meta_type, sizeof(unsigned char), 1, fp); + m_size++; + // Now a variable size for data + m_data_size = read_variable_length(fp, &value_size); + m_size += value_size; + // Finally, read the meta data + m_data = malloc(sizeof(unsigned char) * m_data_size); + fread(m_data, sizeof(unsigned char), m_data_size, fp); + m_size += m_data_size; + break; + case 0xF0: // Sysex + case 0xF7: // Sysex + // Sysex data runs until the next 0xF0 or 0xF7, count how many byte to allocate, and rewind + fgetpos(fp, &saved_position); + while(m_status != fgetc(fp)){ + m_data_size++; + } + fsetpos(fp, &saved_position); + + m_data = malloc(sizeof(unsigned char) * m_data_size); + fread(m_data, sizeof(unsigned char), m_data_size, fp); + m_size += (m_data_size + 1); + fgetc(fp); // Throw away the end byte, although it counts for size + break; + default: // Midi + fread(&m_parameter1, sizeof(unsigned char), 1, fp); + fread(&m_parameter2, sizeof(unsigned char), 1, fp); + m_size += 2; + break; + } +} + + +void Event::init_midi(int delta, int status, int parameter1, int parameter2){ + m_delta = delta; + m_status = status; + m_parameter1 = parameter1; + m_parameter2 = parameter2; + m_size = 4; +} + + +Event::~Event(void){ + if(m_data){ + free(m_data); + } +} + +} diff --git a/utils/midi/source/file.cpp b/utils/midi/source/file.cpp new file mode 100644 index 0000000..2092173 --- /dev/null +++ b/utils/midi/source/file.cpp @@ -0,0 +1,37 @@ +#include "file.h" + +namespace Midi { + +File::File(void){ + m_header.cookie[0] = m_header.cookie[1] = m_header.cookie[2] = m_header.cookie[3] = 0; + m_header.size = 0; +} + + +void File::init_from_file(const char *filename){ + FILE *fp = fopen(filename, "rb"); + fread(&m_header, sizeof(midi_header_t), 1, fp); + // Fix up this Big Endian stuff + m_header.size = swap_endian_32(m_header.size); + m_header.format = swap_endian_16(m_header.format); + m_header.track_count = swap_endian_16(m_header.track_count); + m_header.ticks_per_quarter_note = swap_endian_16(m_header.ticks_per_quarter_note); + + // Read each track + Track *track; + for(int i = 0; i < m_header.track_count; i++){ + track = new Track(); + track->init_from_file(fp); + m_tracks.push_back(track); + } +} + + +File::~File(void){ + // Free all the midi tracks + for(unsigned int i = 0; i < m_tracks.size(); i++){ + delete m_tracks[i]; + } +} + +} diff --git a/utils/midi/source/helpers.cpp b/utils/midi/source/helpers.cpp new file mode 100644 index 0000000..8eee332 --- /dev/null +++ b/utils/midi/source/helpers.cpp @@ -0,0 +1,46 @@ +#include "helpers.h" + + +namespace Midi { + +//// +// I have eliminated any doubt that this function works on variable +// length uintvars up to 4 bytes. +unsigned int read_variable_length(FILE *fp, unsigned int *value_size) { + unsigned int value; + *value_size = 1; + + if((value = fgetc(fp)) & 0x80){ + unsigned char c; + value &= 0x7F; + do{ + (*value_size)++; + value = (value << 7) + ((c = fgetc(fp)) & 0x7F); + } while (c & 0x80); + } + return(value); +} + + +//// +// Swap 4 bytes +int swap_endian_32(int big_endian){ + register int little_endian; + little_endian = (big_endian & 0x000000FF); + little_endian = ((big_endian & 0x0000FF00) >> 0x08) | (little_endian << 0x08); + little_endian = ((big_endian & 0x00FF0000) >> 0x10) | (little_endian << 0x08); + little_endian = ((big_endian & 0xFF000000) >> 0x18) | (little_endian << 0x08); + return(little_endian); +} + + +//// +// Swap 2 bytes +short swap_endian_16(short big_endian){ + register short little_endian; + little_endian = (big_endian & 0x00FF); + little_endian = ((big_endian & 0xFF00) >> 0x08) | (little_endian << 0x08); + return(little_endian); +} + +} diff --git a/utils/midi/source/track.cpp b/utils/midi/source/track.cpp new file mode 100644 index 0000000..3cc636d --- /dev/null +++ b/utils/midi/source/track.cpp @@ -0,0 +1,37 @@ +#include "track.h" + +namespace Midi { + +Track::Track(void){ + m_header.cookie[0] = m_header.cookie[1] = m_header.cookie[2] = m_header.cookie[3] = 0; + m_header.size = 0; + m_total_size = 0; +} + + +void Track::init_from_file(FILE *fp){ + Event *track_event; + unsigned int bytes_read = 0; + unsigned char last_status = 0x0; + + fread(&m_header, sizeof(midi_track_header_t), 1, fp); + m_header.size = swap_endian_32(m_header.size); + if(m_header.size == 0) return; + + while(bytes_read < m_header.size){ + track_event = new Event(); + track_event->init_from_file(fp, last_status); + m_events.push_back(track_event); + last_status = track_event->status(); + bytes_read += track_event->bytes_read(); + } +} + +Track::~Track(void){ + // Free all the track events + for(unsigned int i = 0; i < m_events.size(); i++){ + delete m_events[i]; + } +} + +}